@@ -115,8 +115,27 @@ from manual_sltp_lib import (
resolve_entrust_sltp_prices ,
resolve_open_sltp_prices ,
)
from trigger_entry_key_monitor_lib import (
TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED ,
TRIGGER_ENTRY_CLOSE_EXPIRED ,
TRIGGER_ENTRY_CLOSE_FILLED ,
TRIGGER_ENTRY_CLOSE_TP_INVALIDATE ,
TRIGGER_ENTRY_MONITOR_TYPE ,
TRIGGER_ENTRY_VALIDITY_HOURS ,
check_trigger_entry_intent_limit ,
count_pending_trigger_entries ,
is_trigger_entry_expired ,
is_trigger_entry_key_monitor_type ,
trigger_entry_expires_at_text ,
trigger_entry_gate_preview ,
trigger_entry_invalidate_by_tp ,
trigger_entry_reached ,
validate_trigger_entry_geometry ,
validate_trigger_entry_rr ,
)
from position_sizing_lib import (
OPEN_SOURCE_KEY_AUTO ,
OPEN_SOURCE_KEY_TRIGGER ,
OPEN_SOURCE_MANUAL ,
OPEN_SOURCE_ROLL ,
OPEN_SOURCE_TREND ,
@@ -1032,6 +1051,7 @@ ENTRY_REASON_OPTIONS = (
" 关键位斐波0.618 " ,
" 关键位斐波0.786 " ,
" 关键位假突破 " ,
" 关键位触价开仓 " ,
) + STRATEGY_ENTRY_REASON_OPTIONS
STATS_SEGMENT_DEFS = (
@@ -1042,6 +1062,7 @@ STATS_SEGMENT_DEFS = (
( " key_fib618 " , " 关键位斐波0.618 " , { " segment " : " key_fib618 " } ) ,
( " key_fib786 " , " 关键位斐波0.786 " , { " segment " : " key_fib786 " } ) ,
( " key_false_breakout " , " 关键位假突破 " , { " segment " : " key_false_breakout " } ) ,
( " key_trigger " , " 关键位触价开仓 " , { " segment " : " key_trigger " } ) ,
)
# 复盘表单「其他」选项的 value(非入库值;自定义文本走 entry_reason_custom)
ENTRY_REASON_OTHER = " __OTHER__ "
@@ -1438,6 +1459,7 @@ def init_db():
" ALTER TABLE key_monitors ADD COLUMN manual_take_profit REAL " ,
" ALTER TABLE key_monitors ADD COLUMN breakeven_enabled INTEGER DEFAULT 0 " ,
" ALTER TABLE key_monitors ADD COLUMN last_rs_bar_ts INTEGER " ,
" ALTER TABLE key_monitors ADD COLUMN session_date TEXT " ,
) :
try :
c . execute ( ddl )
@@ -1633,6 +1655,8 @@ def _pnl_row_matches_segment(row, segment_key):
return kst == " 斐波回调0.786 "
if segment_key == " key_false_breakout " :
return kst == FALSE_BREAKOUT_MONITOR_TYPE
if segment_key == " key_trigger " :
return kst == TRIGGER_ENTRY_MONITOR_TYPE
return False
@@ -1650,6 +1674,7 @@ def _count_opens_for_segment(conn, start_td, end_td, segment_key):
" key_fib618 " : " 斐波回调0.618 " ,
" key_fib786 " : " 斐波回调0.786 " ,
" key_false_breakout " : FALSE_BREAKOUT_MONITOR_TYPE ,
" key_trigger " : TRIGGER_ENTRY_MONITOR_TYPE ,
}
kst = kst_map . get ( segment_key )
if kst :
@@ -5031,7 +5056,464 @@ def _finalize_fib_key_fill(conn, row):
_finalize_key_monitor_one_shot ( conn , row , succ , close_reason )
def check_fib_key_monitors ( ) :
def _trigger_entry_exists_for_symbol ( conn , symbol ) :
row = conn . execute (
" SELECT id FROM key_monitors WHERE symbol=? AND monitor_type=? " ,
( symbol , TRIGGER_ENTRY_MONITOR_TYPE ) ,
) . fetchone ( )
return row is not None
def _add_trigger_entry_key_monitor (
conn ,
symbol ,
direction_sel ,
entry ,
sl ,
tp ,
breakeven_enabled = 0 ,
time_close_enabled = 0 ,
time_close_hours = None ,
) :
if _trigger_entry_exists_for_symbol ( conn , symbol ) :
return False , f " { symbol } 已有触价开仓监控(同币仅允许一条) "
ex_sym = normalize_exchange_symbol ( symbol )
mark = get_symbol_mark_price ( symbol )
geom_err = validate_trigger_entry_geometry ( direction_sel , entry , sl , tp , mark_at_add = mark )
if geom_err :
return False , geom_err
rr_err = validate_trigger_entry_rr (
direction_sel , entry , sl , tp , KEY_AUTO_MIN_PLANNED_RR , calc_rr_ratio
)
if rr_err :
return False , rr_err
entry = float ( round_price_to_exchange ( ex_sym , entry ) or entry )
sl = float ( round_price_to_exchange ( ex_sym , sl ) or sl )
tp = float ( round_price_to_exchange ( ex_sym , tp ) or tp )
geom_err = validate_trigger_entry_geometry ( direction_sel , entry , sl , tp , mark_at_add = mark )
if geom_err :
return False , geom_err
rr_err = validate_trigger_entry_rr (
direction_sel , entry , sl , tp , KEY_AUTO_MIN_PLANNED_RR , calc_rr_ratio
)
if rr_err :
return False , rr_err
ok_live , reason_live = ensure_exchange_live_ready ( )
if not ok_live :
return False , reason_live
now = app_now ( )
trading_day = get_trading_day ( now )
opens_today = count_opens_for_trading_day ( conn , trading_day )
ok_intent , intent_msg = check_trigger_entry_intent_limit (
conn , trading_day , opens_today , DAILY_OPEN_HARD_LIMIT
)
if not ok_intent :
return False , intent_msg
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 :
return False , flat_msg
if count_pending_trigger_entries ( conn , trading_day ) > 0 :
return False , " 全仓杠杆模式下仅允许一条待触发触价监控 "
session_row = ensure_session ( conn , trading_day )
_ , trading_capital_live = get_exchange_capitals ( force = True )
live_capital = float ( trading_capital_live ) if trading_capital_live is not None else float ( session_row [ " current_capital " ] )
capital_base = resolve_capital_base_for_key_open ( conn , trading_day , live_capital )
available_usdt = get_available_trading_usdt ( )
if is_full_margin_mode ( POSITION_SIZING_MODE ) :
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 = 2 ,
)
if sizing_err :
return False , sizing_err
margin_capital = float ( sizing [ " margin_capital " ] )
amount_plan = None
else :
default_leverage = get_synced_leverage ( ex_sym , direction_sel ) or infer_leverage ( symbol )
leverage = int ( default_leverage ) if default_leverage else 5
if leverage < = 0 :
leverage = 5
risk_fraction = calc_risk_fraction ( direction_sel , entry , sl )
if risk_fraction is None :
return False , " 止损方向不合法(相对计划入场价) "
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 :
return False , " 以损定仓后保证金超过当前交易资金 "
if available_usdt is not None :
max_margin = round ( max ( available_usdt * FULL_MARGIN_BUFFER_RATIO , 0 ) , 4 )
if margin_capital > max_margin :
return (
False ,
f " 保证金不足:交易账户可用约 { round ( available_usdt , 2 ) } U,当前最多建议 { round ( max_margin , 2 ) } U " ,
)
try :
amount_plan , _ = prepare_order_amount ( ex_sym , margin_capital , leverage , entry )
except Exception as e :
return False , friendly_exchange_error ( e , available_usdt = available_usdt )
upper_px = round_price_to_exchange ( ex_sym , max ( entry , tp ) )
lower_px = round_price_to_exchange ( ex_sym , min ( entry , sl ) )
if upper_px is None or lower_px is None or float ( upper_px ) < = float ( lower_px ) :
upper_px , lower_px = float ( max ( entry , tp , sl ) ) , float ( min ( entry , tp , sl ) )
if upper_px < = lower_px :
lower_px = upper_px * 0.9999
be_flag = 1 if int ( breakeven_enabled or 0 ) != 0 else 0
tc_en , tc_h , _ = time_close_insert_values ( time_close_enabled , time_close_hours , None )
conn . execute (
" INSERT INTO key_monitors "
" (symbol, monitor_type, direction, upper, lower, "
" fib_entry_price, fib_stop_loss, fib_take_profit, "
" fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, "
" time_close_enabled, time_close_hours, session_date) "
" VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) " ,
(
symbol ,
TRIGGER_ENTRY_MONITOR_TYPE ,
direction_sel ,
float ( upper_px ) ,
float ( lower_px ) ,
entry ,
sl ,
tp ,
float ( amount_plan ) if amount_plan is not None else None ,
margin_capital ,
leverage ,
be_flag ,
tc_en ,
tc_h ,
trading_day ,
) ,
)
return True , None
def _market_open_for_trigger_entry (
conn ,
symbol ,
direction ,
exchange_symbol ,
entry_price ,
stop_loss ,
take_profit ,
breakeven_enabled = 0 ,
time_close_enabled = 0 ,
time_close_hours = None ,
) :
""" 触价触发后市价开仓,计仓规则与实盘下单/关键位 RR 门槛一致。 """
ok_src , src_msg = assert_open_source_allowed ( POSITION_SIZING_MODE , OPEN_SOURCE_KEY_TRIGGER )
if not ok_src :
return False , src_msg , None
now = app_now ( )
ok , reason = precheck_risk ( conn , symbol , direction )
if not ok :
return False , f " 风控拒绝下单: { reason } " , None
ok_live , reason_live = ensure_exchange_live_ready ( )
if not ok_live :
return False , reason_live , None
trading_day = get_trading_day ( now )
opens_today_before = count_opens_for_trading_day ( conn , trading_day )
session_row = ensure_session ( conn , trading_day )
_ , trading_capital_live = get_exchange_capitals ( force = True )
live_capital = float ( trading_capital_live ) if trading_capital_live is not None else float ( session_row [ " current_capital " ] )
capital_base = resolve_capital_base_for_key_open ( conn , trading_day , live_capital )
trade_style = ( DEFAULT_TRADE_STYLE or " trend " ) . strip ( ) . lower ( )
if trade_style not in ( " trend " , " swing " ) :
trade_style = " trend "
available_usdt = get_available_trading_usdt ( )
live_price = get_symbol_mark_price ( symbol ) or get_price ( symbol )
if live_price is None :
return False , " 获取标记价/实时价失败 " , None
try :
ensure_markets_loaded ( )
except Exception :
pass
lp_r = round_price_to_exchange ( exchange_symbol , live_price )
if lp_r is not None :
live_price = float ( lp_r )
entry_price = float ( entry_price )
sl_adj = round_price_to_exchange ( exchange_symbol , float ( stop_loss ) )
tp_adj = round_price_to_exchange ( exchange_symbol , float ( take_profit ) )
if sl_adj is not None :
stop_loss = float ( sl_adj )
if tp_adj is not None :
take_profit = float ( tp_adj )
planned_rr = calc_rr_ratio ( direction , entry_price , stop_loss , take_profit )
if planned_rr is None or planned_rr < = KEY_AUTO_MIN_PLANNED_RR :
rr_txt = f " { planned_rr : .4f } " if planned_rr is not None else " 无法计算 "
return False , f " 计划盈亏比 { rr_txt } :1 未达要求(> { KEY_AUTO_MIN_PLANNED_RR } :1) " , None
risk_percent = max ( 0.01 , float ( RISK_PERCENT ) )
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 :
return False , flat_msg , None
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 = 2 ,
)
if sizing_err :
return False , sizing_err , None
margin_capital = float ( sizing [ " margin_capital " ] )
notional_value = float ( sizing [ " notional_value " ] )
position_ratio = float ( sizing [ " position_ratio " ] )
risk_amount = margin_capital
else :
default_leverage = get_synced_leverage ( exchange_symbol , direction ) or infer_leverage ( symbol )
leverage = int ( default_leverage ) if default_leverage else 5
if leverage < = 0 :
leverage = 5
risk_fraction = calc_risk_fraction ( direction , entry_price , stop_loss )
if risk_fraction is None :
return False , " 止损方向不合法(相对计划入场价) " , None
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 :
return False , " 以损定仓后保证金超过当前交易资金 " , None
if available_usdt is not None :
max_margin = round ( max ( available_usdt * FULL_MARGIN_BUFFER_RATIO , 0 ) , 4 )
if margin_capital > max_margin :
return (
False ,
f " 保证金不足:交易账户可用约 { round ( available_usdt , 2 ) } U,当前最多建议 { round ( max_margin , 2 ) } U " ,
None ,
)
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 )
base_amount = round ( float ( amount ) * contract_size , 8 )
order_resp = place_exchange_order (
exchange_symbol , direction , amount , leverage ,
stop_loss = stop_loss , take_profit = take_profit ,
)
open_order_id = order_resp . get ( " id " , " " )
tpsl_attached = bool ( order_resp . get ( " tpsl_attached " ) )
trigger_price = resolve_order_entry_price ( order_resp , exchange_symbol , quote_price )
except Exception as e :
return False , friendly_exchange_error ( e , available_usdt = available_usdt ) , None
trigger_price = round_price_to_exchange ( exchange_symbol , trigger_price )
stop_loss = round_price_to_exchange ( exchange_symbol , stop_loss )
take_profit = round_price_to_exchange ( exchange_symbol , take_profit )
opened_at_bj = app_now_str ( )
opened_at_ms = _to_ms_with_fallback ( None , opened_at_bj )
planned_rr_fill = calc_rr_ratio ( direction , trigger_price , stop_loss , take_profit )
breakeven_rr_trigger = float ( BREAKEVEN_RR_TRIGGER )
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 )
if risk_amount_final is None :
risk_amount_final = risk_amount
else :
try :
risk_amount_final = round ( float ( risk_amount_final ) , 4 )
except ( TypeError , ValueError ) :
risk_amount_final = risk_amount
if direction == " short " :
breakeven_raw = float ( trigger_price ) * ( 1 - breakeven_offset_pct / 100.0 )
else :
breakeven_raw = float ( trigger_price ) * ( 1 + breakeven_offset_pct / 100.0 )
breakeven_price = round_price_to_exchange ( exchange_symbol , breakeven_raw )
be_enabled = 1 if int ( breakeven_enabled or 0 ) != 0 else 0
tc_en , tc_h , tc_at = time_close_insert_values ( time_close_enabled , time_close_hours , opened_at_ms )
risk_percent_db = risk_percent_for_storage ( POSITION_SIZING_MODE , risk_percent )
conn . execute (
" 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, key_signal_type, "
" time_close_enabled, time_close_hours, time_close_at_ms) "
" VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) " ,
(
symbol ,
exchange_symbol ,
direction ,
trigger_price ,
stop_loss ,
stop_loss ,
take_profit ,
margin_capital ,
leverage ,
trade_style ,
risk_percent_db ,
risk_amount_final ,
breakeven_rr_trigger ,
breakeven_offset_pct ,
breakeven_step_r ,
0 ,
breakeven_price ,
be_enabled ,
notional_value ,
position_ratio ,
base_amount ,
amount ,
open_order_id ,
opened_at_bj ,
opened_at_ms ,
trading_day ,
ORDER_MONITOR_TYPE_KEY_AUTO ,
stored_key_signal_type ( TRIGGER_ENTRY_MONITOR_TYPE ) ,
tc_en ,
tc_h ,
tc_at ,
) ,
)
new_order_id = int ( conn . execute ( " SELECT last_insert_rowid() " ) . fetchone ( ) [ 0 ] )
try_persist_exchange_margin_for_order ( conn , new_order_id , exchange_symbol , direction , order_leverage = leverage )
opens_today_after = count_opens_for_trading_day ( conn , trading_day )
return True , None , {
" new_order_id " : new_order_id ,
" open_order_id " : open_order_id ,
" trigger_price " : trigger_price ,
" planned_rr_fill " : planned_rr_fill ,
" risk_amount_final " : risk_amount_final ,
" margin_capital " : margin_capital ,
" leverage " : leverage ,
" amount " : amount ,
" tpsl_attached " : tpsl_attached ,
" opens_today_before " : opens_today_before ,
" opens_today_after " : opens_today_after ,
" trading_day " : trading_day ,
" stop_loss " : stop_loss ,
" take_profit " : take_profit ,
}
def _execute_trigger_entry_cross ( conn , row ) :
""" 标记价触达计划入场:先删监控行防重复触发,再市价开仓。 """
symbol = row [ " symbol " ]
direction = ( row [ " direction " ] or " long " ) . lower ( )
ex_sym = normalize_exchange_symbol ( symbol )
entry = float ( _sqlite_row_val ( row , " fib_entry_price " ) or 0 )
sl = float ( _sqlite_row_val ( row , " fib_stop_loss " ) or 0 )
tp = float ( _sqlite_row_val ( row , " fib_take_profit " ) or 0 )
be_en = breakeven_enabled_from_row ( row , 0 )
tc_en , tc_h , _ = time_close_settings_from_row ( row )
kid = int ( row [ " id " ] )
conn . execute ( " DELETE FROM key_monitors WHERE id=? " , ( kid , ) )
conn . commit ( )
ok , err , det = _market_open_for_trigger_entry (
conn ,
symbol ,
direction ,
ex_sym ,
entry ,
sl ,
tp ,
breakeven_enabled = be_en ,
time_close_enabled = tc_en ,
time_close_hours = tc_h ,
)
if ok and det :
rr_txt = format_wechat_scalar_2dp ( det . get ( " planned_rr_fill " ) ) if det . get ( " planned_rr_fill " ) is not None else " - "
msg = (
f " # ✅ { symbol } 触价开仓成交 \n "
f " **账户: { _wechat_account_label ( ) } ** \n "
f " - 来源: { ORDER_MONITOR_TYPE_KEY_AUTO } (程序触价 @ E) \n "
f " - 类型: { TRIGGER_ENTRY_MONITOR_TYPE } | { _wechat_direction_text ( direction ) } \n "
f " - 订单 ID: ** { det . get ( ' new_order_id ' ) } ** \n "
f " - 计划入场: { format_price_for_symbol ( symbol , entry ) } \n "
f " - 成交价: { format_price_for_symbol ( symbol , det . get ( ' trigger_price ' ) ) } \n "
f " - 止损: { format_wechat_scalar_2dp ( det . get ( ' stop_loss ' ) ) } |止盈: { format_price_for_symbol ( symbol , det . get ( ' take_profit ' ) ) } \n "
f " - 计划 RR: { rr_txt } :1 \n "
f " - { ' 已挂交易所 TP/SL ' if det . get ( ' tpsl_attached ' ) else ' TP/SL 未挂上 ' } \n "
)
send_wechat_msg ( msg )
insert_key_monitor_history ( conn , row , 0 , msg , TRIGGER_ENTRY_CLOSE_FILLED )
return True , None
fail_msg = err or " 触价触发后开仓失败 "
send_wechat_msg (
f " # ❌ { symbol } 触价开仓失败 \n "
f " **账户: { _wechat_account_label ( ) } ** \n "
f " - 计划入场: { format_price_for_symbol ( symbol , entry ) } \n "
f " - 原因: { fail_msg } \n "
)
insert_key_monitor_history ( conn , row , 0 , fail_msg , TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED )
return False , fail_msg
def check_trigger_entry_key_monitors ( ) :
conn = get_db ( )
rows = conn . execute ( " SELECT * FROM key_monitors WHERE monitor_type=? " , ( TRIGGER_ENTRY_MONITOR_TYPE , ) ) . fetchall ( )
now_dt = app_now ( )
for r in rows :
symbol = r [ " symbol " ]
direction = ( r [ " direction " ] or " long " ) . lower ( )
entry = float ( _sqlite_row_val ( r , " fib_entry_price " ) or 0 )
sl = float ( _sqlite_row_val ( r , " fib_stop_loss " ) or 0 )
tp = float ( _sqlite_row_val ( r , " fib_take_profit " ) or 0 )
if entry < = 0 or sl < = 0 or tp < = 0 :
_finalize_key_monitor_one_shot ( conn , r , " 触价计划价位无效 " , " fib_plan_invalid " )
continue
mark = get_symbol_mark_price ( symbol )
if mark is None :
continue
if is_trigger_entry_expired ( r [ " created_at " ] , now_dt , hours = TRIGGER_ENTRY_VALIDITY_HOURS ) :
exp_txt = trigger_entry_expires_at_text ( r [ " created_at " ] , hours = TRIGGER_ENTRY_VALIDITY_HOURS )
msg = (
f " # ⚠️ { symbol } 触价开仓已过期 \n "
f " **账户: { _wechat_account_label ( ) } ** \n "
f " - 类型: { TRIGGER_ENTRY_MONITOR_TYPE } | { _wechat_direction_text ( direction ) } \n "
f " - 有效期 { TRIGGER_ENTRY_VALIDITY_HOURS } h(应于 { exp_txt } 前触发) \n "
)
send_wechat_msg ( msg )
_finalize_key_monitor_one_shot ( conn , r , msg , TRIGGER_ENTRY_CLOSE_EXPIRED )
continue
if trigger_entry_invalidate_by_tp ( direction , mark , tp ) :
msg = (
f " # ⚠️ { symbol } 触价开仓失效 \n "
f " **账户: { _wechat_account_label ( ) } ** \n "
f " - 标记价 { format_price_for_symbol ( symbol , mark ) } 已触达止盈侧(未成交) \n "
)
send_wechat_msg ( msg )
_finalize_key_monitor_one_shot ( conn , r , msg , TRIGGER_ENTRY_CLOSE_TP_INVALIDATE )
continue
if trigger_entry_reached ( direction , mark , entry ) :
try :
_execute_trigger_entry_cross ( conn , r )
except Exception as e :
fail_msg = friendly_exchange_error ( e )
try :
insert_key_monitor_history ( conn , r , 0 , fail_msg , TRIGGER_ENTRY_CLOSE_EXCHANGE_FAILED )
except Exception :
pass
send_wechat_msg (
f " # ❌ { symbol } 触价开仓异常 \n **账户: { _wechat_account_label ( ) } ** \n - { fail_msg } \n "
)
conn . commit ( )
conn . close ( )
conn = get_db ( )
rows = conn . execute ( " SELECT * FROM key_monitors " ) . fetchall ( )
for r in rows :
@@ -5915,6 +6397,7 @@ def background_task():
conn . close ( )
force_close_before_reset ( )
check_fib_key_monitors ( )
check_trigger_entry_key_monitors ( )
_roll_cfg = app . extensions . get ( " strategy_roll_cfg " )
if _roll_cfg :
from strategy_roll_monitor_lib import check_roll_monitors
@@ -6170,6 +6653,7 @@ def render_main_page(page="trade"):
key_stop_outside_breakout_pct = KEY_STOP_OUTSIDE_BREAKOUT_PCT ,
key_trend_stop_outside_pct = KEY_TREND_STOP_OUTSIDE_PCT ,
false_breakout_validity_hours = FALSE_BREAKOUT_VALIDITY_HOURS ,
trigger_entry_validity_hours = TRIGGER_ENTRY_VALIDITY_HOURS ,
)
strategy_extra = { }
if page in ( " strategy " , " strategy_trend " , " strategy_roll " , " strategy_records " ) :
@@ -6328,7 +6812,7 @@ def api_account_snapshot():
def api_price_snapshot ( ) :
conn = get_db ( )
key_rows = conn . execute (
" SELECT id,symbol,monitor_type,direction,upper,lower,fib_entry_price,fib_limit_order_id,created_at FROM key_monitors "
" SELECT id,symbol,monitor_type,direction,upper,lower,fib_entry_price,fib_stop_loss,fib_take_profit,fib_ limit_order_id,created_at FROM key_monitors "
) . fetchall ( )
order_rows = conn . execute (
" SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage, "
@@ -6359,7 +6843,8 @@ def api_price_snapshot():
for r in key_rows :
is_fib = is_fib_key_monitor_type ( r [ " monitor_type " ] )
is_fb = is_false_breakout_key_monitor_type ( r [ " monitor_type " ] )
if is_fib or is_fb :
is_te = is_trigger_entry_key_monitor_type ( r [ " monitor_type " ] )
if is_fib or is_fb or is_te :
price = get_symbol_mark_price ( r [ " symbol " ] )
else :
price = prices . get ( r [ " symbol " ] )
@@ -6393,6 +6878,24 @@ def api_price_snapshot():
gate_summary = prev . get ( " summary " ) or " - "
gate_metrics = prev . get ( " metrics " ) or " "
fb_gate_ok = bool ( prev . get ( " gate_ok " ) )
elif is_te :
direction = ( r [ " direction " ] or " long " ) . lower ( )
entry = _sqlite_row_val ( r , " fib_entry_price " )
tp_v = _sqlite_row_val ( r , " fib_take_profit " )
entry_txt = format_price_for_symbol ( r [ " symbol " ] , entry ) if entry else " - "
tp_txt = format_price_for_symbol ( r [ " symbol " ] , tp_v ) if tp_v else " - "
tp_inv = trigger_entry_invalidate_by_tp ( direction , price , float ( tp_v ) ) if tp_v else False
prev = trigger_entry_gate_preview (
entry_display = entry_txt ,
take_profit_display = tp_txt ,
created_at = _sqlite_row_val ( r , " created_at " ) ,
now = app_now ( ) ,
tp_invalidated = tp_inv ,
hours = TRIGGER_ENTRY_VALIDITY_HOURS ,
)
gate_summary = prev . get ( " summary " ) or " - "
gate_metrics = prev . get ( " metrics " ) or " "
fib_gate_ok = bool ( prev . get ( " gate_ok " ) )
elif ( r [ " monitor_type " ] or " " ) . strip ( ) in KEY_MONITOR_RS_TYPES :
try :
prev = _key_rs_gate_preview ( r [ " symbol " ] , r [ " upper " ] , r [ " lower " ] )
@@ -6967,14 +7470,15 @@ def add_key():
+ tuple ( KEY_MONITOR_ALERT_ONLY_TYPES )
+ tuple ( FIB_KEY_MONITOR_TYPES )
+ ( FALSE_BREAKOUT_MONITOR_TYPE , )
+ ( TRIGGER_ENTRY_MONITOR_TYPE , )
)
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 并重启(须无持仓)。"
" 全仓杠杆模式下不可添加箱体/收敛突破、 斐波或假突破 监控; "
" 可使用「触价开仓」或 阻力/支撑(仅提醒),或切换 POSITION_SIZING_MODE=risk 并重启(须无持仓)。"
)
return redirect ( " /key_monitor " )
skip_volume_rank = is_false_breakout_key_monitor_type ( mt )
@@ -7003,6 +7507,45 @@ def add_key():
except Exception :
pass
be_flag = parse_breakeven_enabled_form ( d . get ( " breakeven_enabled " ) )
tc_en = parse_time_close_enabled_form ( d . get ( " time_close_enabled " ) )
tc_h = parse_time_close_hours_form ( d . get ( " time_close_hours " ) ) if tc_en else None
if tc_en and not tc_h :
tc_en = 0
if is_trigger_entry_key_monitor_type ( mt ) :
if direction_sel not in ( " long " , " short " ) :
conn . close ( )
conn = None
flash ( " 触价开仓请选择做多或做空 " )
return redirect ( " /key_monitor " )
try :
entry_px = float ( d . get ( " trigger_entry " ) or 0 )
sl_px = float ( d . get ( " trigger_sl " ) or 0 )
tp_px = float ( d . get ( " trigger_tp " ) or 0 )
except ( TypeError , ValueError ) :
entry_px = sl_px = tp_px = 0
if entry_px < = 0 or sl_px < = 0 or tp_px < = 0 :
conn . close ( )
conn = None
flash ( " 触价开仓须填写有效的入场价、止损价、止盈价 " )
return redirect ( " /key_monitor " )
ok_te , err_te = _add_trigger_entry_key_monitor (
conn , symbol , direction_sel , entry_px , sl_px , tp_px , breakeven_enabled = be_flag ,
time_close_enabled = tc_en , time_close_hours = tc_h ,
)
conn . commit ( )
conn . close ( )
conn = None
if not ok_te :
flash ( err_te or " 触价开仓监控添加失败 " )
return redirect ( " /key_monitor " )
flash (
f " 触价开仓已添加( { symbol } 日成交量排名 { rank } / { total } ) "
f " |有效期 { TRIGGER_ENTRY_VALIDITY_HOURS } h "
f " |标记价触达入场价后下一轮询市价开仓 "
f " |移动保本: { ' 开 ' if be_flag else ' 关 ' } "
+ ( f " | { time_close_label ( tc_h ) } " if tc_en else " " )
)
return redirect ( " /key_monitor " )
if is_false_breakout_key_monitor_type ( mt ) :
fb_sym = normalize_false_breakout_symbol ( symbol )
if not fb_sym :