feat: add per-account daily open hard limit across all exchanges
Enforce optional DAILY_OPEN_HARD_LIMIT in precheck_risk and can_trade, keep AI alerts at DAILY_OPEN_ALERT_THRESHOLD, and document env setup for all four instances. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -299,7 +299,18 @@ ORDER_CHART_ENABLED = os.getenv("ORDER_CHART_ENABLED", "true").lower() == "true"
|
||||
ORDER_CHART_TFS = [x.strip() for x in (os.getenv("ORDER_CHART_TFS", "4h,1h,15m,5m") or "").split(",") if x.strip()]
|
||||
ORDER_CHART_LIMIT = int(os.getenv("ORDER_CHART_LIMIT", "100"))
|
||||
ORDER_CHART_DIR = resolve_path(os.getenv("ORDER_CHART_DIR", "static/images/order_charts"))
|
||||
DAILY_OPEN_ALERT_THRESHOLD = int(os.getenv("DAILY_OPEN_ALERT_THRESHOLD", "5"))
|
||||
from daily_open_limit_lib import (
|
||||
build_daily_open_alert_prompt,
|
||||
can_trade_new_open,
|
||||
check_daily_open_hard_limit,
|
||||
count_opens_for_trading_day,
|
||||
format_daily_open_counter_line,
|
||||
format_daily_open_summary_short,
|
||||
load_daily_open_limits_from_env,
|
||||
should_send_daily_open_alert,
|
||||
)
|
||||
|
||||
DAILY_OPEN_ALERT_THRESHOLD, DAILY_OPEN_HARD_LIMIT = load_daily_open_limits_from_env()
|
||||
RISK_PERCENT = float(os.getenv("RISK_PERCENT", "2"))
|
||||
BREAKEVEN_RR_TRIGGER = float(os.getenv("BREAKEVEN_RR_TRIGGER", "1.0"))
|
||||
BREAKEVEN_OFFSET_PCT = float(os.getenv("BREAKEVEN_OFFSET_PCT", "0.02"))
|
||||
@@ -3053,6 +3064,11 @@ def precheck_risk(conn, symbol, direction):
|
||||
active_count = get_active_position_count(conn)
|
||||
if active_count >= MAX_ACTIVE_POSITIONS:
|
||||
return False, f"已达最大持仓数({active_count}/{MAX_ACTIVE_POSITIONS})"
|
||||
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
||||
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
||||
)
|
||||
if not ok_daily:
|
||||
return False, daily_reason
|
||||
if direction not in ("long", "short"):
|
||||
return False, "方向必须为 long 或 short"
|
||||
if symbol.upper().startswith("BTC") or symbol.upper().startswith("ETH"):
|
||||
@@ -5374,7 +5390,7 @@ def check_key_monitors():
|
||||
f"- 名义 {format_wechat_scalar_2dp(det.get('notional_value'))}U|张数 {format_wechat_scalar_2dp(det.get('amount'))}|折算标的 {det.get('base_amount')}",
|
||||
f"- **{tpsl_txt}**",
|
||||
f"- 保本触发:{det.get('breakeven_rr_trigger')}R→{format_price_for_symbol(sym, det.get('breakeven_price'))}",
|
||||
f"- 当日开仓次数:**{det.get('opens_today_after')}** / {DAILY_OPEN_ALERT_THRESHOLD}(提醒阈值)",
|
||||
f"- {format_daily_open_summary_short(det.get('opens_today_after'), DAILY_OPEN_ALERT_THRESHOLD, DAILY_OPEN_HARD_LIMIT)}",
|
||||
]
|
||||
succ_msg_lines.extend(["---", "### 硬条件"] + [f"- {x}" for x in hard_lines])
|
||||
if risk_tip:
|
||||
@@ -5383,11 +5399,19 @@ def check_key_monitors():
|
||||
send_wechat_msg(succ_msg)
|
||||
_finalize_key_monitor_one_shot(conn, r, succ_msg, "auto_opened")
|
||||
|
||||
if det.get("opens_today_before", 0) < DAILY_OPEN_ALERT_THRESHOLD <= det.get("opens_today_after", 0):
|
||||
if should_send_daily_open_alert(
|
||||
det.get("opens_today_before", 0),
|
||||
det.get("opens_today_after", 0),
|
||||
DAILY_OPEN_ALERT_THRESHOLD,
|
||||
):
|
||||
advice = ai_short_advice(
|
||||
f"用户在北京时间交易日 {det['trading_day']} 已累计开仓 {det['opens_today_after']} 次(阈值 {DAILY_OPEN_ALERT_THRESHOLD})。"
|
||||
f"最新一笔来源为关键位自动单:{sym} {direction},杠杆{det['leverage']}x。"
|
||||
f"用户自述“上头了”。请给克制提醒。"
|
||||
build_daily_open_alert_prompt(
|
||||
det["trading_day"],
|
||||
det.get("opens_today_after", 0),
|
||||
DAILY_OPEN_ALERT_THRESHOLD,
|
||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||
detail_line=f"最新一笔来源为关键位自动单:{sym} {direction},杠杆{det['leverage']}x。",
|
||||
)
|
||||
)
|
||||
if advice:
|
||||
send_wechat_msg(f"【AI提醒】今日开仓次数已达 {det['opens_today_after']}\n{advice[:800]}")
|
||||
@@ -6092,7 +6116,14 @@ def render_main_page(page="trade"):
|
||||
)
|
||||
rate = round(win/total*100,2) if total else 0
|
||||
active_count = len(order_list)
|
||||
can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS
|
||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||
can_trade = can_trade_new_open(
|
||||
time_allows=trading_day_reset_allows_new_open(now),
|
||||
active_count=active_count,
|
||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||
opens_today=opens_today,
|
||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||
)
|
||||
key_gate_rule_text = (
|
||||
f"【箱体/收敛】{KLINE_TIMEFRAME} 两根闭合K|突破越过关键位 > {KEY_BREAKOUT_AMP_MIN_PCT}%|"
|
||||
f"确认K收于箱外|量能>前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}|"
|
||||
@@ -6141,6 +6172,9 @@ def render_main_page(page="trade"):
|
||||
price_refresh_seconds=PRICE_REFRESH_SECONDS,
|
||||
active_count=active_count,
|
||||
can_trade=can_trade,
|
||||
opens_today=opens_today,
|
||||
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||
daily_open_alert_threshold=DAILY_OPEN_ALERT_THRESHOLD,
|
||||
focus_key_id=(key_list[0]["id"] if key_list else None),
|
||||
focus_order_id=(order_list[0]["id"] if order_list else None),
|
||||
data_export_version=3,
|
||||
@@ -6223,8 +6257,15 @@ def api_account_snapshot():
|
||||
current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
|
||||
recommended_capital = get_recommended_capital(current_capital)
|
||||
active_count = get_active_position_count(conn)
|
||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||
conn.close()
|
||||
can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS
|
||||
can_trade = can_trade_new_open(
|
||||
time_allows=trading_day_reset_allows_new_open(now),
|
||||
active_count=active_count,
|
||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||
opens_today=opens_today,
|
||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||
)
|
||||
available_trading_usdt = get_available_trading_usdt()
|
||||
return jsonify({
|
||||
"funding_usdt": funding_usdt,
|
||||
@@ -6234,6 +6275,9 @@ def api_account_snapshot():
|
||||
"active_count": active_count,
|
||||
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||
"can_trade": can_trade,
|
||||
"opens_today": opens_today,
|
||||
"daily_open_hard_limit": DAILY_OPEN_HARD_LIMIT,
|
||||
"daily_open_alert_threshold": DAILY_OPEN_ALERT_THRESHOLD,
|
||||
"manual_min_planned_rr": MANUAL_MIN_PLANNED_RR,
|
||||
"trading_day": trading_day
|
||||
})
|
||||
@@ -7345,7 +7389,9 @@ def add_order():
|
||||
f"移动保本位:{breakeven_rr_trigger}R → {be_wx}",
|
||||
"📌 状态统计",
|
||||
f"✅ 条件委托:{order_state_text}",
|
||||
f"📅 当日开仓次数:{opens_today_after} / {DAILY_OPEN_ALERT_THRESHOLD} 次(风控阈值提醒)",
|
||||
format_daily_open_counter_line(
|
||||
opens_today_after, DAILY_OPEN_ALERT_THRESHOLD, DAILY_OPEN_HARD_LIMIT
|
||||
),
|
||||
]
|
||||
if chart_url:
|
||||
wx_lines.append(f"多周期K线图:{chart_url}")
|
||||
@@ -7354,17 +7400,25 @@ def add_order():
|
||||
flash_lines = [
|
||||
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}",
|
||||
format_daily_open_summary_short(
|
||||
opens_today_after, DAILY_OPEN_ALERT_THRESHOLD, DAILY_OPEN_HARD_LIMIT
|
||||
),
|
||||
]
|
||||
if chart_url:
|
||||
flash_lines.append(f"已生成多周期K线图:{chart_url}")
|
||||
flash(" ".join(flash_lines))
|
||||
|
||||
if opens_today_before < DAILY_OPEN_ALERT_THRESHOLD <= opens_today_after:
|
||||
if should_send_daily_open_alert(
|
||||
opens_today_before, opens_today_after, DAILY_OPEN_ALERT_THRESHOLD
|
||||
):
|
||||
advice = ai_short_advice(
|
||||
f"用户在北京时间交易日 {trading_day} 已累计开仓 {opens_today_after} 次(阈值 {DAILY_OPEN_ALERT_THRESHOLD})。"
|
||||
f"最新一笔:{symbol} {direction},杠杆{leverage}x,基数{margin_capital}U。"
|
||||
f"用户自述“上头了”。请给克制提醒。"
|
||||
build_daily_open_alert_prompt(
|
||||
trading_day,
|
||||
opens_today_after,
|
||||
DAILY_OPEN_ALERT_THRESHOLD,
|
||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||
detail_line=f"最新一笔:{symbol} {direction},杠杆{leverage}x,基数{margin_capital}U。",
|
||||
)
|
||||
)
|
||||
if advice:
|
||||
send_wechat_msg(f"【AI提醒】今日开仓次数已达 {opens_today_after}\n{advice[:800]}")
|
||||
|
||||
Reference in New Issue
Block a user