Files
crypto_monitor/scripts/patch_position_sizing_to_exchanges.py
T
dekun f7bac11694 feat: add full-margin position sizing mode across four exchanges
Env POSITION_SIZING_MODE switches risk vs full-margin (available*buffer, BTC/ETH 10x). Blocks trend/roll/key auto opens in full margin, purges breakout/fib monitors with WeChat notice, keeps RR check and initial SL snapshot for records.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 08:24:35 +08:00

198 lines
7.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""一次性:为 okx/gate/gate_bot 注入与 binance 一致的计仓模式补丁(已 patch 过则跳过)。"""
from __future__ import annotations
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
IMPORT_BLOCK = '''from position_sizing_lib import (
OPEN_SOURCE_KEY_AUTO,
OPEN_SOURCE_MANUAL,
assert_open_source_allowed,
compute_full_margin_sizing,
full_margin_requires_flat_position,
is_full_margin_mode,
leverage_for_full_margin,
load_position_sizing_mode,
mode_label_zh,
)
from key_monitor_full_margin_lib import (
monitor_type_disallowed_in_full_margin,
purge_disallowed_key_monitors,
)
'''
ENV_LINE = (
"# 计仓模式:risk=以损定仓(默认);full_margin=合约可用×比例全仓杠杆(仅 env 切换,须无仓)\n"
"POSITION_SIZING_MODE = load_position_sizing_mode()\n"
)
PURGE_FN = '''
def _purge_key_monitors_if_full_margin():
if not is_full_margin_mode(POSITION_SIZING_MODE):
return
conn = get_db()
try:
cancel = globals().get("_cancel_fib_monitor_limit")
if not callable(cancel):
cancel = lambda _row: None
purge_disallowed_key_monitors(
conn,
sizing_mode=POSITION_SIZING_MODE,
select_rows=lambda c: c.execute("SELECT * FROM key_monitors").fetchall(),
cancel_fib_limit=cancel,
delete_monitor=lambda c, kid: c.execute("DELETE FROM key_monitors WHERE id=?", (kid,)),
send_wechat=send_wechat_msg,
)
conn.commit()
except Exception as e:
print(f"[full_margin] purge key monitors: {e}", flush=True)
finally:
conn.close()
'''
MARKET_OPEN_GUARD = ''' ok_src, src_msg = assert_open_source_allowed(POSITION_SIZING_MODE, OPEN_SOURCE_KEY_AUTO)
if not ok_src:
return False, src_msg, None
'''
ADD_KEY_GUARD = ''' if is_full_margin_mode(POSITION_SIZING_MODE) and monitor_type_disallowed_in_full_margin(mt):
flash(
"全仓杠杆模式下不可添加箱体/收敛突破或斐波监控;"
"请改用阻力/支撑(仅提醒),或切换 POSITION_SIZING_MODE=risk 并重启(须无持仓)。"
)
return redirect("/key_monitor")
'''
TEMPLATE_RULE = ''' <div class="rule-tip">
计仓模式:<strong>{{ position_sizing_mode_label }}</strong>(仅 .env <code>POSITION_SIZING_MODE</code>,须无仓后重启)
{% if position_sizing_mode == 'full_margin' %}
|全仓:合约可用×{{ full_margin_buffer_ratio }}BTC/ETH {{ btc_leverage }}x、其它 {{ alt_leverage }}x,单仓;张数按交易所精度
{% else %}
|以损定仓:风险 {{ risk_percent }}%
{% endif %}
|移动保本:下单可勾选关闭;开启时 {{ breakeven_rr_trigger }}R 触发(每 1R 阶梯上移),偏移 {{ breakeven_offset_pct }}%
</div>'''
APPS = [
("crypto_monitor_okx", 4, "_market_open_for_key_monitor", True),
("crypto_monitor_gate", 2, "_market_open_for_key_monitor", True),
("crypto_monitor_gate_bot", 4, None, False),
]
def patch_app(app_dir: str, funds_dec: int, market_fn: str | None, has_fib: bool):
path = ROOT / app_dir / "app.py"
text = path.read_text(encoding="utf-8")
if "POSITION_SIZING_MODE" in text:
print(f"SKIP {app_dir}/app.py (already patched)")
return
if "from position_sizing_lib import" not in text:
anchor = "from key_monitor_lib import ("
if anchor not in text:
anchor = "from form_submit_lib import"
text = text.replace(
anchor,
IMPORT_BLOCK + "\n" + anchor,
1,
)
else:
text = text.replace(anchor, IMPORT_BLOCK + anchor, 1)
if "POSITION_SIZING_MODE = load_position_sizing_mode()" not in text:
text = text.replace(
"AUTO_TRANSFER_BJ_HOUR = int(os.getenv(\"AUTO_TRANSFER_BJ_HOUR\", \"8\"))\n",
"AUTO_TRANSFER_BJ_HOUR = int(os.getenv(\"AUTO_TRANSFER_BJ_HOUR\", \"8\"))\n" + ENV_LINE,
1,
)
if "_purge_key_monitors_if_full_margin" not in text:
text = text.replace("init_db()\n\n\ndef get_db():", "init_db()" + PURGE_FN + "\ndef get_db():", 1)
text = text.replace(
"install_strategy_trend(app,",
"_purge_key_monitors_if_full_margin()\n\ninstall_strategy_trend(app,",
1,
)
if market_fn and MARKET_OPEN_GUARD.strip() not in text:
text = text.replace(
f"def {market_fn}(\n",
f"def {market_fn}(\n",
1,
)
text = text.replace(
' """\n 与手动',
MARKET_OPEN_GUARD + ' """\n 与手动',
1,
)
# fallback: after docstring closing
if MARKET_OPEN_GUARD.strip() not in text:
pat = rf"(def {market_fn}\([^)]+\):\s*\n\s*\"\"\"[^\"\"]*\"\"\"\s*\n)"
text = re.sub(pat, r"\1" + MARKET_OPEN_GUARD, text, count=1)
if has_fib and ADD_KEY_GUARD.strip() not in text:
text = text.replace(
' if mt not in allowed_types:',
ADD_KEY_GUARD + ' if mt not in allowed_types:',
1,
) if "if mt not in allowed_types:" in text else text.replace(
' rank, total = _daily_volume_rank(symbol)',
ADD_KEY_GUARD + ' rank, total = _daily_volume_rank(symbol)',
1,
)
# render_template risk_percent= add template vars
if "position_sizing_mode=POSITION_SIZING_MODE" not in text:
text = text.replace(
"risk_percent=RISK_PERCENT,\n",
"risk_percent=RISK_PERCENT,\n"
" position_sizing_mode=POSITION_SIZING_MODE,\n"
" position_sizing_mode_label=mode_label_zh(POSITION_SIZING_MODE),\n"
" open_position_button_label=(\n"
' "开仓(全仓杠杆)" if is_full_margin_mode(POSITION_SIZING_MODE) else "开仓(以损定仓)"\n'
" ),\n",
1,
)
path.write_text(text, encoding="utf-8")
print(f"DONE {app_dir}/app.py (partial — verify add_order block manually if needed)")
def patch_template(app_dir: str):
tpl = ROOT / app_dir / "templates" / "index.html"
if not tpl.exists():
return
text = tpl.read_text(encoding="utf-8")
if "position_sizing_mode_label" in text:
print(f"SKIP {tpl}")
return
old = re.search(
r'<div class="rule-tip">\s*以损定仓:风险 \{\{ risk_percent \}\}%.*?</div>',
text,
re.S,
)
if old:
text = text[: old.start()] + TEMPLATE_RULE + text[old.end() :]
text = text.replace(
'<button type="submit">开仓(以损定仓)</button>',
'<button type="submit">{{ open_position_button_label }}</button>',
)
text = text.replace(
'<input id="order-leverage" name="leverage" type="number" min="1" step="1" placeholder="杠杆(可选)">',
'{% if position_sizing_mode != \'full_margin\' %}\n'
' <input id="order-leverage" name="leverage" type="number" min="1" step="1" placeholder="杠杆(可选)">\n'
' {% endif %}',
1,
)
tpl.write_text(text, encoding="utf-8")
print(f"DONE {tpl}")
def main():
for app_dir, funds, mfn, fib in APPS:
patch_app(app_dir, funds, mfn, fib)
patch_template(app_dir)
if __name__ == "__main__":
main()