From 934e48b9a845fff629118ea72e5c8b24823e1828 Mon Sep 17 00:00:00 2001 From: dekun Date: Fri, 5 Jun 2026 09:08:45 +0800 Subject: [PATCH] fix: show risk amount only in full-margin mode across four exchanges Co-authored-by: Cursor --- crypto_monitor_binance/app.py | 12 ++++-- crypto_monitor_binance/templates/index.html | 2 +- crypto_monitor_gate/app.py | 12 ++++-- crypto_monitor_gate/templates/index.html | 2 +- crypto_monitor_gate_bot/app.py | 12 ++++-- crypto_monitor_gate_bot/templates/index.html | 2 +- crypto_monitor_okx/app.py | 12 ++++-- crypto_monitor_okx/templates/index.html | 2 +- manual_trading_hub/static/app.js | 42 +++++++++++++++----- manual_trading_hub/static/index.html | 2 +- position_sizing_lib.py | 36 +++++++++++++++++ tests/test_position_sizing_risk_display.py | 34 ++++++++++++++++ 12 files changed, 143 insertions(+), 27 deletions(-) create mode 100644 tests/test_position_sizing_risk_display.py diff --git a/crypto_monitor_binance/app.py b/crypto_monitor_binance/app.py index e97ccb4..54e1c19 100644 --- a/crypto_monitor_binance/app.py +++ b/crypto_monitor_binance/app.py @@ -88,11 +88,13 @@ from position_sizing_lib import ( OPEN_SOURCE_TREND, assert_open_source_allowed, compute_full_margin_sizing, + format_risk_display_text, full_margin_requires_flat_position, is_full_margin_mode, leverage_for_full_margin, load_position_sizing_mode, mode_label_zh, + risk_percent_for_storage, ) from key_monitor_full_margin_lib import ( monitor_type_disallowed_in_full_margin, @@ -7075,6 +7077,10 @@ def add_order(): breakeven_offset_pct = float(BREAKEVEN_OFFSET_PCT) breakeven_step_r = float(BREAKEVEN_STEP_R) if float(BREAKEVEN_STEP_R) > 0 else 1.0 risk_amount_final = calc_risk_amount_from_plan(direction, trigger_price, stop_loss, margin_capital, leverage) or risk_amount + risk_percent_db = risk_percent_for_storage(POSITION_SIZING_MODE, risk_percent) + risk_display = format_risk_display_text( + POSITION_SIZING_MODE, risk_percent, risk_amount_final, decimals=FUNDS_DECIMALS + ) if direction == "short": breakeven_price = round(float(trigger_price) * (1 - breakeven_offset_pct / 100.0), 8) else: @@ -7084,7 +7090,7 @@ def add_order(): "INSERT INTO order_monitors (symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, margin_capital, leverage, trade_style, risk_percent, risk_amount, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", ( symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit, - margin_capital, leverage, trade_style, risk_percent, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, + margin_capital, leverage, trade_style, risk_percent_db, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day, ORDER_MONITOR_TYPE_MANUAL, @@ -7200,7 +7206,7 @@ def add_order(): "🧾 订单基础信息", f"🔖 交易所订单 ID:{open_order_id}", f"📈 交易风格:{style_zh}", - f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), FUNDS_DECIMALS)} U", + f"⚠️ 单笔风控风险:{risk_display}", "📊 仓位配置详情", f"账户基数:{account_base_display} USDT", f"合约杠杆:{leverage} 倍", @@ -7223,7 +7229,7 @@ def add_order(): send_wechat_msg("\n".join(wx_lines)) flash_lines = [ - f"实盘开单成功:风格 {trade_style};风险 {risk_percent}%≈{risk_amount_final}U;基数 {margin_capital}U,杠杆 {leverage}x,名义仓位 {notional_value}U,仓位占比 {position_ratio}%,合约数量 {amount}(折算标的 {base_amount})," + f"实盘开单成功:风格 {trade_style};风险 {risk_display};基数 {margin_capital}U,杠杆 {leverage}x,名义仓位 {notional_value}U,仓位占比 {position_ratio}%,合约数量 {amount}(折算标的 {base_amount})," f"计划RR {planned_rr if planned_rr is not None else '-'};已在交易所挂条件止盈/止损委托(非仓位绑定型)", f"本交易日累计开仓:{opens_today_after}", ] diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index 0ea0ab1..902152c 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -521,7 +521,7 @@
来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} - 风险: {{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U + 风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %} {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} diff --git a/crypto_monitor_gate/app.py b/crypto_monitor_gate/app.py index bd8a2e9..d494878 100644 --- a/crypto_monitor_gate/app.py +++ b/crypto_monitor_gate/app.py @@ -87,11 +87,13 @@ from position_sizing_lib import ( OPEN_SOURCE_MANUAL, assert_open_source_allowed, compute_full_margin_sizing, + format_risk_display_text, full_margin_requires_flat_position, is_full_margin_mode, leverage_for_full_margin, load_position_sizing_mode, mode_label_zh, + risk_percent_for_storage, ) from key_monitor_full_margin_lib import ( monitor_type_disallowed_in_full_margin, @@ -7141,6 +7143,10 @@ def add_order(): breakeven_offset_pct = float(BREAKEVEN_OFFSET_PCT) breakeven_step_r = float(BREAKEVEN_STEP_R) if float(BREAKEVEN_STEP_R) > 0 else 1.0 risk_amount_final = calc_risk_amount_from_plan(direction, trigger_price, stop_loss, margin_capital, leverage) or risk_amount + risk_percent_db = risk_percent_for_storage(POSITION_SIZING_MODE, risk_percent) + risk_display = format_risk_display_text( + POSITION_SIZING_MODE, risk_percent, risk_amount_final, decimals=2 + ) if direction == "short": breakeven_raw = float(trigger_price) * (1 - breakeven_offset_pct / 100.0) else: @@ -7151,7 +7157,7 @@ def add_order(): "INSERT INTO order_monitors (symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, margin_capital, leverage, trade_style, risk_percent, risk_amount, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", ( symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit, - margin_capital, leverage, trade_style, risk_percent, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, + margin_capital, leverage, trade_style, risk_percent_db, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day, ORDER_MONITOR_TYPE_MANUAL, @@ -7269,7 +7275,7 @@ def add_order(): "🧾 订单基础信息", f"🔖 交易所订单 ID:{open_order_id}", f"📈 交易风格:{style_zh}", - f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 2)} U", + f"⚠️ 单笔风控风险:{risk_display}", "📊 仓位配置详情", f"账户基数:{account_base_display} USDT", f"合约杠杆:{leverage} 倍", @@ -7292,7 +7298,7 @@ def add_order(): send_wechat_msg("\n".join(wx_lines)) flash_lines = [ - f"实盘开单成功:风格 {trade_style};风险 {risk_percent}%≈{round(float(risk_amount_final), 2)}U;基数 {round(float(margin_capital), 2)}U,杠杆 {leverage}x,名义仓位 {format_wechat_scalar_2dp(notional_value)}U,仓位占比 {position_ratio}%,合约张数 {format_wechat_scalar_2dp(amount)}(折算标的 {base_amount})," + f"实盘开单成功:风格 {trade_style};风险 {risk_display};基数 {round(float(margin_capital), 2)}U,杠杆 {leverage}x,名义仓位 {format_wechat_scalar_2dp(notional_value)}U,仓位占比 {position_ratio}%,合约张数 {format_wechat_scalar_2dp(amount)}(折算标的 {base_amount})," f"计划RR {format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else '-'};已在交易所挂条件止盈/止损委托(非仓位绑定型)", f"本交易日累计开仓:{opens_today_after}", ] diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 0ea0ab1..902152c 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -521,7 +521,7 @@
来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} - 风险: {{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U + 风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %} {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} diff --git a/crypto_monitor_gate_bot/app.py b/crypto_monitor_gate_bot/app.py index 7e2795d..3700579 100644 --- a/crypto_monitor_gate_bot/app.py +++ b/crypto_monitor_gate_bot/app.py @@ -39,11 +39,13 @@ from ai_review_lib import build_journal_ai_chart_path, collect_images_for_ai_rev from position_sizing_lib import ( assert_open_source_allowed, compute_full_margin_sizing, + format_risk_display_text, full_margin_requires_flat_position, is_full_margin_mode, leverage_for_full_margin, load_position_sizing_mode, mode_label_zh, + risk_percent_for_storage, ) from key_monitor_full_margin_lib import ( monitor_type_disallowed_in_full_margin, @@ -6705,6 +6707,10 @@ def add_order(): breakeven_offset_pct = float(BREAKEVEN_OFFSET_PCT) breakeven_step_r = float(BREAKEVEN_STEP_R) if float(BREAKEVEN_STEP_R) > 0 else 1.0 risk_amount_final = calc_risk_amount_from_plan(direction, trigger_price, stop_loss, margin_capital, leverage) or risk_amount + risk_percent_db = risk_percent_for_storage(POSITION_SIZING_MODE, risk_percent) + risk_display = format_risk_display_text( + POSITION_SIZING_MODE, risk_percent, risk_amount_final, decimals=2 + ) if direction == "short": breakeven_price = round(float(trigger_price) * (1 - breakeven_offset_pct / 100.0), 8) else: @@ -6714,7 +6720,7 @@ def add_order(): "INSERT INTO order_monitors (symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, margin_capital, leverage, trade_style, risk_percent, risk_amount, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", ( symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit, - margin_capital, leverage, trade_style, risk_percent, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, + margin_capital, leverage, trade_style, risk_percent_db, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day ) @@ -6829,7 +6835,7 @@ def add_order(): "🧾 订单基础信息", f"🔖 交易所订单 ID:{open_order_id}", f"📈 交易风格:{style_zh}", - f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 4)} U", + f"⚠️ 单笔风控风险:{risk_display}", "📊 仓位配置详情", f"账户基数:{account_base_display} USDT", f"合约杠杆:{leverage} 倍", @@ -6852,7 +6858,7 @@ def add_order(): send_wechat_msg("\n".join(wx_lines)) flash_lines = [ - f"机器人开单成功:风格 {trade_style};风险 {risk_percent}%≈{risk_amount_final}U;基数 {margin_capital}U,杠杆 {leverage}x,名义仓位 {notional_value}U,仓位占比 {position_ratio}%,合约张数 {amount}(折算标的 {base_amount})," + f"机器人开单成功:风格 {trade_style};风险 {risk_display};基数 {margin_capital}U,杠杆 {leverage}x,名义仓位 {notional_value}U,仓位占比 {position_ratio}%,合约张数 {amount}(折算标的 {base_amount})," f"计划RR {planned_rr if planned_rr is not None else '-'};已在交易所挂条件止盈/止损委托(非仓位绑定型)", f"本交易日累计开仓:{opens_today_after}", ] diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index 83ec3c6..33c7496 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -468,7 +468,7 @@
来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} - 风险: {{ o.risk_percent or '-' }}%≈{{ money_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U + 风险: {% if position_sizing_mode == 'full_margin' %}{{ money_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ money_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %} {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 0b70e23..c699059 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -87,11 +87,13 @@ from position_sizing_lib import ( OPEN_SOURCE_MANUAL, assert_open_source_allowed, compute_full_margin_sizing, + format_risk_display_text, full_margin_requires_flat_position, is_full_margin_mode, leverage_for_full_margin, load_position_sizing_mode, mode_label_zh, + risk_percent_for_storage, ) from key_monitor_full_margin_lib import ( monitor_type_disallowed_in_full_margin, @@ -6757,6 +6759,10 @@ def add_order(): breakeven_offset_pct = float(BREAKEVEN_OFFSET_PCT) breakeven_step_r = float(BREAKEVEN_STEP_R) if float(BREAKEVEN_STEP_R) > 0 else 1.0 risk_amount_final = calc_risk_amount_from_plan(direction, trigger_price, stop_loss, margin_capital, leverage) or risk_amount + risk_percent_db = risk_percent_for_storage(POSITION_SIZING_MODE, risk_percent) + risk_display = format_risk_display_text( + POSITION_SIZING_MODE, risk_percent, risk_amount_final, decimals=FUNDS_DECIMALS + ) if direction == "short": breakeven_price = round(float(trigger_price) * (1 - breakeven_offset_pct / 100.0), 8) else: @@ -6766,7 +6772,7 @@ def add_order(): "INSERT INTO order_monitors (symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, margin_capital, leverage, trade_style, risk_percent, risk_amount, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", ( symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit, - margin_capital, leverage, trade_style, risk_percent, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, + margin_capital, leverage, trade_style, risk_percent_db, risk_amount_final, breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, 0, breakeven_price, breakeven_enabled, notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day, "下单监控", ) @@ -6880,7 +6886,7 @@ def add_order(): "🧾 订单基础信息", f"🔖 交易所订单 ID:{open_order_id}", f"📈 交易风格:{style_zh}", - f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 2)} U", + f"⚠️ 单笔风控风险:{risk_display}", "📊 仓位配置详情", f"账户基数:{account_base_display} USDT", f"合约杠杆:{leverage} 倍", @@ -6903,7 +6909,7 @@ def add_order(): send_wechat_msg("\n".join(wx_lines)) flash_lines = [ - f"实盘开单成功:风格 {trade_style};风险 {risk_percent}%≈{round(float(risk_amount_final), 2)}U;基数 {round(float(margin_capital), 2)}U,杠杆 {leverage}x,名义仓位 {format_wechat_scalar_2dp(notional_value)}U,仓位占比 {position_ratio}%,合约张数 {format_wechat_scalar_2dp(amount)}(折算标的 {base_amount})," + f"实盘开单成功:风格 {trade_style};风险 {risk_display};基数 {round(float(margin_capital), 2)}U,杠杆 {leverage}x,名义仓位 {format_wechat_scalar_2dp(notional_value)}U,仓位占比 {position_ratio}%,合约张数 {format_wechat_scalar_2dp(amount)}(折算标的 {base_amount})," f"计划RR {format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else '-'};已在交易所挂条件止盈/止损委托(非仓位绑定型)", f"本交易日累计开仓:{opens_today_after}", ] diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index 6958f7b..70976cf 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -530,7 +530,7 @@
来源: {{ o.monitor_type|default('下单监控', true) }}{% if o.key_signal_type %} · {{ o.key_signal_type }}{% endif %} 风格: {{ o.trade_style or 'trend' }} - 风险: {{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U + 风险: {% if position_sizing_mode == 'full_margin' %}{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% else %}{{ o.risk_percent or '-' }}%≈{{ funds_fmt(o.risk_amount) if o.risk_amount is not none else '-' }}U{% endif %} {% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %} diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index c1447cf..f8721de 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -322,6 +322,34 @@ }; } + function formatMonitorRiskMeta(mo, trendPlan) { + const m = mo || {}; + const t = trendPlan || {}; + const amt = + m.risk_amount != null && m.risk_amount !== "" + ? Number(m.risk_amount) + : t.risk_amount != null && t.risk_amount !== "" + ? Number(t.risk_amount) + : null; + const pctRaw = + m.risk_percent != null && m.risk_percent !== "" + ? m.risk_percent + : t.risk_percent != null && t.risk_percent !== "" + ? t.risk_percent + : null; + if (pctRaw == null || pctRaw === "") { + if (amt != null && Number.isFinite(amt)) { + return `风险: ${fmt(amt, 2)}U`; + } + return null; + } + const pct = esc(pctRaw); + if (amt != null && Number.isFinite(amt)) { + return `风险: ${pct}%≈${fmt(amt, 2)}U`; + } + return `风险: ${pct}%`; + } + function resolveTrendMarkPrice(pos, trendPlan, symbol, tickMap) { const fromPos = fmtMarkPrice(pos, tickMap); if (fromPos && fromPos !== "—") return fromPos; @@ -1983,13 +2011,8 @@ const meta = []; if (isTrend) { meta.push(monitorOrderSourceHtml(mo, trendPlan)); - const riskPct = - trendPlan && trendPlan.risk_percent != null && trendPlan.risk_percent !== "" - ? trendPlan.risk_percent - : mo.risk_percent; - if (riskPct != null && riskPct !== "") { - meta.push(`风险: ${esc(riskPct)}%`); - } + const riskLine = formatMonitorRiskMeta(mo, trendPlan); + if (riskLine) meta.push(riskLine); if (trendPlan && trendPlan.id) { const zone = trendPlan.add_upper_display || @@ -2006,9 +2029,8 @@ meta.push(monitorOrderSourceHtml(mo, trendPlan)); if (mo.trade_style) meta.push(`风格: ${esc(mo.trade_style)}`); else meta.push("风格: —"); - if (mo.risk_percent != null) { - meta.push(`风险: ${esc(mo.risk_percent)}%`); - } + const riskLine = formatMonitorRiskMeta(mo, trendPlan); + if (riskLine) meta.push(riskLine); const beOn = mo.breakeven_enabled === 1 || mo.breakeven_enabled === true; meta.push( `移动保本:${beOn ? "开" : "关"}` diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index e47dd4a..3957d13 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -250,6 +250,6 @@
- + diff --git a/position_sizing_lib.py b/position_sizing_lib.py index 854f759..06b19e0 100644 --- a/position_sizing_lib.py +++ b/position_sizing_lib.py @@ -53,6 +53,42 @@ def round_funds(value: float, decimals: int = 2) -> float: return round(float(value), int(decimals)) +def risk_percent_for_storage(mode: str, risk_percent: float) -> Optional[float]: + """全仓杠杆:库内不写风险百分比(仅 risk_amount U)。""" + if is_full_margin_mode(mode): + return None + return risk_percent + + +def format_risk_display_text( + mode: str, + risk_percent: Optional[float], + risk_amount: Optional[float], + *, + decimals: int = 2, +) -> str: + """持仓/通知「风险」文案:全仓仅 U;以损定仓为 %≈U。""" + amt: Optional[float] = None + if risk_amount is not None and risk_amount != "": + try: + amt = float(risk_amount) + except (TypeError, ValueError): + amt = None + if is_full_margin_mode(mode): + if amt is None: + return "—" + return f"{round_funds(amt, decimals)}U" + pct: Optional[float] = None + if risk_percent is not None and risk_percent != "": + try: + pct = float(risk_percent) + except (TypeError, ValueError): + pct = None + pct_txt = f"{pct:g}" if pct is not None else "—" + amt_txt = round_funds(amt, decimals) if amt is not None else "—" + return f"{pct_txt}%≈{amt_txt}U" + + def assert_open_source_allowed(mode: str, source: str) -> Tuple[bool, str]: if not is_full_margin_mode(mode): return True, "" diff --git a/tests/test_position_sizing_risk_display.py b/tests/test_position_sizing_risk_display.py new file mode 100644 index 0000000..6b28eda --- /dev/null +++ b/tests/test_position_sizing_risk_display.py @@ -0,0 +1,34 @@ +"""全仓 / 以损定仓 风险展示文案。""" +from __future__ import annotations + +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) + +from position_sizing_lib import ( # noqa: E402 + format_risk_display_text, + risk_percent_for_storage, +) + + +class TestPositionSizingRiskDisplay(unittest.TestCase): + def test_full_margin_shows_amount_only(self): + self.assertEqual( + format_risk_display_text("full_margin", 1.0, 2.58, decimals=2), + "2.58U", + ) + self.assertIsNone(risk_percent_for_storage("full_margin", 1.0)) + + def test_risk_mode_shows_percent_and_amount(self): + self.assertEqual( + format_risk_display_text("risk", 2.0, 10.5, decimals=2), + "2%≈10.5U", + ) + self.assertEqual(risk_percent_for_storage("risk", 2.0), 2.0) + + +if __name__ == "__main__": + unittest.main()