29b0634c6d
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>
131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
"""
|
||
每日自动划转:北京时间指定整点小时内,将交易账户(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()}"
|
||
)
|