feat: bidirectional daily auto-transfer with position skip

Rebalance swap to AUTO_TRANSFER_AMOUNT at Beijing hour: top up from funding or sweep excess back. Skip and WeChat notify when active positions exist.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-04 09:57:25 +08:00
parent 0456d5fa2c
commit 29b0634c6d
16 changed files with 241 additions and 270 deletions
+130
View File
@@ -0,0 +1,130 @@
"""
每日自动划转:北京时间指定整点小时内,将交易账户(AUTO_TRANSFER_TO)余额调整至目标额。
- 交易账户 < 目标:从资金账户划入差额
- 交易账户 > 目标:将多余划回资金账户
- 有 active 持仓:不划转,写账簿并企业微信说明
"""
from __future__ import annotations
from typing import Any, Callable
def run_auto_transfer_once_per_day(
*,
enabled: bool,
bj_hour: int,
target_amount: float,
from_account: str,
to_account: str,
funds_decimals: int,
get_db: Callable[[], Any],
get_active_position_count: Callable[[Any], int],
get_account_usdt_total: Callable[[str], float | None],
execute_transfer_usdt: Callable[[float, str, str], tuple[bool, str, Any]],
send_wechat_msg: Callable[[str], None],
utc_now_dt: Callable[[], Any],
app_tz: Any,
utc_calendar_date_str: Callable[[], str],
app_now_str: Callable[[], str],
min_transfer: float = 0.01,
) -> None:
if not enabled:
return
utc_dt = utc_now_dt()
bj = utc_dt.astimezone(app_tz)
if bj.hour != bj_hour:
return
transfer_day = utc_calendar_date_str()
conn = get_db()
exists = conn.execute(
"SELECT id FROM transfer_logs WHERE transfer_type=? AND transfer_day=?",
("auto_daily", transfer_day),
).fetchone()
if exists:
conn.close()
return
def _log(
amount: float,
fr: str,
to: str,
status: str,
message: str,
*,
commit_close: bool = True,
) -> None:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, amount, fr, to, status, message[:500]),
)
conn.commit()
if commit_close:
conn.close()
active = get_active_position_count(conn)
if active > 0:
msg = f"持仓中({active}笔),本次资金无划转"
_log(0, from_account, to_account, "skipped", msg)
send_wechat_msg(
f"自动划转:{msg}\n"
f"目标:{to_account} 调整至 {round(float(target_amount), funds_decimals)}U\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
return
target = round(float(target_amount), funds_decimals)
trade_bal = get_account_usdt_total(to_account)
if trade_bal is None:
_log(
0,
from_account,
to_account,
"failed",
f"读取{to_account}账户USDT失败",
)
return
trade = round(float(trade_bal), funds_decimals)
diff = round(target - trade, funds_decimals)
if abs(diff) < min_transfer:
_log(
0,
from_account,
to_account,
"skipped",
f"{to_account}账户已为{trade}U(目标{target}U",
)
return
if diff > 0:
fr, to, amount = from_account, to_account, diff
action = "划入"
else:
fr, to, amount = to_account, from_account, round(abs(diff), funds_decimals)
action = "划出"
from_bal = get_account_usdt_total(fr)
if from_bal is not None and round(float(from_bal), funds_decimals) < amount:
cur = round(float(from_bal), funds_decimals)
_log(amount, fr, to, "failed", f"{fr}账户USDT不足,需{amount}U,当前{cur}U")
send_wechat_msg(
f"自动划转失败:{fr}余额不足,需{amount}U,当前{cur}U{action}{to_account}目标{target}U\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
return
ok, msg, _ = execute_transfer_usdt(amount, fr, to)
_log(amount, fr, to, "success" if ok else "failed", msg)
if ok:
send_wechat_msg(
f"自动划转成功:{to_account} {trade}U→目标{target}U{action}{amount}U {fr}->{to}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
else:
send_wechat_msg(
f"自动划转失败:计划{action}{amount}U {fr}->{to}(目标{target}U\n原因:{msg}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
+1 -1
View File
@@ -136,7 +136,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 合约/交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
+17 -65
View File
@@ -98,6 +98,7 @@ from key_monitor_full_margin_lib import (
monitor_type_disallowed_in_full_margin,
purge_disallowed_key_monitors,
)
from auto_transfer_daily_lib import run_auto_transfer_once_per_day
from key_monitor_lib import (
KEY_DIRECTION_WATCH,
KEY_MONITOR_ALERT_ONLY_TYPES,
@@ -2977,71 +2978,22 @@ def get_account_usdt_total(account_type):
def auto_transfer_once_per_day():
if not AUTO_TRANSFER_ENABLED:
return
utc_dt = utc_now_dt()
bj = utc_dt.astimezone(APP_TZ)
if bj.hour != AUTO_TRANSFER_BJ_HOUR:
return
transfer_day = utc_calendar_date_str()
conn = get_db()
exists = conn.execute(
"SELECT id FROM transfer_logs WHERE transfer_type=? AND transfer_day=?",
("auto_daily", transfer_day)
).fetchone()
if exists:
conn.close()
return
target_amount = AUTO_TRANSFER_AMOUNT
to_balance = get_account_usdt_total(AUTO_TRANSFER_TO)
from_balance = get_account_usdt_total(AUTO_TRANSFER_FROM)
if to_balance is None:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"读取{AUTO_TRANSFER_TO}账户USDT失败")
)
conn.commit()
conn.close()
return
needed = round(max(target_amount - float(to_balance), 0), FUNDS_DECIMALS)
if needed <= 0:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "skipped", f"{AUTO_TRANSFER_TO}账户已达到目标{target_amount}U")
)
conn.commit()
conn.close()
return
if from_balance is not None and from_balance < needed:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{needed}U,当前{round(from_balance, FUNDS_DECIMALS)}U")
)
conn.commit()
conn.close()
send_wechat_msg(
f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{needed}U,当前{round(from_balance, FUNDS_DECIMALS)}U\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
return
ok, msg, _ = execute_transfer_usdt(needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO)
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "success" if ok else "failed", msg[:500])
)
conn.commit()
conn.close()
if ok:
send_wechat_msg(
f"自动划转成功:补足到{target_amount}U,实际划转{needed}U "
f"{AUTO_TRANSFER_FROM}->{AUTO_TRANSFER_TO}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
else:
send_wechat_msg(
f"自动划转失败:计划补足到{target_amount}U,需划转{needed}U\n原因:{msg}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
run_auto_transfer_once_per_day(
enabled=AUTO_TRANSFER_ENABLED,
bj_hour=AUTO_TRANSFER_BJ_HOUR,
target_amount=AUTO_TRANSFER_AMOUNT,
from_account=AUTO_TRANSFER_FROM,
to_account=AUTO_TRANSFER_TO,
funds_decimals=FUNDS_DECIMALS,
get_db=get_db,
get_active_position_count=get_active_position_count,
get_account_usdt_total=get_account_usdt_total,
execute_transfer_usdt=execute_transfer_usdt,
send_wechat_msg=send_wechat_msg,
utc_now_dt=utc_now_dt,
app_tz=APP_TZ,
utc_calendar_date_str=utc_calendar_date_str,
app_now_str=app_now_str,
)
+1 -1
View File
@@ -426,7 +426,7 @@
|移动保本:下单可勾选关闭;开启时 {{ breakeven_rr_trigger }}R 触发(每 1R 阶梯上移),偏移 {{ breakeven_offset_pct }}%
</div>
<div class="rule-tip">
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;界面时间为北京;将 {{ auto_transfer_to }} 补足到 {{ auto_transfer_amount }}U,来自 {{ auto_transfer_from }}
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;将 {{ auto_transfer_to }} 调整至 {{ auto_transfer_amount }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }}<strong>持仓中不划转</strong>并微信通知
</div>
<form action="/manual_transfer" method="post" class="form-row">
<input name="amount" type="number" min="0.01" step="0.01" placeholder="手动划转金额U" required>
+1 -1
View File
@@ -142,7 +142,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
+17 -65
View File
@@ -97,6 +97,7 @@ from key_monitor_full_margin_lib import (
monitor_type_disallowed_in_full_margin,
purge_disallowed_key_monitors,
)
from auto_transfer_daily_lib import run_auto_transfer_once_per_day
from key_monitor_lib import (
KEY_DIRECTION_WATCH,
KEY_MONITOR_ALERT_ONLY_TYPES,
@@ -2670,71 +2671,22 @@ def get_account_usdt_total(account_type):
def auto_transfer_once_per_day():
if not AUTO_TRANSFER_ENABLED:
return
utc_dt = utc_now_dt()
bj = utc_dt.astimezone(APP_TZ)
if bj.hour != AUTO_TRANSFER_BJ_HOUR:
return
transfer_day = utc_calendar_date_str()
conn = get_db()
exists = conn.execute(
"SELECT id FROM transfer_logs WHERE transfer_type=? AND transfer_day=?",
("auto_daily", transfer_day)
).fetchone()
if exists:
conn.close()
return
target_amount = AUTO_TRANSFER_AMOUNT
to_balance = get_account_usdt_total(AUTO_TRANSFER_TO)
from_balance = get_account_usdt_total(AUTO_TRANSFER_FROM)
if to_balance is None:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"读取{AUTO_TRANSFER_TO}账户USDT失败")
)
conn.commit()
conn.close()
return
needed = round(max(target_amount - float(to_balance), 0), 4)
if needed <= 0:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "skipped", f"{AUTO_TRANSFER_TO}账户已达到目标{round(float(target_amount), 2)}U")
)
conn.commit()
conn.close()
return
if from_balance is not None and from_balance < needed:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U")
)
conn.commit()
conn.close()
send_wechat_msg(
f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
return
ok, msg, _ = execute_transfer_usdt(needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO)
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "success" if ok else "failed", msg[:500])
)
conn.commit()
conn.close()
if ok:
send_wechat_msg(
f"自动划转成功:补足到{round(float(target_amount), 2)}U,实际划转{round(needed, 2)}U "
f"{AUTO_TRANSFER_FROM}->{AUTO_TRANSFER_TO}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
else:
send_wechat_msg(
f"自动划转失败:计划补足到{round(float(target_amount), 2)}U,需划转{round(needed, 2)}U\n原因:{msg}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
run_auto_transfer_once_per_day(
enabled=AUTO_TRANSFER_ENABLED,
bj_hour=AUTO_TRANSFER_BJ_HOUR,
target_amount=AUTO_TRANSFER_AMOUNT,
from_account=AUTO_TRANSFER_FROM,
to_account=AUTO_TRANSFER_TO,
funds_decimals=2,
get_db=get_db,
get_active_position_count=get_active_position_count,
get_account_usdt_total=get_account_usdt_total,
execute_transfer_usdt=execute_transfer_usdt,
send_wechat_msg=send_wechat_msg,
utc_now_dt=utc_now_dt,
app_tz=APP_TZ,
utc_calendar_date_str=utc_calendar_date_str,
app_now_str=app_now_str,
)
+1 -1
View File
@@ -426,7 +426,7 @@
|移动保本:下单可勾选关闭;开启时 {{ breakeven_rr_trigger }}R 触发(每 1R 阶梯上移),偏移 {{ breakeven_offset_pct }}%
</div>
<div class="rule-tip">
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;界面时间为北京;将 {{ auto_transfer_to }} 补足到 {{ auto_transfer_amount }}U,来自 {{ auto_transfer_from }}
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;将 {{ auto_transfer_to }} 调整至 {{ auto_transfer_amount }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }}<strong>持仓中不划转</strong>并微信通知
</div>
<form action="/manual_transfer" method="post" class="form-row">
<input name="amount" type="number" min="0.01" step="0.01" placeholder="手动划转金额U" required>
+1 -1
View File
@@ -110,7 +110,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
+17 -65
View File
@@ -49,6 +49,7 @@ from key_monitor_full_margin_lib import (
monitor_type_disallowed_in_full_margin,
purge_disallowed_key_monitors,
)
from auto_transfer_daily_lib import run_auto_transfer_once_per_day
from form_submit_lib import check_duplicate_submit, submit_scope_add_key, submit_scope_add_order
from order_monitor_display_lib import (
apply_order_price_display_fields,
@@ -2675,71 +2676,22 @@ def get_account_usdt_total(account_type):
def auto_transfer_once_per_day():
if not AUTO_TRANSFER_ENABLED:
return
utc_dt = utc_now_dt()
bj = utc_dt.astimezone(APP_TZ)
if bj.hour != AUTO_TRANSFER_BJ_HOUR:
return
transfer_day = utc_calendar_date_str()
conn = get_db()
exists = conn.execute(
"SELECT id FROM transfer_logs WHERE transfer_type=? AND transfer_day=?",
("auto_daily", transfer_day)
).fetchone()
if exists:
conn.close()
return
target_amount = AUTO_TRANSFER_AMOUNT
to_balance = get_account_usdt_total(AUTO_TRANSFER_TO)
from_balance = get_account_usdt_total(AUTO_TRANSFER_FROM)
if to_balance is None:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"读取{AUTO_TRANSFER_TO}账户USDT失败")
)
conn.commit()
conn.close()
return
needed = round(max(target_amount - float(to_balance), 0), 4)
if needed <= 0:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "skipped", f"{AUTO_TRANSFER_TO}账户已达到目标{target_amount}U")
)
conn.commit()
conn.close()
return
if from_balance is not None and from_balance < needed:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{needed}U,当前{round(from_balance,4)}U")
)
conn.commit()
conn.close()
send_wechat_msg(
f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{needed}U,当前{round(from_balance,4)}U\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
return
ok, msg, _ = execute_transfer_usdt(needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO)
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "success" if ok else "failed", msg[:500])
)
conn.commit()
conn.close()
if ok:
send_wechat_msg(
f"自动划转成功:补足到{target_amount}U,实际划转{needed}U "
f"{AUTO_TRANSFER_FROM}->{AUTO_TRANSFER_TO}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
else:
send_wechat_msg(
f"自动划转失败:计划补足到{target_amount}U,需划转{needed}U\n原因:{msg}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
run_auto_transfer_once_per_day(
enabled=AUTO_TRANSFER_ENABLED,
bj_hour=AUTO_TRANSFER_BJ_HOUR,
target_amount=AUTO_TRANSFER_AMOUNT,
from_account=AUTO_TRANSFER_FROM,
to_account=AUTO_TRANSFER_TO,
funds_decimals=2,
get_db=get_db,
get_active_position_count=get_active_position_count,
get_account_usdt_total=get_account_usdt_total,
execute_transfer_usdt=execute_transfer_usdt,
send_wechat_msg=send_wechat_msg,
utc_now_dt=utc_now_dt,
app_tz=APP_TZ,
utc_calendar_date_str=utc_calendar_date_str,
app_now_str=app_now_str,
)
+1 -1
View File
@@ -311,7 +311,7 @@
|移动保本:下单可勾选关闭;开启时 {{ breakeven_rr_trigger }}R 触发(每 1R 阶梯上移),偏移 {{ breakeven_offset_pct }}%
</div>
<div class="rule-tip">
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;界面时间为北京;将 {{ auto_transfer_to }} 补足到 {{ money_fmt(auto_transfer_amount) }}U,来自 {{ auto_transfer_from }}
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;将 {{ auto_transfer_to }} 调整至 {{ money_fmt(auto_transfer_amount) }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }}<strong>持仓中不划转</strong>并微信通知
</div>
<form action="/manual_transfer" method="post" class="form-row">
<input name="amount" type="number" min="0.01" step="0.01" placeholder="手动划转金额U" required>
+1 -1
View File
@@ -101,7 +101,7 @@ FULL_MARGIN_BUFFER_RATIO=0.98
# 自动划转(页顶「将 swap 补足到 XU」;与 DAILY_START_CAPITAL 独立,需一致时请设为相同值)
# =============================================================================
AUTO_TRANSFER_ENABLED=false
# 交易账户(AUTO_TRANSFER_TO)补足到的 USDT 总额,非每日开仓基数
# 交易账户(swap)目标余额 U:每日 8 点(北京)自动划入或划出至 funding;持仓中不划转
AUTO_TRANSFER_AMOUNT=30
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
+17 -65
View File
@@ -97,6 +97,7 @@ from key_monitor_full_margin_lib import (
monitor_type_disallowed_in_full_margin,
purge_disallowed_key_monitors,
)
from auto_transfer_daily_lib import run_auto_transfer_once_per_day
from key_monitor_lib import (
KEY_DIRECTION_WATCH,
KEY_MONITOR_ALERT_ONLY_TYPES,
@@ -2356,71 +2357,22 @@ def get_account_usdt_total(account_type):
def auto_transfer_once_per_day():
if not AUTO_TRANSFER_ENABLED:
return
utc_dt = utc_now_dt()
bj = utc_dt.astimezone(APP_TZ)
if bj.hour != AUTO_TRANSFER_BJ_HOUR:
return
transfer_day = utc_calendar_date_str()
conn = get_db()
exists = conn.execute(
"SELECT id FROM transfer_logs WHERE transfer_type=? AND transfer_day=?",
("auto_daily", transfer_day)
).fetchone()
if exists:
conn.close()
return
target_amount = AUTO_TRANSFER_AMOUNT
to_balance = get_account_usdt_total(AUTO_TRANSFER_TO)
from_balance = get_account_usdt_total(AUTO_TRANSFER_FROM)
if to_balance is None:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"读取{AUTO_TRANSFER_TO}账户USDT失败")
)
conn.commit()
conn.close()
return
needed = round(max(target_amount - float(to_balance), 0), 4)
if needed <= 0:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, 0, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "skipped", f"{AUTO_TRANSFER_TO}账户已达到目标{target_amount}U")
)
conn.commit()
conn.close()
return
if from_balance is not None and from_balance < needed:
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U")
)
conn.commit()
conn.close()
send_wechat_msg(
f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
return
ok, msg, _ = execute_transfer_usdt(needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO)
conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "success" if ok else "failed", msg[:500])
)
conn.commit()
conn.close()
if ok:
send_wechat_msg(
f"自动划转成功:补足到{round(float(target_amount), 2)}U,实际划转{round(needed, 2)}U "
f"{AUTO_TRANSFER_FROM}->{AUTO_TRANSFER_TO}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
)
else:
send_wechat_msg(
f"自动划转失败:计划补足到{round(float(target_amount), 2)}U,需划转{round(needed, 2)}U\n原因:{msg}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
run_auto_transfer_once_per_day(
enabled=AUTO_TRANSFER_ENABLED,
bj_hour=AUTO_TRANSFER_BJ_HOUR,
target_amount=AUTO_TRANSFER_AMOUNT,
from_account=AUTO_TRANSFER_FROM,
to_account=AUTO_TRANSFER_TO,
funds_decimals=FUNDS_DECIMALS,
get_db=get_db,
get_active_position_count=get_active_position_count,
get_account_usdt_total=get_account_usdt_total,
execute_transfer_usdt=execute_transfer_usdt,
send_wechat_msg=send_wechat_msg,
utc_now_dt=utc_now_dt,
app_tz=APP_TZ,
utc_calendar_date_str=utc_calendar_date_str,
app_now_str=app_now_str,
)
+1 -1
View File
@@ -435,7 +435,7 @@
|移动保本:下单可勾选关闭;开启时 {{ breakeven_rr_trigger }}R 触发(每 1R 阶梯上移),偏移 {{ breakeven_offset_pct }}%
</div>
<div class="rule-tip">
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;界面时间为北京;将 {{ auto_transfer_to }} 补足到 {{ auto_transfer_amount }}U,来自 {{ auto_transfer_from }}
划转:自动划转 {{ '开启' if auto_transfer_enabled else '关闭' }}(每天<strong>北京时间 {{ auto_transfer_bj_hour }}:00</strong>起该整点小时内尝试;账簿按 <strong>UTC 自然日</strong>去重;将 {{ auto_transfer_to }} 调整至 {{ auto_transfer_amount }}U:不足从 {{ auto_transfer_from }} 划入、超出划回 {{ auto_transfer_from }}<strong>持仓中不划转</strong>并微信通知
</div>
<form action="/manual_transfer" method="post" class="form-row">
<input name="amount" type="number" min="0.01" step="0.01" placeholder="手动划转金额U" required>
+1 -1
View File
@@ -87,7 +87,7 @@ bash deploy/setup_env.sh
## 四所 `.env` 自动划转项(已有 .env 时)
`AUTO_TRANSFER_AMOUNT` 等与 `DAILY_START_CAPITAL` **独立**;四所 `.env.example` 已统一注释。若服务器上已有 `.env`,可合并写入(不覆盖 API 密钥):
`AUTO_TRANSFER_AMOUNT`为交易账户目标余额(北京时间 8 点自动划入/划出,**持仓中不划转**并微信通知),`DAILY_START_CAPITAL` **独立**。若服务器上已有 `.env`,可合并写入(不覆盖 API 密钥):
```bash
python scripts/sync_four_exchange_transfer_env.py
+33
View File
@@ -0,0 +1,33 @@
# 每日自动划转(四所统一)
## 行为
`.env` 开启 `AUTO_TRANSFER_ENABLED=true` 后,监控轮询在**北京时间 `AUTO_TRANSFER_BJ_HOUR` 整点所在小时**内(默认 8:00–8:59)执行一次(按 **UTC 自然日** 去重):
| 交易账户 (`AUTO_TRANSFER_TO`,默认 swap) | 动作 |
|------------------------------------------|------|
| 余额 **低于** `AUTO_TRANSFER_AMOUNT` | 从 `AUTO_TRANSFER_FROM`(默认 funding)划入差额 |
| 余额 **高于** `AUTO_TRANSFER_AMOUNT` | 将多余划回 `AUTO_TRANSFER_FROM` |
| 与目标相差 &lt; 0.01U | 跳过,不写划转 |
| 存在 **active** 持仓(`order_monitors` | **不划转**,写账簿 `skipped`,并**企业微信**说明「持仓中,本次资金无划转」 |
## 配置示例(目标 50U
```env
AUTO_TRANSFER_ENABLED=true
AUTO_TRANSFER_AMOUNT=50
AUTO_TRANSFER_FROM=funding
AUTO_TRANSFER_TO=swap
AUTO_TRANSFER_BJ_HOUR=8
```
API Key 须具备万向划转权限(与手动划转相同)。
## 部署
```bash
git pull
python scripts/sync_four_exchange_transfer_env.py # 仅补全缺项
# 编辑各所 .envAUTO_TRANSFER_ENABLED=true、AUTO_TRANSFER_AMOUNT=50
pm2 restart crypto-monitor-binance crypto-monitor-okx crypto-monitor-gate crypto-monitor-gate-bot
```
+1 -1
View File
@@ -23,7 +23,7 @@ INSTANCES = (
"crypto_monitor_gate_bot",
)
# 四所统一(页顶「将 swap 补足到 XU」= AUTO_TRANSFER_AMOUNT,与 DAILY_START_CAPITAL 独立)
# 四所统一(页顶「将 swap 调整至 XU」= AUTO_TRANSFER_AMOUNT双向归集,与 DAILY_START_CAPITAL 独立)
COMMON_KEYS = {
"AUTO_TRANSFER_ENABLED": "false",
"AUTO_TRANSFER_FROM": "funding",