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>
This commit is contained in:
+101
-25
@@ -82,6 +82,21 @@ from key_sl_tp_lib import (
|
||||
sl_tp_mode_label,
|
||||
sl_tp_plan_summary_text,
|
||||
)
|
||||
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,
|
||||
)
|
||||
from key_monitor_lib import (
|
||||
KEY_DIRECTION_WATCH,
|
||||
KEY_MONITOR_ALERT_ONLY_TYPES,
|
||||
@@ -213,6 +228,7 @@ FORCE_CLOSE_ENABLED = os.getenv("FORCE_CLOSE_ENABLED", "false").lower() == "true
|
||||
FORCE_CLOSE_BJ_HOUR = int(os.getenv("FORCE_CLOSE_BJ_HOUR", "0"))
|
||||
# 自动划转:仅在北京时间该整点「小时」内尝试;transfer_logs.transfer_day 存 UTC 自然日(与 OKX 日界一致便于对账)
|
||||
AUTO_TRANSFER_BJ_HOUR = int(os.getenv("AUTO_TRANSFER_BJ_HOUR", "8"))
|
||||
POSITION_SIZING_MODE = load_position_sizing_mode()
|
||||
WECHAT_TIMEOUT_SECONDS = int(os.getenv("WECHAT_TIMEOUT_SECONDS", "10"))
|
||||
AI_TIMEOUT_SECONDS = int(os.getenv("AI_TIMEOUT_SECONDS", "120"))
|
||||
MONITOR_POLL_SECONDS = int(os.getenv("MONITOR_POLL_SECONDS", "3"))
|
||||
@@ -1389,6 +1405,27 @@ def init_db():
|
||||
|
||||
init_db()
|
||||
|
||||
|
||||
def _purge_key_monitors_if_full_margin():
|
||||
if not is_full_margin_mode(POSITION_SIZING_MODE):
|
||||
return
|
||||
conn = get_db()
|
||||
try:
|
||||
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_fib_monitor_limit,
|
||||
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()
|
||||
|
||||
|
||||
def get_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
@@ -4639,6 +4676,9 @@ def _market_open_for_key_monitor(
|
||||
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入(OKX 永续)。
|
||||
返回 (ok: bool, err_msg: Optional[str], detail: Optional[dict])
|
||||
"""
|
||||
ok_src, src_msg = assert_open_source_allowed(POSITION_SIZING_MODE, OPEN_SOURCE_KEY_AUTO)
|
||||
if not ok_src:
|
||||
return False, src_msg, None
|
||||
now = app_now()
|
||||
ok, reason = precheck_risk(conn, symbol, direction)
|
||||
if not ok:
|
||||
@@ -5669,6 +5709,11 @@ def render_main_page(page="trade"):
|
||||
},
|
||||
key_alert_max_times=KEY_ALERT_MAX_TIMES,
|
||||
risk_percent=RISK_PERCENT,
|
||||
position_sizing_mode=POSITION_SIZING_MODE,
|
||||
position_sizing_mode_label=mode_label_zh(POSITION_SIZING_MODE),
|
||||
open_position_button_label=(
|
||||
"开仓(全仓杠杆)" if is_full_margin_mode(POSITION_SIZING_MODE) else "开仓(以损定仓)"
|
||||
),
|
||||
breakeven_rr_trigger=BREAKEVEN_RR_TRIGGER,
|
||||
breakeven_offset_pct=BREAKEVEN_OFFSET_PCT,
|
||||
occupied_miss_total=occupied_miss_total,
|
||||
@@ -6403,6 +6448,12 @@ def add_key():
|
||||
if mt not in allowed_types:
|
||||
flash("监控类型无效")
|
||||
return redirect("/key_monitor")
|
||||
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")
|
||||
rank, total = _daily_volume_rank(symbol)
|
||||
if rank is None:
|
||||
flash("日成交量排名读取失败,请稍后重试")
|
||||
@@ -6573,19 +6624,6 @@ def add_order():
|
||||
flash(f"风控拒绝下单:{reason_live}")
|
||||
return redirect("/trade")
|
||||
exchange_symbol = normalize_okx_symbol(symbol)
|
||||
default_leverage = get_synced_leverage(exchange_symbol, direction) or infer_leverage(symbol)
|
||||
try:
|
||||
leverage_input = parse_positive_float(d.get("leverage"))
|
||||
leverage = int(leverage_input) if leverage_input is not None else default_leverage
|
||||
except Exception:
|
||||
conn.close()
|
||||
flash("杠杆参数格式错误")
|
||||
return redirect("/trade")
|
||||
if leverage <= 0:
|
||||
conn.close()
|
||||
flash("杠杆必须大于0")
|
||||
return redirect("/trade")
|
||||
|
||||
trading_day = get_trading_day(now)
|
||||
opens_today_before = conn.execute(
|
||||
"SELECT COUNT(*) FROM order_monitors WHERE session_date=?",
|
||||
@@ -6648,20 +6686,56 @@ def add_order():
|
||||
flash("止损方向不合法:请检查入场方向与止损价格关系")
|
||||
return redirect("/trade")
|
||||
risk_percent = max(0.01, float(RISK_PERCENT))
|
||||
risk_amount = round(capital_base * risk_percent / 100.0, 4)
|
||||
notional_value = round(risk_amount / risk_fraction, 4)
|
||||
margin_capital = round(notional_value / leverage, 4)
|
||||
if capital_base and margin_capital > capital_base:
|
||||
conn.close()
|
||||
flash("以损定仓后保证金超过当前交易资金,请放宽止损或降低风险比例")
|
||||
return redirect("/trade")
|
||||
if available_usdt is not None:
|
||||
max_margin = round(max(available_usdt * FULL_MARGIN_BUFFER_RATIO, 0), 4)
|
||||
if margin_capital > max_margin:
|
||||
risk_amount = round(capital_base * risk_percent / 100.0, FUNDS_DECIMALS)
|
||||
if is_full_margin_mode(POSITION_SIZING_MODE):
|
||||
ok_flat, flat_msg = full_margin_requires_flat_position(get_active_position_count(conn))
|
||||
if not ok_flat:
|
||||
conn.close()
|
||||
flash(f"保证金不足:交易账户可用约 {round(available_usdt,4)}U,当前最多建议 {max_margin}U")
|
||||
flash(flat_msg)
|
||||
return redirect("/trade")
|
||||
position_ratio = round(margin_capital / capital_base * 100, 2) if capital_base else 0
|
||||
leverage = leverage_for_full_margin(symbol, BTC_LEVERAGE, ALT_LEVERAGE)
|
||||
sizing, sizing_err = compute_full_margin_sizing(
|
||||
symbol=symbol,
|
||||
available_usdt=available_usdt if available_usdt is not None else 0.0,
|
||||
capital_base=capital_base,
|
||||
buffer_ratio=FULL_MARGIN_BUFFER_RATIO,
|
||||
btc_leverage=BTC_LEVERAGE,
|
||||
alt_leverage=ALT_LEVERAGE,
|
||||
funds_decimals=FUNDS_DECIMALS,
|
||||
)
|
||||
if sizing_err:
|
||||
conn.close()
|
||||
flash(sizing_err)
|
||||
return redirect("/trade")
|
||||
margin_capital = sizing["margin_capital"]
|
||||
notional_value = sizing["notional_value"]
|
||||
position_ratio = sizing["position_ratio"]
|
||||
else:
|
||||
default_leverage = get_synced_leverage(exchange_symbol, direction) or infer_leverage(symbol)
|
||||
try:
|
||||
leverage_input = parse_positive_float(d.get("leverage"))
|
||||
leverage = int(leverage_input) if leverage_input is not None else default_leverage
|
||||
except Exception:
|
||||
conn.close()
|
||||
flash("杠杆参数格式错误")
|
||||
return redirect("/trade")
|
||||
if leverage <= 0:
|
||||
conn.close()
|
||||
flash("杠杆必须大于0")
|
||||
return redirect("/trade")
|
||||
notional_value = round(risk_amount / risk_fraction, FUNDS_DECIMALS)
|
||||
margin_capital = round(notional_value / leverage, FUNDS_DECIMALS)
|
||||
if capital_base and margin_capital > capital_base:
|
||||
conn.close()
|
||||
flash("以损定仓后保证金超过当前交易资金,请放宽止损或降低风险比例")
|
||||
return redirect("/trade")
|
||||
if available_usdt is not None:
|
||||
max_margin = round(max(available_usdt * FULL_MARGIN_BUFFER_RATIO, 0), FUNDS_DECIMALS)
|
||||
if margin_capital > max_margin:
|
||||
conn.close()
|
||||
flash(f"保证金不足:交易账户可用约 {round(available_usdt, FUNDS_DECIMALS)}U,当前最多建议 {max_margin}U")
|
||||
return redirect("/trade")
|
||||
position_ratio = round(margin_capital / capital_base * 100, 2) if capital_base else 0
|
||||
try:
|
||||
amount, quote_price = prepare_order_amount(exchange_symbol, margin_capital, leverage, live_price)
|
||||
contract_size = get_contract_size(exchange_symbol)
|
||||
@@ -7825,6 +7899,8 @@ from strategy_trend_register import install_strategy_trend
|
||||
install_strategy_trading(app, _REPO_ROOT, app_module=sys.modules[__name__])
|
||||
install_strategy_trend(app, _REPO_ROOT, app_module=sys.modules[__name__])
|
||||
|
||||
_purge_key_monitors_if_full_margin()
|
||||
|
||||
|
||||
# 启动
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user