feat: 账户方向与币种白名单 env 开关(三所)
Per-instance TRADE_DIRECTION / TRADE_SYMBOL_WHITELIST restricts UI and API for manual orders, key monitors, and strategies; includes sync script for deployment profiles. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -52,6 +52,13 @@ UPLOAD_DIR=static/images
|
||||
# BINANCE_FUNDING_INCLUDE_SPOT=false
|
||||
# 计仓:risk=以损定仓(默认);full_margin=合约可用×FULL_MARGIN_BUFFER_RATIO 全仓杠杆(须无仓后重启)
|
||||
POSITION_SIZING_MODE=risk
|
||||
# 方向限制(默认 false=双向均可;true 时按 TRADE_DIRECTION 限制,修改后须重启)
|
||||
# TRADE_DIRECTION=long_only | short_only | both(或 多/空/双向)
|
||||
TRADE_DIRECTION_RESTRICT_ENABLED=false
|
||||
TRADE_DIRECTION=both
|
||||
# 币种白名单(默认 false=全币种可手输;true 时关键位/下单/策略仅下拉选择)
|
||||
TRADE_SYMBOL_RESTRICT_ENABLED=false
|
||||
TRADE_SYMBOL_WHITELIST=BTC,ETH
|
||||
# 每天起始基数(U)
|
||||
DAILY_START_CAPITAL=30
|
||||
# 日内回撤后基数(U)
|
||||
|
||||
@@ -159,6 +159,14 @@ from lib.trade.position_sizing_lib import (
|
||||
mode_label_zh,
|
||||
risk_percent_for_storage,
|
||||
)
|
||||
from lib.trade.trade_policy_lib import load_trade_policy
|
||||
from lib.trade.trade_policy_app_lib import (
|
||||
check_direction_policy,
|
||||
check_open_policy,
|
||||
check_symbol_policy,
|
||||
default_symbol_for_policy,
|
||||
trade_policy_template_context,
|
||||
)
|
||||
from lib.key_monitor.key_monitor_full_margin_lib import (
|
||||
monitor_type_disallowed_in_full_margin,
|
||||
purge_disallowed_key_monitors,
|
||||
@@ -342,6 +350,7 @@ FORCE_CLOSE_BJ_HOUR = int(os.getenv("FORCE_CLOSE_BJ_HOUR", "0"))
|
||||
AUTO_TRANSFER_BJ_HOUR = int(os.getenv("AUTO_TRANSFER_BJ_HOUR", "8"))
|
||||
# 计仓模式:risk=以损定仓(默认);full_margin=合约可用保证金×比例全仓杠杆(仅 env 切换,须无仓)
|
||||
POSITION_SIZING_MODE = load_position_sizing_mode()
|
||||
TRADE_POLICY = load_trade_policy()
|
||||
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"))
|
||||
@@ -2035,6 +2044,12 @@ def normalize_symbol_input(symbol):
|
||||
return f"{sym}/USDT"
|
||||
|
||||
|
||||
def validate_trade_policy_open(symbol, direction):
|
||||
return check_open_policy(
|
||||
TRADE_POLICY, symbol, direction, normalize_symbol_input
|
||||
)
|
||||
|
||||
|
||||
def normalize_kline_limit(limit_raw, default=200):
|
||||
try:
|
||||
n = int(limit_raw)
|
||||
@@ -7277,6 +7292,7 @@ def render_main_page(page="trade", embed_mode=None):
|
||||
risk_percent=RISK_PERCENT,
|
||||
position_sizing_mode=POSITION_SIZING_MODE,
|
||||
position_sizing_mode_label=mode_label_zh(POSITION_SIZING_MODE),
|
||||
trade_policy=trade_policy_template_context(TRADE_POLICY),
|
||||
open_position_button_label=(
|
||||
"开仓(全仓杠杆)" if is_full_margin_mode(POSITION_SIZING_MODE) else "开仓(以损定仓)"
|
||||
),
|
||||
@@ -7996,7 +8012,10 @@ def key_focus():
|
||||
selected_key = next((k for k in key_list if (k.get("symbol") or "").upper() == symbol_query), None)
|
||||
if selected_key is None and key_list:
|
||||
selected_key = key_list[0]
|
||||
default_symbol = symbol_query or ((selected_key or {}).get("symbol")) or "BTC/USDT"
|
||||
default_symbol = default_symbol_for_policy(
|
||||
TRADE_POLICY,
|
||||
symbol_query or ((selected_key or {}).get("symbol")) or "BTC/USDT",
|
||||
)
|
||||
return render_template(
|
||||
"key_focus_v2.html",
|
||||
key_list=key_list,
|
||||
@@ -8006,6 +8025,7 @@ def key_focus():
|
||||
default_kline_limit=200,
|
||||
price_refresh_seconds=PRICE_REFRESH_SECONDS,
|
||||
exchange_display=EXCHANGE_DISPLAY_NAME,
|
||||
trade_policy=trade_policy_template_context(TRADE_POLICY),
|
||||
)
|
||||
|
||||
|
||||
@@ -8116,6 +8136,12 @@ def add_key():
|
||||
if not symbol:
|
||||
flash("symbol 不能为空")
|
||||
return redirect("/key_monitor")
|
||||
ok_sym, sym_msg = check_symbol_policy(
|
||||
TRADE_POLICY, symbol, normalize_symbol_input
|
||||
)
|
||||
if not ok_sym:
|
||||
flash(sym_msg)
|
||||
return redirect("/key_monitor")
|
||||
mt = (d.get("type") or "").strip()
|
||||
direction_sel = (d.get("direction") or "").strip().lower()
|
||||
dup_msg = check_duplicate_submit(
|
||||
@@ -8130,6 +8156,10 @@ def add_key():
|
||||
elif direction_sel not in ("long", "short"):
|
||||
flash("箱体/收敛突破请选择做多或做空")
|
||||
return redirect("/key_monitor")
|
||||
ok_dir, dir_msg = check_direction_policy(TRADE_POLICY, direction_sel)
|
||||
if not ok_dir:
|
||||
flash(dir_msg)
|
||||
return redirect("/key_monitor")
|
||||
allowed_types = (
|
||||
tuple(KEY_MONITOR_AUTO_TYPES)
|
||||
+ tuple(KEY_MONITOR_ALERT_ONLY_TYPES)
|
||||
@@ -8375,6 +8405,11 @@ def add_order():
|
||||
conn.close()
|
||||
flash("symbol 不能为空")
|
||||
return redirect("/")
|
||||
ok_pol, pol_msg = validate_trade_policy_open(symbol, direction)
|
||||
if not ok_pol:
|
||||
conn.close()
|
||||
flash(f"账户限制:{pol_msg}")
|
||||
return redirect("/trade")
|
||||
dup_msg = check_duplicate_submit(session, submit_scope_add_order(symbol, direction))
|
||||
if dup_msg:
|
||||
conn.close()
|
||||
@@ -9703,6 +9738,7 @@ def _hub_meta_bundle():
|
||||
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||
"btc_leverage": BTC_LEVERAGE,
|
||||
"alt_leverage": ALT_LEVERAGE,
|
||||
"trade_policy": trade_policy_template_context(TRADE_POLICY),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
.stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px}
|
||||
.stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=48">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=49">
|
||||
|
||||
</head>
|
||||
<body
|
||||
@@ -278,6 +278,9 @@
|
||||
<h1>加密货币|交易监控 + AI复盘一体化</h1>
|
||||
<div class="header-row">
|
||||
<div class="exchange-tag">{{ exchange_display }}</div>
|
||||
{% if trade_policy.badge_text %}
|
||||
<span class="trade-policy-badge" title="账户交易限制(.env)">{{ trade_policy.badge_text }}</span>
|
||||
{% endif %}
|
||||
<span class="risk-status-badge risk-status-{{ risk_status.status|default('normal') }}" id="account-risk-badge" role="status" title="{{ risk_status.reason|default('', true) }}" data-status-label="{{ risk_status.status_label|default('正常') }}"{% if risk_status.freeze_until_ms %} data-freeze-until-ms="{{ risk_status.freeze_until_ms }}"{% endif %}>{{ risk_status.status_label|default('正常') }}</span>
|
||||
<div class="theme-toggle instance-theme-toggle" role="group" aria-label="界面主题">
|
||||
<button type="button" class="theme-toggle-btn is-active" data-theme-value="dark" aria-pressed="true" title="暗色主题">
|
||||
@@ -374,10 +377,9 @@
|
||||
<button type="submit">手动划转</button>
|
||||
</form>
|
||||
<form id="add-order-form" action="/add_order" method="post" class="form-row" data-risk-percent="{{ risk_percent }}">
|
||||
<input id="order-symbol" name="symbol" placeholder="BTC 或 BTC/USDT" required>
|
||||
<select id="order-direction" name="direction" required>
|
||||
<option value="">方向</option><option value="long">做多</option><option value="short">做空</option>
|
||||
</select>
|
||||
{% from 'trade_policy_fields.html' import trade_policy_symbol, trade_policy_direction %}
|
||||
{{ trade_policy_symbol('symbol', 'order-symbol') }}
|
||||
{{ trade_policy_direction('direction', 'order-direction') }}
|
||||
<select id="sltp-mode" name="sltp_mode">
|
||||
<option value="fixed_rr" selected>止盈止损:固定盈亏比</option>
|
||||
<option value="price">止盈止损:价格模式</option>
|
||||
|
||||
Reference in New Issue
Block a user