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>
116 lines
3.5 KiB
Python
116 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
将自动划转 / 币安资金账户相关项写入四所实例 .env(已存在则更新,缺失则追加)。
|
||
|
||
用法(仓库根目录):
|
||
python scripts/sync_four_exchange_transfer_env.py
|
||
python scripts/sync_four_exchange_transfer_env.py --dry-run
|
||
|
||
不修改 API 密钥与其它自定义项;若 .env 不存在则跳过(请先从 .env.example 复制)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import os
|
||
import re
|
||
|
||
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
|
||
INSTANCES = (
|
||
"crypto_monitor_binance",
|
||
"crypto_monitor_okx",
|
||
"crypto_monitor_gate",
|
||
"crypto_monitor_gate_bot",
|
||
)
|
||
|
||
# 四所统一(页顶「将 swap 调整至 XU」= AUTO_TRANSFER_AMOUNT,双向归集,与 DAILY_START_CAPITAL 独立)
|
||
COMMON_KEYS = {
|
||
"AUTO_TRANSFER_ENABLED": "false",
|
||
"AUTO_TRANSFER_FROM": "funding",
|
||
"AUTO_TRANSFER_TO": "swap",
|
||
"TRANSFER_CCY": "USDT",
|
||
"AUTO_TRANSFER_BJ_HOUR": "8",
|
||
}
|
||
|
||
BINANCE_ONLY = {
|
||
"BINANCE_FUNDING_INCLUDE_SPOT": "false",
|
||
}
|
||
|
||
|
||
def _parse_env(path: str) -> list[str]:
|
||
if not os.path.isfile(path):
|
||
return []
|
||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||
return f.read().replace("\r\n", "\n").replace("\r", "\n").splitlines()
|
||
|
||
|
||
def _env_get(lines: list[str], key: str) -> str | None:
|
||
pat = re.compile(r"^\s*" + re.escape(key) + r"\s*=\s*(.*)\s*$")
|
||
for line in lines:
|
||
m = pat.match(line)
|
||
if m:
|
||
return m.group(1).strip().strip('"').strip("'")
|
||
return None
|
||
|
||
|
||
def _upsert(lines: list[str], key: str, value: str) -> list[str]:
|
||
pat = re.compile(r"^\s*" + re.escape(key) + r"\s*=")
|
||
out = []
|
||
replaced = False
|
||
for line in lines:
|
||
if pat.match(line):
|
||
if not replaced:
|
||
out.append(f"{key}={value}")
|
||
replaced = True
|
||
continue
|
||
out.append(line)
|
||
if not replaced:
|
||
if out and out[-1].strip():
|
||
out.append("")
|
||
out.append(f"{key}={value}")
|
||
return out
|
||
|
||
|
||
def _ensure_transfer_block(lines: list[str], extra: dict[str, str]) -> list[str]:
|
||
daily = _env_get(lines, "DAILY_START_CAPITAL")
|
||
amount = _env_get(lines, "AUTO_TRANSFER_AMOUNT")
|
||
if amount is None:
|
||
amount = daily if daily else "30"
|
||
keys = dict(COMMON_KEYS)
|
||
keys["AUTO_TRANSFER_AMOUNT"] = amount
|
||
keys.update(extra)
|
||
for k, v in keys.items():
|
||
lines = _upsert(lines, k, v)
|
||
return lines
|
||
|
||
|
||
def sync_one(dir_name: str, dry_run: bool) -> str:
|
||
env_path = os.path.join(REPO, dir_name, ".env")
|
||
if not os.path.isfile(env_path):
|
||
return f"SKIP {dir_name}: 无 .env(请 cp .env.example .env)"
|
||
lines = _parse_env(env_path)
|
||
extra = dict(BINANCE_ONLY) if dir_name == "crypto_monitor_binance" else {}
|
||
new_lines = _ensure_transfer_block(lines, extra)
|
||
if new_lines == lines:
|
||
return f"OK {dir_name}: 已是最新"
|
||
if dry_run:
|
||
return f"DRY {dir_name}: 将更新 {len([1 for a,b in zip(lines,new_lines) if a!=b])}+ 行"
|
||
with open(env_path, "w", encoding="utf-8", newline="\n") as f:
|
||
f.write("\n".join(new_lines))
|
||
if new_lines and new_lines[-1].strip():
|
||
f.write("\n")
|
||
amt = _env_get(new_lines, "AUTO_TRANSFER_AMOUNT")
|
||
return f"DONE {dir_name}: AUTO_TRANSFER_AMOUNT={amt}"
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--dry-run", action="store_true")
|
||
args = ap.parse_args()
|
||
for name in INSTANCES:
|
||
print(sync_one(name, args.dry_run))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|