feat: add timed position close (1h/2h/4h) for key levels and live orders
Program monitors open positions and market-closes at deadline; UI shows label and countdown on instance and hub boards. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -99,6 +99,17 @@ from key_sl_tp_lib import (
|
|||||||
sl_tp_mode_label,
|
sl_tp_mode_label,
|
||||||
sl_tp_plan_summary_text,
|
sl_tp_plan_summary_text,
|
||||||
)
|
)
|
||||||
|
from time_close_lib import (
|
||||||
|
TIME_CLOSE_RESULT,
|
||||||
|
apply_time_close_to_payload,
|
||||||
|
ensure_time_close_schema,
|
||||||
|
parse_time_close_enabled_form,
|
||||||
|
parse_time_close_hours_form,
|
||||||
|
should_trigger_time_close,
|
||||||
|
time_close_insert_values,
|
||||||
|
time_close_label,
|
||||||
|
time_close_settings_from_row,
|
||||||
|
)
|
||||||
from manual_sltp_lib import (
|
from manual_sltp_lib import (
|
||||||
normalize_open_sltp_mode,
|
normalize_open_sltp_mode,
|
||||||
resolve_entrust_sltp_prices,
|
resolve_entrust_sltp_prices,
|
||||||
@@ -1431,6 +1442,7 @@ def init_db():
|
|||||||
c.execute(ddl)
|
c.execute(ddl)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
ensure_time_close_schema(c)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
||||||
@@ -4534,6 +4546,8 @@ def _market_open_for_key_monitor(
|
|||||||
take_profit,
|
take_profit,
|
||||||
key_signal_type=None,
|
key_signal_type=None,
|
||||||
breakeven_enabled=0,
|
breakeven_enabled=0,
|
||||||
|
time_close_enabled=0,
|
||||||
|
time_close_hours=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入(Binance U 本位)。
|
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入(Binance U 本位)。
|
||||||
@@ -5073,7 +5087,10 @@ def check_fib_key_monitors():
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0):
|
def _add_fib_key_monitor(
|
||||||
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
|
):
|
||||||
if _fib_key_exists_for_symbol(conn, symbol):
|
if _fib_key_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
||||||
ratio = fib_ratio_from_type(mt)
|
ratio = fib_ratio_from_type(mt)
|
||||||
@@ -5134,15 +5151,16 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, br
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
||||||
be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol, monitor_type, direction, upper, lower, "
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, time_close_enabled, time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, mt, direction_sel, upper_px, lower_px,
|
symbol, mt, direction_sel, upper_px, lower_px,
|
||||||
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return True, None
|
return True, None
|
||||||
@@ -5158,6 +5176,7 @@ def _false_breakout_exists_for_symbol(conn, symbol):
|
|||||||
|
|
||||||
def _add_false_breakout_key_monitor(
|
def _add_false_breakout_key_monitor(
|
||||||
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
):
|
):
|
||||||
if _false_breakout_exists_for_symbol(conn, symbol):
|
if _false_breakout_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
||||||
@@ -5214,15 +5233,16 @@ def _add_false_breakout_key_monitor(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
||||||
be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol, monitor_type, direction, upper, lower, "
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, time_close_enabled, time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
||||||
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return True, None
|
return True, None
|
||||||
@@ -5333,6 +5353,7 @@ def check_key_monitors():
|
|||||||
|
|
||||||
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
||||||
be_on = breakeven_enabled_from_row(r, 0)
|
be_on = breakeven_enabled_from_row(r, 0)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(r)
|
||||||
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
||||||
conn,
|
conn,
|
||||||
sym,
|
sym,
|
||||||
@@ -5342,6 +5363,8 @@ def check_key_monitors():
|
|||||||
tp_raw,
|
tp_raw,
|
||||||
key_signal_type=key_sig,
|
key_signal_type=key_sig,
|
||||||
breakeven_enabled=1 if be_on else 0,
|
breakeven_enabled=1 if be_on else 0,
|
||||||
|
time_close_enabled=tc_en,
|
||||||
|
time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
planned_rr_txt = (
|
planned_rr_txt = (
|
||||||
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
||||||
@@ -5515,12 +5538,14 @@ def check_order_monitors():
|
|||||||
send_wechat_msg(be_msg)
|
send_wechat_msg(be_msg)
|
||||||
|
|
||||||
res = None
|
res = None
|
||||||
|
if should_trigger_time_close(r):
|
||||||
|
res = TIME_CLOSE_RESULT
|
||||||
# 做多
|
# 做多
|
||||||
if direction == "long":
|
if not res and direction == "long":
|
||||||
if p >= take_profit: res = "止盈"
|
if p >= take_profit: res = "止盈"
|
||||||
elif p <= stop_loss: res = "止损"
|
elif p <= stop_loss: res = "止损"
|
||||||
# 做空
|
# 做空
|
||||||
elif direction == "short":
|
elif not res and direction == "short":
|
||||||
if p <= take_profit: res = "止盈"
|
if p <= take_profit: res = "止盈"
|
||||||
elif p >= stop_loss: res = "止损"
|
elif p >= stop_loss: res = "止损"
|
||||||
|
|
||||||
@@ -6304,7 +6329,8 @@ def api_price_snapshot():
|
|||||||
"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_limit_order_id,created_at FROM key_monitors"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
order_rows = conn.execute(
|
order_rows = conn.execute(
|
||||||
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status='active'"
|
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage,"
|
||||||
|
"time_close_enabled,time_close_hours,time_close_at_ms,opened_at_ms FROM order_monitors WHERE status='active'"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
symbol_set = set()
|
symbol_set = set()
|
||||||
@@ -6488,6 +6514,7 @@ def api_price_snapshot():
|
|||||||
format_price_fn=format_price_for_symbol,
|
format_price_fn=format_price_for_symbol,
|
||||||
symbol=r["symbol"],
|
symbol=r["symbol"],
|
||||||
)
|
)
|
||||||
|
apply_time_close_to_payload(payload, r)
|
||||||
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
||||||
r["stop_loss"], r["take_profit"], exchange_tpsl
|
r["stop_loss"], r["take_profit"], exchange_tpsl
|
||||||
)
|
)
|
||||||
@@ -7284,14 +7311,20 @@ def add_order():
|
|||||||
else:
|
else:
|
||||||
breakeven_price = round(float(trigger_price) * (1 + breakeven_offset_pct / 100.0), 8)
|
breakeven_price = round(float(trigger_price) * (1 + breakeven_offset_pct / 100.0), 8)
|
||||||
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
||||||
|
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
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
conn.execute(
|
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) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"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, time_close_enabled, time_close_hours, time_close_at_ms) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit,
|
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,
|
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,
|
breakeven_enabled,
|
||||||
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
||||||
ORDER_MONITOR_TYPE_MANUAL,
|
ORDER_MONITOR_TYPE_MANUAL,
|
||||||
|
tc_en, tc_h, tc_at,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -376,6 +376,14 @@
|
|||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
||||||
</label>
|
</label>
|
||||||
|
<label id="order-time-close-wrap" class="order-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
|
<input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb"> 时间平仓
|
||||||
|
<select name="time_close_hours" id="order-time-close-hours" disabled>
|
||||||
|
<option value="1">1h</option>
|
||||||
|
<option value="2">2h</option>
|
||||||
|
<option value="4" selected>4h</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
||||||
</label>
|
</label>
|
||||||
@@ -417,6 +425,12 @@
|
|||||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||||
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="pos-meta-item pos-meta-on pos-time-close-meta" id="order-time-close-wrap-{{ o.id }}"
|
||||||
|
{% if not o.time_close_enabled %}style="display:none"{% endif %}
|
||||||
|
data-close-at-ms="{{ o.time_close_at_ms or '' }}">
|
||||||
|
<span class="pos-time-close-label">时间平仓 {{ o.time_close_hours or '' }}h</span>
|
||||||
|
· 倒计时 <span class="pos-time-close-cd" id="order-time-close-cd-{{ o.id }}">--:--:--</span>
|
||||||
|
</span>
|
||||||
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pos-grid">
|
<div class="pos-grid">
|
||||||
@@ -785,6 +799,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=1"></script>
|
<script src="/static/instance_ui.js?v=1"></script>
|
||||||
|
<script src="/static/time_close_ui.js?v=1"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -1540,6 +1555,7 @@ function syncKeyMonitorFormFields(){
|
|||||||
manualTp.required = !!trend;
|
manualTp.required = !!trend;
|
||||||
}
|
}
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
||||||
if(upperEl){
|
if(upperEl){
|
||||||
upperEl.style.display = showFb ? "none" : "";
|
upperEl.style.display = showFb ? "none" : "";
|
||||||
upperEl.required = !showFb;
|
upperEl.required = !showFb;
|
||||||
@@ -1564,6 +1580,10 @@ if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
syncKeyMonitorFormFields();
|
syncKeyMonitorFormFields();
|
||||||
|
if(window.TimeCloseUI){
|
||||||
|
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
||||||
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
const keyForm = document.getElementById("key-form");
|
||||||
if(keyForm){
|
if(keyForm){
|
||||||
@@ -1897,6 +1917,7 @@ function refreshPriceSnapshot(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
}
|
}
|
||||||
@@ -2155,6 +2176,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
|
|||||||
+79
-20
@@ -100,6 +100,17 @@ from key_sl_tp_lib import (
|
|||||||
sl_tp_mode_label,
|
sl_tp_mode_label,
|
||||||
sl_tp_plan_summary_text,
|
sl_tp_plan_summary_text,
|
||||||
)
|
)
|
||||||
|
from time_close_lib import (
|
||||||
|
TIME_CLOSE_RESULT,
|
||||||
|
apply_time_close_to_payload,
|
||||||
|
ensure_time_close_schema,
|
||||||
|
parse_time_close_enabled_form,
|
||||||
|
parse_time_close_hours_form,
|
||||||
|
should_trigger_time_close,
|
||||||
|
time_close_insert_values,
|
||||||
|
time_close_label,
|
||||||
|
time_close_settings_from_row,
|
||||||
|
)
|
||||||
from manual_sltp_lib import (
|
from manual_sltp_lib import (
|
||||||
normalize_open_sltp_mode,
|
normalize_open_sltp_mode,
|
||||||
resolve_entrust_sltp_prices,
|
resolve_entrust_sltp_prices,
|
||||||
@@ -1426,6 +1437,7 @@ def init_db():
|
|||||||
c.execute(ddl)
|
c.execute(ddl)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
ensure_time_close_schema(c)
|
||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -4493,6 +4505,8 @@ def _market_open_for_key_monitor(
|
|||||||
take_profit,
|
take_profit,
|
||||||
key_signal_type=None,
|
key_signal_type=None,
|
||||||
breakeven_enabled=0,
|
breakeven_enabled=0,
|
||||||
|
time_close_enabled=0,
|
||||||
|
time_close_hours=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入。
|
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入。
|
||||||
@@ -4609,14 +4623,18 @@ def _market_open_for_key_monitor(
|
|||||||
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
be_enabled = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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
|
||||||
|
)
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO order_monitors "
|
"INSERT INTO order_monitors "
|
||||||
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
||||||
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
||||||
"breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, "
|
"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) "
|
"notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, key_signal_type, "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"time_close_enabled, time_close_hours, time_close_at_ms) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
@@ -4646,6 +4664,9 @@ def _market_open_for_key_monitor(
|
|||||||
trading_day,
|
trading_day,
|
||||||
ORDER_MONITOR_TYPE_KEY_AUTO,
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
stored_key_signal_type(key_signal_type),
|
stored_key_signal_type(key_signal_type),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
||||||
@@ -4835,13 +4856,16 @@ def _insert_order_monitor_from_fib_fill(
|
|||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
opened_at_bj = app_now_str()
|
opened_at_bj = app_now_str()
|
||||||
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(row)
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO order_monitors "
|
"INSERT INTO order_monitors "
|
||||||
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
||||||
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
||||||
"breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, "
|
"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) "
|
"notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, key_signal_type, "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"time_close_enabled, time_close_hours, time_close_at_ms) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
@@ -4871,6 +4895,9 @@ def _insert_order_monitor_from_fib_fill(
|
|||||||
trading_day,
|
trading_day,
|
||||||
ORDER_MONITOR_TYPE_KEY_AUTO,
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
stored_key_signal_type(typ),
|
stored_key_signal_type(typ),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
||||||
@@ -5040,6 +5067,7 @@ def _false_breakout_exists_for_symbol(conn, symbol):
|
|||||||
|
|
||||||
def _add_false_breakout_key_monitor(
|
def _add_false_breakout_key_monitor(
|
||||||
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
):
|
):
|
||||||
if _false_breakout_exists_for_symbol(conn, symbol):
|
if _false_breakout_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
||||||
@@ -5096,21 +5124,25 @@ def _add_false_breakout_key_monitor(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
||||||
be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol, monitor_type, direction, upper, lower, "
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, time_close_enabled, time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
||||||
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0):
|
def _add_fib_key_monitor(
|
||||||
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
|
):
|
||||||
if _fib_key_exists_for_symbol(conn, symbol):
|
if _fib_key_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
||||||
ratio = fib_ratio_from_type(mt)
|
ratio = fib_ratio_from_type(mt)
|
||||||
@@ -5171,15 +5203,16 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, br
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
||||||
be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol, monitor_type, direction, upper, lower, "
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, time_close_enabled, time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, mt, direction_sel, upper_px, lower_px,
|
symbol, mt, direction_sel, upper_px, lower_px,
|
||||||
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return True, None
|
return True, None
|
||||||
@@ -5290,6 +5323,7 @@ def check_key_monitors():
|
|||||||
|
|
||||||
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
||||||
be_on = breakeven_enabled_from_row(r, 0)
|
be_on = breakeven_enabled_from_row(r, 0)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(r)
|
||||||
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
||||||
conn,
|
conn,
|
||||||
sym,
|
sym,
|
||||||
@@ -5299,6 +5333,8 @@ def check_key_monitors():
|
|||||||
tp_raw,
|
tp_raw,
|
||||||
key_signal_type=key_sig,
|
key_signal_type=key_sig,
|
||||||
breakeven_enabled=1 if be_on else 0,
|
breakeven_enabled=1 if be_on else 0,
|
||||||
|
time_close_enabled=tc_en,
|
||||||
|
time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
planned_rr_txt = (
|
planned_rr_txt = (
|
||||||
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
||||||
@@ -5487,12 +5523,14 @@ def check_order_monitors():
|
|||||||
send_wechat_msg(be_msg)
|
send_wechat_msg(be_msg)
|
||||||
|
|
||||||
res = None
|
res = None
|
||||||
|
if should_trigger_time_close(r):
|
||||||
|
res = TIME_CLOSE_RESULT
|
||||||
# 做多
|
# 做多
|
||||||
if direction == "long":
|
if not res and direction == "long":
|
||||||
if p >= take_profit: res = "止盈"
|
if p >= take_profit: res = "止盈"
|
||||||
elif p <= stop_loss: res = "止损"
|
elif p <= stop_loss: res = "止损"
|
||||||
# 做空
|
# 做空
|
||||||
elif direction == "short":
|
elif not res and direction == "short":
|
||||||
if p <= take_profit: res = "止盈"
|
if p <= take_profit: res = "止盈"
|
||||||
elif p >= stop_loss: res = "止损"
|
elif p >= stop_loss: res = "止损"
|
||||||
|
|
||||||
@@ -6414,7 +6452,8 @@ def api_price_snapshot():
|
|||||||
"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_limit_order_id,created_at FROM key_monitors"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
order_rows = conn.execute(
|
order_rows = conn.execute(
|
||||||
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status='active'"
|
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage,"
|
||||||
|
"time_close_enabled,time_close_hours,time_close_at_ms,opened_at_ms FROM order_monitors WHERE status='active'"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -6623,6 +6662,7 @@ def api_price_snapshot():
|
|||||||
format_price_fn=format_price_for_symbol,
|
format_price_fn=format_price_for_symbol,
|
||||||
symbol=r["symbol"],
|
symbol=r["symbol"],
|
||||||
)
|
)
|
||||||
|
apply_time_close_to_payload(payload, r)
|
||||||
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
||||||
r["stop_loss"], r["take_profit"], exchange_tpsl
|
r["stop_loss"], r["take_profit"], exchange_tpsl
|
||||||
)
|
)
|
||||||
@@ -7120,6 +7160,10 @@ def add_key():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
|
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_false_breakout_key_monitor_type(mt):
|
if is_false_breakout_key_monitor_type(mt):
|
||||||
fb_sym = normalize_false_breakout_symbol(symbol)
|
fb_sym = normalize_false_breakout_symbol(symbol)
|
||||||
if not fb_sym:
|
if not fb_sym:
|
||||||
@@ -7154,6 +7198,7 @@ def add_key():
|
|||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ok_fb, err_fb = _add_false_breakout_key_monitor(
|
ok_fb, err_fb = _add_false_breakout_key_monitor(
|
||||||
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,
|
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,
|
||||||
|
time_close_enabled=tc_en, time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -7164,6 +7209,7 @@ def add_key():
|
|||||||
flash(
|
flash(
|
||||||
f"假突破监控已添加,限价单已挂出({symbol})"
|
f"假突破监控已添加,限价单已挂出({symbol})"
|
||||||
f"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}"
|
f"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
+ (f"|{time_close_label(tc_h)}" if tc_en else "")
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
try:
|
try:
|
||||||
@@ -7184,6 +7230,7 @@ def add_key():
|
|||||||
if is_fib_key_monitor_type(mt):
|
if is_fib_key_monitor_type(mt):
|
||||||
ok_fib, err_fib = _add_fib_key_monitor(
|
ok_fib, err_fib = _add_fib_key_monitor(
|
||||||
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
||||||
|
time_close_enabled=tc_en, time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -7194,6 +7241,7 @@ def add_key():
|
|||||||
flash(
|
flash(
|
||||||
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
||||||
f"|移动保本:{'开' if be_flag else '关'}"
|
f"|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
+ (f"|{time_close_label(tc_h)}" if tc_en else "")
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
sl_tp_mode = "standard"
|
sl_tp_mode = "standard"
|
||||||
@@ -7227,8 +7275,8 @@ def add_key():
|
|||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
"max_notify,notify_interval_min) "
|
"max_notify,notify_interval_min,time_close_enabled,time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
mt,
|
mt,
|
||||||
@@ -7240,14 +7288,17 @@ def add_key():
|
|||||||
be_flag,
|
be_flag,
|
||||||
KEY_ALERT_MAX_TIMES,
|
KEY_ALERT_MAX_TIMES,
|
||||||
KEY_ALERT_INTERVAL_MINUTES,
|
KEY_ALERT_INTERVAL_MINUTES,
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
"VALUES (?,?,?,?,?,?,?,?)",
|
"time_close_enabled,time_close_hours) "
|
||||||
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag, tc_en, tc_h),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -7263,6 +7314,8 @@ def add_key():
|
|||||||
extra = ""
|
extra = ""
|
||||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||||
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
if tc_en:
|
||||||
|
extra += f"|{time_close_label(tc_h)}"
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
flash(
|
flash(
|
||||||
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
||||||
@@ -7478,14 +7531,20 @@ def add_order():
|
|||||||
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
||||||
|
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
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
conn.execute(
|
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) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"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, time_close_enabled, time_close_hours, time_close_at_ms) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit,
|
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,
|
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,
|
breakeven_enabled,
|
||||||
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
||||||
ORDER_MONITOR_TYPE_MANUAL,
|
ORDER_MONITOR_TYPE_MANUAL,
|
||||||
|
tc_en, tc_h, tc_at,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -356,6 +356,14 @@
|
|||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
||||||
</label>
|
</label>
|
||||||
|
<label id="order-time-close-wrap" class="order-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
|
<input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb"> 时间平仓
|
||||||
|
<select name="time_close_hours" id="order-time-close-hours" disabled>
|
||||||
|
<option value="1">1h</option>
|
||||||
|
<option value="2">2h</option>
|
||||||
|
<option value="4" selected>4h</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
||||||
</label>
|
</label>
|
||||||
@@ -397,6 +405,12 @@
|
|||||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||||
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="pos-meta-item pos-meta-on pos-time-close-meta" id="order-time-close-wrap-{{ o.id }}"
|
||||||
|
{% if not o.time_close_enabled %}style="display:none"{% endif %}
|
||||||
|
data-close-at-ms="{{ o.time_close_at_ms or '' }}">
|
||||||
|
<span class="pos-time-close-label">时间平仓 {{ o.time_close_hours or '' }}h</span>
|
||||||
|
· 倒计时 <span class="pos-time-close-cd" id="order-time-close-cd-{{ o.id }}">--:--:--</span>
|
||||||
|
</span>
|
||||||
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pos-grid">
|
<div class="pos-grid">
|
||||||
@@ -765,6 +779,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=1"></script>
|
<script src="/static/instance_ui.js?v=1"></script>
|
||||||
|
<script src="/static/time_close_ui.js?v=1"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -1520,6 +1535,7 @@ function syncKeyMonitorFormFields(){
|
|||||||
manualTp.required = !!trend;
|
manualTp.required = !!trend;
|
||||||
}
|
}
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
||||||
if(upperEl){
|
if(upperEl){
|
||||||
upperEl.style.display = showFb ? "none" : "";
|
upperEl.style.display = showFb ? "none" : "";
|
||||||
upperEl.required = !showFb;
|
upperEl.required = !showFb;
|
||||||
@@ -1544,6 +1560,10 @@ if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
syncKeyMonitorFormFields();
|
syncKeyMonitorFormFields();
|
||||||
|
if(window.TimeCloseUI){
|
||||||
|
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
||||||
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
const keyForm = document.getElementById("key-form");
|
||||||
if(keyForm){
|
if(keyForm){
|
||||||
@@ -1877,6 +1897,7 @@ function refreshPriceSnapshot(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
}
|
}
|
||||||
@@ -2135,6 +2156,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
|
|||||||
@@ -100,6 +100,17 @@ from key_sl_tp_lib import (
|
|||||||
sl_tp_mode_label,
|
sl_tp_mode_label,
|
||||||
sl_tp_plan_summary_text,
|
sl_tp_plan_summary_text,
|
||||||
)
|
)
|
||||||
|
from time_close_lib import (
|
||||||
|
TIME_CLOSE_RESULT,
|
||||||
|
apply_time_close_to_payload,
|
||||||
|
ensure_time_close_schema,
|
||||||
|
parse_time_close_enabled_form,
|
||||||
|
parse_time_close_hours_form,
|
||||||
|
should_trigger_time_close,
|
||||||
|
time_close_insert_values,
|
||||||
|
time_close_label,
|
||||||
|
time_close_settings_from_row,
|
||||||
|
)
|
||||||
from manual_sltp_lib import (
|
from manual_sltp_lib import (
|
||||||
normalize_open_sltp_mode,
|
normalize_open_sltp_mode,
|
||||||
resolve_entrust_sltp_prices,
|
resolve_entrust_sltp_prices,
|
||||||
@@ -1426,6 +1437,7 @@ def init_db():
|
|||||||
c.execute(ddl)
|
c.execute(ddl)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
ensure_time_close_schema(c)
|
||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -4493,6 +4505,8 @@ def _market_open_for_key_monitor(
|
|||||||
take_profit,
|
take_profit,
|
||||||
key_signal_type=None,
|
key_signal_type=None,
|
||||||
breakeven_enabled=0,
|
breakeven_enabled=0,
|
||||||
|
time_close_enabled=0,
|
||||||
|
time_close_hours=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入。
|
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入。
|
||||||
@@ -4609,14 +4623,18 @@ def _market_open_for_key_monitor(
|
|||||||
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
be_enabled = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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
|
||||||
|
)
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO order_monitors "
|
"INSERT INTO order_monitors "
|
||||||
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
||||||
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
||||||
"breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, "
|
"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) "
|
"notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, key_signal_type, "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"time_close_enabled, time_close_hours, time_close_at_ms) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
@@ -4646,6 +4664,9 @@ def _market_open_for_key_monitor(
|
|||||||
trading_day,
|
trading_day,
|
||||||
ORDER_MONITOR_TYPE_KEY_AUTO,
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
stored_key_signal_type(key_signal_type),
|
stored_key_signal_type(key_signal_type),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
||||||
@@ -4835,13 +4856,16 @@ def _insert_order_monitor_from_fib_fill(
|
|||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
opened_at_bj = app_now_str()
|
opened_at_bj = app_now_str()
|
||||||
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(row)
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO order_monitors "
|
"INSERT INTO order_monitors "
|
||||||
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
||||||
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
||||||
"breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, "
|
"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) "
|
"notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, key_signal_type, "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"time_close_enabled, time_close_hours, time_close_at_ms) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
@@ -4871,6 +4895,9 @@ def _insert_order_monitor_from_fib_fill(
|
|||||||
trading_day,
|
trading_day,
|
||||||
ORDER_MONITOR_TYPE_KEY_AUTO,
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
stored_key_signal_type(typ),
|
stored_key_signal_type(typ),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
||||||
@@ -5040,6 +5067,7 @@ def _false_breakout_exists_for_symbol(conn, symbol):
|
|||||||
|
|
||||||
def _add_false_breakout_key_monitor(
|
def _add_false_breakout_key_monitor(
|
||||||
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
):
|
):
|
||||||
if _false_breakout_exists_for_symbol(conn, symbol):
|
if _false_breakout_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
||||||
@@ -5096,21 +5124,25 @@ def _add_false_breakout_key_monitor(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
||||||
be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol, monitor_type, direction, upper, lower, "
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, time_close_enabled, time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
||||||
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0):
|
def _add_fib_key_monitor(
|
||||||
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
|
):
|
||||||
if _fib_key_exists_for_symbol(conn, symbol):
|
if _fib_key_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
||||||
ratio = fib_ratio_from_type(mt)
|
ratio = fib_ratio_from_type(mt)
|
||||||
@@ -5171,15 +5203,16 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, br
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
return False, friendly_exchange_error(e, available_usdt=available_usdt)
|
||||||
be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol, monitor_type, direction, upper, lower, "
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, time_close_enabled, time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, mt, direction_sel, upper_px, lower_px,
|
symbol, mt, direction_sel, upper_px, lower_px,
|
||||||
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return True, None
|
return True, None
|
||||||
@@ -5290,6 +5323,7 @@ def check_key_monitors():
|
|||||||
|
|
||||||
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
||||||
be_on = breakeven_enabled_from_row(r, 0)
|
be_on = breakeven_enabled_from_row(r, 0)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(r)
|
||||||
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
||||||
conn,
|
conn,
|
||||||
sym,
|
sym,
|
||||||
@@ -5299,6 +5333,8 @@ def check_key_monitors():
|
|||||||
tp_raw,
|
tp_raw,
|
||||||
key_signal_type=key_sig,
|
key_signal_type=key_sig,
|
||||||
breakeven_enabled=1 if be_on else 0,
|
breakeven_enabled=1 if be_on else 0,
|
||||||
|
time_close_enabled=tc_en,
|
||||||
|
time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
planned_rr_txt = (
|
planned_rr_txt = (
|
||||||
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
||||||
@@ -5487,12 +5523,14 @@ def check_order_monitors():
|
|||||||
send_wechat_msg(be_msg)
|
send_wechat_msg(be_msg)
|
||||||
|
|
||||||
res = None
|
res = None
|
||||||
|
if should_trigger_time_close(r):
|
||||||
|
res = TIME_CLOSE_RESULT
|
||||||
# 做多
|
# 做多
|
||||||
if direction == "long":
|
if not res and direction == "long":
|
||||||
if p >= take_profit: res = "止盈"
|
if p >= take_profit: res = "止盈"
|
||||||
elif p <= stop_loss: res = "止损"
|
elif p <= stop_loss: res = "止损"
|
||||||
# 做空
|
# 做空
|
||||||
elif direction == "short":
|
elif not res and direction == "short":
|
||||||
if p <= take_profit: res = "止盈"
|
if p <= take_profit: res = "止盈"
|
||||||
elif p >= stop_loss: res = "止损"
|
elif p >= stop_loss: res = "止损"
|
||||||
|
|
||||||
@@ -6414,7 +6452,8 @@ def api_price_snapshot():
|
|||||||
"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_limit_order_id,created_at FROM key_monitors"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
order_rows = conn.execute(
|
order_rows = conn.execute(
|
||||||
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status='active'"
|
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage,"
|
||||||
|
"time_close_enabled,time_close_hours,time_close_at_ms,opened_at_ms FROM order_monitors WHERE status='active'"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -6623,6 +6662,7 @@ def api_price_snapshot():
|
|||||||
format_price_fn=format_price_for_symbol,
|
format_price_fn=format_price_for_symbol,
|
||||||
symbol=r["symbol"],
|
symbol=r["symbol"],
|
||||||
)
|
)
|
||||||
|
apply_time_close_to_payload(payload, r)
|
||||||
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
||||||
r["stop_loss"], r["take_profit"], exchange_tpsl
|
r["stop_loss"], r["take_profit"], exchange_tpsl
|
||||||
)
|
)
|
||||||
@@ -7120,6 +7160,10 @@ def add_key():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
|
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_false_breakout_key_monitor_type(mt):
|
if is_false_breakout_key_monitor_type(mt):
|
||||||
fb_sym = normalize_false_breakout_symbol(symbol)
|
fb_sym = normalize_false_breakout_symbol(symbol)
|
||||||
if not fb_sym:
|
if not fb_sym:
|
||||||
@@ -7154,6 +7198,7 @@ def add_key():
|
|||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ok_fb, err_fb = _add_false_breakout_key_monitor(
|
ok_fb, err_fb = _add_false_breakout_key_monitor(
|
||||||
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,
|
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,
|
||||||
|
time_close_enabled=tc_en, time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -7164,6 +7209,7 @@ def add_key():
|
|||||||
flash(
|
flash(
|
||||||
f"假突破监控已添加,限价单已挂出({symbol})"
|
f"假突破监控已添加,限价单已挂出({symbol})"
|
||||||
f"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}"
|
f"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
+ (f"|{time_close_label(tc_h)}" if tc_en else "")
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
try:
|
try:
|
||||||
@@ -7184,6 +7230,7 @@ def add_key():
|
|||||||
if is_fib_key_monitor_type(mt):
|
if is_fib_key_monitor_type(mt):
|
||||||
ok_fib, err_fib = _add_fib_key_monitor(
|
ok_fib, err_fib = _add_fib_key_monitor(
|
||||||
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
||||||
|
time_close_enabled=tc_en, time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -7194,6 +7241,7 @@ def add_key():
|
|||||||
flash(
|
flash(
|
||||||
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
||||||
f"|移动保本:{'开' if be_flag else '关'}"
|
f"|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
+ (f"|{time_close_label(tc_h)}" if tc_en else "")
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
sl_tp_mode = "standard"
|
sl_tp_mode = "standard"
|
||||||
@@ -7227,8 +7275,8 @@ def add_key():
|
|||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
"max_notify,notify_interval_min) "
|
"max_notify,notify_interval_min,time_close_enabled,time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
mt,
|
mt,
|
||||||
@@ -7240,14 +7288,17 @@ def add_key():
|
|||||||
be_flag,
|
be_flag,
|
||||||
KEY_ALERT_MAX_TIMES,
|
KEY_ALERT_MAX_TIMES,
|
||||||
KEY_ALERT_INTERVAL_MINUTES,
|
KEY_ALERT_INTERVAL_MINUTES,
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
"VALUES (?,?,?,?,?,?,?,?)",
|
"time_close_enabled,time_close_hours) "
|
||||||
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag, tc_en, tc_h),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -7263,6 +7314,8 @@ def add_key():
|
|||||||
extra = ""
|
extra = ""
|
||||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||||
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
if tc_en:
|
||||||
|
extra += f"|{time_close_label(tc_h)}"
|
||||||
if mt in KEY_MONITOR_RS_TYPES:
|
if mt in KEY_MONITOR_RS_TYPES:
|
||||||
flash(
|
flash(
|
||||||
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿,"
|
||||||
@@ -7478,14 +7531,20 @@ def add_order():
|
|||||||
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
||||||
|
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
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
conn.execute(
|
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) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"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, time_close_enabled, time_close_hours, time_close_at_ms) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit,
|
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,
|
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,
|
breakeven_enabled,
|
||||||
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
||||||
ORDER_MONITOR_TYPE_MANUAL,
|
ORDER_MONITOR_TYPE_MANUAL,
|
||||||
|
tc_en, tc_h, tc_at,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -356,6 +356,14 @@
|
|||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
||||||
</label>
|
</label>
|
||||||
|
<label id="order-time-close-wrap" class="order-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
|
<input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb"> 时间平仓
|
||||||
|
<select name="time_close_hours" id="order-time-close-hours" disabled>
|
||||||
|
<option value="1">1h</option>
|
||||||
|
<option value="2">2h</option>
|
||||||
|
<option value="4" selected>4h</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
||||||
</label>
|
</label>
|
||||||
@@ -397,6 +405,12 @@
|
|||||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||||
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="pos-meta-item pos-meta-on pos-time-close-meta" id="order-time-close-wrap-{{ o.id }}"
|
||||||
|
{% if not o.time_close_enabled %}style="display:none"{% endif %}
|
||||||
|
data-close-at-ms="{{ o.time_close_at_ms or '' }}">
|
||||||
|
<span class="pos-time-close-label">时间平仓 {{ o.time_close_hours or '' }}h</span>
|
||||||
|
· 倒计时 <span class="pos-time-close-cd" id="order-time-close-cd-{{ o.id }}">--:--:--</span>
|
||||||
|
</span>
|
||||||
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pos-grid">
|
<div class="pos-grid">
|
||||||
@@ -765,6 +779,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=1"></script>
|
<script src="/static/instance_ui.js?v=1"></script>
|
||||||
|
<script src="/static/time_close_ui.js?v=1"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -1520,6 +1535,7 @@ function syncKeyMonitorFormFields(){
|
|||||||
manualTp.required = !!trend;
|
manualTp.required = !!trend;
|
||||||
}
|
}
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
||||||
if(upperEl){
|
if(upperEl){
|
||||||
upperEl.style.display = showFb ? "none" : "";
|
upperEl.style.display = showFb ? "none" : "";
|
||||||
upperEl.required = !showFb;
|
upperEl.required = !showFb;
|
||||||
@@ -1544,6 +1560,10 @@ if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
syncKeyMonitorFormFields();
|
syncKeyMonitorFormFields();
|
||||||
|
if(window.TimeCloseUI){
|
||||||
|
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
||||||
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
const keyForm = document.getElementById("key-form");
|
||||||
if(keyForm){
|
if(keyForm){
|
||||||
@@ -1877,6 +1897,7 @@ function refreshPriceSnapshot(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
}
|
}
|
||||||
@@ -2135,6 +2156,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
|
|||||||
+75
-17
@@ -100,6 +100,17 @@ from key_sl_tp_lib import (
|
|||||||
sl_tp_mode_label,
|
sl_tp_mode_label,
|
||||||
sl_tp_plan_summary_text,
|
sl_tp_plan_summary_text,
|
||||||
)
|
)
|
||||||
|
from time_close_lib import (
|
||||||
|
TIME_CLOSE_RESULT,
|
||||||
|
apply_time_close_to_payload,
|
||||||
|
ensure_time_close_schema,
|
||||||
|
parse_time_close_enabled_form,
|
||||||
|
parse_time_close_hours_form,
|
||||||
|
should_trigger_time_close,
|
||||||
|
time_close_insert_values,
|
||||||
|
time_close_label,
|
||||||
|
time_close_settings_from_row,
|
||||||
|
)
|
||||||
from manual_sltp_lib import (
|
from manual_sltp_lib import (
|
||||||
normalize_open_sltp_mode,
|
normalize_open_sltp_mode,
|
||||||
resolve_entrust_sltp_prices,
|
resolve_entrust_sltp_prices,
|
||||||
@@ -1427,6 +1438,7 @@ def init_db():
|
|||||||
c.execute(ddl)
|
c.execute(ddl)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
ensure_time_close_schema(c)
|
||||||
try:
|
try:
|
||||||
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
c.execute("ALTER TABLE trading_sessions ADD COLUMN key_sizing_capital_snapshot REAL")
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -4384,13 +4396,16 @@ def _insert_order_monitor_from_fib_fill(
|
|||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
opened_at_bj = app_now_str()
|
opened_at_bj = app_now_str()
|
||||||
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(row)
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO order_monitors "
|
"INSERT INTO order_monitors "
|
||||||
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
||||||
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
||||||
"breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, "
|
"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) "
|
"notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, key_signal_type, "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"time_close_enabled, time_close_hours, time_close_at_ms) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
@@ -4420,6 +4435,9 @@ def _insert_order_monitor_from_fib_fill(
|
|||||||
trading_day,
|
trading_day,
|
||||||
ORDER_MONITOR_TYPE_KEY_AUTO,
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
stored_key_signal_type(typ),
|
stored_key_signal_type(typ),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
return int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
||||||
@@ -4608,6 +4626,7 @@ def _false_breakout_exists_for_symbol(conn, symbol):
|
|||||||
|
|
||||||
def _add_false_breakout_key_monitor(
|
def _add_false_breakout_key_monitor(
|
||||||
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
):
|
):
|
||||||
if _false_breakout_exists_for_symbol(conn, symbol):
|
if _false_breakout_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
return False, f"{symbol} 已有假突破监控(同币仅允许一条)"
|
||||||
@@ -4664,21 +4683,25 @@ def _add_false_breakout_key_monitor(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, friendly_okx_error(e, available_usdt=available_usdt)
|
return False, friendly_okx_error(e, available_usdt=available_usdt)
|
||||||
be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol, monitor_type, direction, upper, lower, "
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled, time_close_enabled, time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
||||||
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0):
|
def _add_fib_key_monitor(
|
||||||
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0,
|
||||||
|
time_close_enabled=0, time_close_hours=None,
|
||||||
|
):
|
||||||
if _fib_key_exists_for_symbol(conn, symbol):
|
if _fib_key_exists_for_symbol(conn, symbol):
|
||||||
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
||||||
ratio = fib_ratio_from_type(mt)
|
ratio = fib_ratio_from_type(mt)
|
||||||
@@ -4775,6 +4798,8 @@ def _market_open_for_key_monitor(
|
|||||||
take_profit,
|
take_profit,
|
||||||
key_signal_type=None,
|
key_signal_type=None,
|
||||||
breakeven_enabled=0,
|
breakeven_enabled=0,
|
||||||
|
time_close_enabled=0,
|
||||||
|
time_close_hours=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入(OKX 永续)。
|
与手动「实盘下单」对齐的市价开仓与 order_monitors 写入(OKX 永续)。
|
||||||
@@ -4891,14 +4916,18 @@ def _market_open_for_key_monitor(
|
|||||||
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0)
|
||||||
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
be_enabled = 1 if int(breakeven_enabled or 0) != 0 else 0
|
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
|
||||||
|
)
|
||||||
|
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO order_monitors "
|
"INSERT INTO order_monitors "
|
||||||
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
"(symbol, exchange_symbol, direction, trigger_price, stop_loss, initial_stop_loss, take_profit, "
|
||||||
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
"margin_capital, leverage, trade_style, risk_percent, risk_amount, "
|
||||||
"breakeven_rr_trigger, breakeven_offset_pct, breakeven_step_r, breakeven_armed, breakeven_price, breakeven_enabled, "
|
"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) "
|
"notional_value, position_ratio, base_amount, order_amount, exchange_order_id, opened_at, opened_at_ms, session_date, monitor_type, key_signal_type, "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"time_close_enabled, time_close_hours, time_close_at_ms) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
exchange_symbol,
|
exchange_symbol,
|
||||||
@@ -4928,6 +4957,9 @@ def _market_open_for_key_monitor(
|
|||||||
trading_day,
|
trading_day,
|
||||||
ORDER_MONITOR_TYPE_KEY_AUTO,
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
stored_key_signal_type(key_signal_type),
|
stored_key_signal_type(key_signal_type),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0])
|
||||||
@@ -5089,6 +5121,7 @@ def check_key_monitors():
|
|||||||
|
|
||||||
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None
|
||||||
be_on = breakeven_enabled_from_row(r, 0)
|
be_on = breakeven_enabled_from_row(r, 0)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(r)
|
||||||
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
ok_trade, trade_err, det = _market_open_for_key_monitor(
|
||||||
conn,
|
conn,
|
||||||
sym,
|
sym,
|
||||||
@@ -5098,6 +5131,8 @@ def check_key_monitors():
|
|||||||
tp_raw,
|
tp_raw,
|
||||||
key_signal_type=key_sig,
|
key_signal_type=key_sig,
|
||||||
breakeven_enabled=1 if be_on else 0,
|
breakeven_enabled=1 if be_on else 0,
|
||||||
|
time_close_enabled=tc_en,
|
||||||
|
time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
planned_rr_txt = (
|
planned_rr_txt = (
|
||||||
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
|
||||||
@@ -5276,12 +5311,14 @@ def check_order_monitors():
|
|||||||
send_wechat_msg(be_msg)
|
send_wechat_msg(be_msg)
|
||||||
|
|
||||||
res = None
|
res = None
|
||||||
|
if should_trigger_time_close(r):
|
||||||
|
res = TIME_CLOSE_RESULT
|
||||||
# 做多
|
# 做多
|
||||||
if direction == "long":
|
if not res and direction == "long":
|
||||||
if p >= take_profit: res = "止盈"
|
if p >= take_profit: res = "止盈"
|
||||||
elif p <= stop_loss: res = "止损"
|
elif p <= stop_loss: res = "止损"
|
||||||
# 做空
|
# 做空
|
||||||
elif direction == "short":
|
elif not res and direction == "short":
|
||||||
if p <= take_profit: res = "止盈"
|
if p <= take_profit: res = "止盈"
|
||||||
elif p >= stop_loss: res = "止损"
|
elif p >= stop_loss: res = "止损"
|
||||||
|
|
||||||
@@ -6001,7 +6038,8 @@ def api_price_snapshot():
|
|||||||
"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_limit_order_id,created_at FROM key_monitors"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
order_rows = conn.execute(
|
order_rows = conn.execute(
|
||||||
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status='active'"
|
"SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage,"
|
||||||
|
"time_close_enabled,time_close_hours,time_close_at_ms,opened_at_ms FROM order_monitors WHERE status='active'"
|
||||||
).fetchall()
|
).fetchall()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -6210,6 +6248,7 @@ def api_price_snapshot():
|
|||||||
format_price_fn=format_price_for_symbol,
|
format_price_fn=format_price_for_symbol,
|
||||||
symbol=r["symbol"],
|
symbol=r["symbol"],
|
||||||
)
|
)
|
||||||
|
apply_time_close_to_payload(payload, r)
|
||||||
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(
|
||||||
r["stop_loss"], r["take_profit"], exchange_tpsl
|
r["stop_loss"], r["take_profit"], exchange_tpsl
|
||||||
)
|
)
|
||||||
@@ -6709,6 +6748,10 @@ def add_key():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
|
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_false_breakout_key_monitor_type(mt):
|
if is_false_breakout_key_monitor_type(mt):
|
||||||
fb_sym = normalize_false_breakout_symbol(symbol)
|
fb_sym = normalize_false_breakout_symbol(symbol)
|
||||||
if not fb_sym:
|
if not fb_sym:
|
||||||
@@ -6739,6 +6782,7 @@ def add_key():
|
|||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
ok_fb, err_fb = _add_false_breakout_key_monitor(
|
ok_fb, err_fb = _add_false_breakout_key_monitor(
|
||||||
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,
|
conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,
|
||||||
|
time_close_enabled=tc_en, time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -6748,6 +6792,7 @@ def add_key():
|
|||||||
flash(
|
flash(
|
||||||
f"假突破监控已添加,限价单已挂出({symbol})"
|
f"假突破监控已添加,限价单已挂出({symbol})"
|
||||||
f"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}"
|
f"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
+ (f"|{time_close_label(tc_h)}" if tc_en else "")
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
uh = round_price_to_exchange(ex_sym_key, float(d["upper"]))
|
uh = round_price_to_exchange(ex_sym_key, float(d["upper"]))
|
||||||
@@ -6761,6 +6806,7 @@ def add_key():
|
|||||||
if is_fib_key_monitor_type(mt):
|
if is_fib_key_monitor_type(mt):
|
||||||
ok_fib, err_fib = _add_fib_key_monitor(
|
ok_fib, err_fib = _add_fib_key_monitor(
|
||||||
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
||||||
|
time_close_enabled=tc_en, time_close_hours=tc_h,
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -6770,6 +6816,7 @@ def add_key():
|
|||||||
flash(
|
flash(
|
||||||
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
||||||
f"|移动保本:{'开' if be_flag else '关'}"
|
f"|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
+ (f"|{time_close_label(tc_h)}" if tc_en else "")
|
||||||
)
|
)
|
||||||
return redirect("/key_monitor")
|
return redirect("/key_monitor")
|
||||||
sl_tp_mode = "standard"
|
sl_tp_mode = "standard"
|
||||||
@@ -6800,8 +6847,8 @@ def add_key():
|
|||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
"max_notify,notify_interval_min) "
|
"max_notify,notify_interval_min,time_close_enabled,time_close_hours) "
|
||||||
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
mt,
|
mt,
|
||||||
@@ -6813,20 +6860,25 @@ def add_key():
|
|||||||
be_flag,
|
be_flag,
|
||||||
KEY_ALERT_MAX_TIMES,
|
KEY_ALERT_MAX_TIMES,
|
||||||
KEY_ALERT_INTERVAL_MINUTES,
|
KEY_ALERT_INTERVAL_MINUTES,
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO key_monitors "
|
"INSERT INTO key_monitors "
|
||||||
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
"VALUES (?,?,?,?,?,?,?,?)",
|
"time_close_enabled,time_close_hours) "
|
||||||
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag, tc_en, tc_h),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
extra = ""
|
extra = ""
|
||||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||||
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
||||||
|
if tc_en:
|
||||||
|
extra += f"|{time_close_label(tc_h)}"
|
||||||
ctr = False
|
ctr = False
|
||||||
try:
|
try:
|
||||||
coin4h_status, _, _ = _status_by_ema55(symbol, "4h")
|
coin4h_status, _, _ = _status_by_ema55(symbol, "4h")
|
||||||
@@ -7019,13 +7071,19 @@ def add_order():
|
|||||||
else:
|
else:
|
||||||
breakeven_price = round(float(trigger_price) * (1 + breakeven_offset_pct / 100.0), 8)
|
breakeven_price = round(float(trigger_price) * (1 + breakeven_offset_pct / 100.0), 8)
|
||||||
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
||||||
|
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
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
conn.execute(
|
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) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
"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, time_close_enabled, time_close_hours, time_close_at_ms) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
(
|
(
|
||||||
symbol, exchange_symbol, direction, trigger_price, stop_loss, stop_loss, take_profit,
|
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,
|
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,
|
breakeven_enabled,
|
||||||
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day, "下单监控",
|
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day, "下单监控",
|
||||||
|
tc_en, tc_h, tc_at,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -385,6 +385,14 @@
|
|||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
<input type="checkbox" name="breakeven_enabled" value="1" checked> 启用移动保本(关闭则仅保留初始止损与交易所挂单)
|
||||||
</label>
|
</label>
|
||||||
|
<label id="order-time-close-wrap" class="order-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
|
<input type="checkbox" name="time_close_enabled" value="1" id="order-time-close-cb"> 时间平仓
|
||||||
|
<select name="time_close_hours" id="order-time-close-hours" disabled>
|
||||||
|
<option value="1">1h</option>
|
||||||
|
<option value="2">2h</option>
|
||||||
|
<option value="4" selected>4h</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
<label style="display:flex;align-items:center;gap:4px;font-size:.82rem;color:#cfd3ef">
|
||||||
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
<input type="checkbox" name="order_chart" value="true"> 开仓后生成多周期K线图(各周期100根,含开平仓标记)
|
||||||
</label>
|
</label>
|
||||||
@@ -426,6 +434,12 @@
|
|||||||
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
<span class="pos-meta-item {% if o.breakeven_enabled %}pos-meta-on{% else %}pos-meta-off{% endif %}">
|
||||||
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
{% if o.breakeven_enabled %}移动保本:开 {{ o.breakeven_rr_trigger or '-' }}R→{{ price_fmt(o.symbol, o.breakeven_price) }}{% else %}移动保本:关{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="pos-meta-item pos-meta-on pos-time-close-meta" id="order-time-close-wrap-{{ o.id }}"
|
||||||
|
{% if not o.time_close_enabled %}style="display:none"{% endif %}
|
||||||
|
data-close-at-ms="{{ o.time_close_at_ms or '' }}">
|
||||||
|
<span class="pos-time-close-label">时间平仓 {{ o.time_close_hours or '' }}h</span>
|
||||||
|
· 倒计时 <span class="pos-time-close-cd" id="order-time-close-cd-{{ o.id }}">--:--:--</span>
|
||||||
|
</span>
|
||||||
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
<span class="pos-meta-item" id="order-be-wrap-{{ o.id }}" style="display:none"><span class="pos-breakeven-badge">已保本</span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pos-grid">
|
<div class="pos-grid">
|
||||||
@@ -794,6 +808,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/instance_ui.js?v=1"></script>
|
<script src="/static/instance_ui.js?v=1"></script>
|
||||||
|
<script src="/static/time_close_ui.js?v=1"></script>
|
||||||
<script src="/static/ai_review_render.js?v=2"></script>
|
<script src="/static/ai_review_render.js?v=2"></script>
|
||||||
<script src="/static/form_submit_guard.js?v=2"></script>
|
<script src="/static/form_submit_guard.js?v=2"></script>
|
||||||
<script>
|
<script>
|
||||||
@@ -1549,6 +1564,7 @@ function syncKeyMonitorFormFields(){
|
|||||||
manualTp.required = !!trend;
|
manualTp.required = !!trend;
|
||||||
}
|
}
|
||||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.syncKeyTimeCloseVisibility(showBe);
|
||||||
if(upperEl){
|
if(upperEl){
|
||||||
upperEl.style.display = showFb ? "none" : "";
|
upperEl.style.display = showFb ? "none" : "";
|
||||||
upperEl.required = !showFb;
|
upperEl.required = !showFb;
|
||||||
@@ -1573,6 +1589,10 @@ if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
|||||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
if(keyDirSel) keyDirSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||||
syncKeyMonitorFormFields();
|
syncKeyMonitorFormFields();
|
||||||
|
if(window.TimeCloseUI){
|
||||||
|
TimeCloseUI.bindTimeCloseForm("key-time-close-cb", "key-time-close-hours", "key-time-close-wrap");
|
||||||
|
TimeCloseUI.bindTimeCloseForm("order-time-close-cb", "order-time-close-hours", "order-time-close-wrap");
|
||||||
|
}
|
||||||
|
|
||||||
const keyForm = document.getElementById("key-form");
|
const keyForm = document.getElementById("key-form");
|
||||||
if(keyForm){
|
if(keyForm){
|
||||||
@@ -1907,6 +1927,7 @@ function refreshPriceSnapshot(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
if(o.exchange_tpsl) paintExchangeTpslRow(o.id, o.exchange_tpsl);
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
}
|
}
|
||||||
@@ -2188,6 +2209,7 @@ function refreshPriceSnapshotConditional(){
|
|||||||
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
paintBreakevenBadge(o.id, o.sl_breakeven_secured);
|
||||||
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
paintExchangeTpslRow(o.id, o.exchange_tpsl || {});
|
||||||
paintPlanTpslDisplay(o.id, o);
|
paintPlanTpslDisplay(o.id, o);
|
||||||
|
if(window.TimeCloseUI) TimeCloseUI.paintOrderTimeClose(o);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch(()=>{});
|
}).catch(()=>{});
|
||||||
|
|||||||
@@ -1359,6 +1359,12 @@ def _merge_flask_order_price_fields(hub_mon: dict | None, snap: dict | None) ->
|
|||||||
"stop_loss_display",
|
"stop_loss_display",
|
||||||
"take_profit_display",
|
"take_profit_display",
|
||||||
"display_rr_ratio",
|
"display_rr_ratio",
|
||||||
|
"time_close_enabled",
|
||||||
|
"time_close_hours",
|
||||||
|
"time_close_at_ms",
|
||||||
|
"time_close_label",
|
||||||
|
"time_close_countdown",
|
||||||
|
"time_close_remaining_sec",
|
||||||
):
|
):
|
||||||
if key in op and op[key] not in (None, ""):
|
if key in op and op[key] not in (None, ""):
|
||||||
o[key] = op[key]
|
o[key] = op[key]
|
||||||
|
|||||||
@@ -2304,6 +2304,15 @@
|
|||||||
meta.push(
|
meta.push(
|
||||||
`<span class="${beOn ? "pos-meta-on" : "pos-meta-off"}">移动保本:${beOn ? "开" : "关"}</span>`
|
`<span class="${beOn ? "pos-meta-on" : "pos-meta-off"}">移动保本:${beOn ? "开" : "关"}</span>`
|
||||||
);
|
);
|
||||||
|
if (mo.time_close_enabled) {
|
||||||
|
const tcLabel = mo.time_close_label || `时间平仓 ${mo.time_close_hours || ""}h`;
|
||||||
|
const tcCd = mo.time_close_countdown || "--:--:--";
|
||||||
|
const tcAt = mo.time_close_at_ms != null ? String(mo.time_close_at_ms) : "";
|
||||||
|
meta.push(
|
||||||
|
`<span class="pos-meta-item pos-meta-on pos-time-close-meta" data-close-at-ms="${esc(tcAt)}">` +
|
||||||
|
`${esc(tcLabel)} · 倒计时 <span class="pos-time-close-cd">${esc(tcCd)}</span></span>`
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
meta.push("来源: 交易所持仓");
|
meta.push("来源: 交易所持仓");
|
||||||
meta.push("风格: —");
|
meta.push("风格: —");
|
||||||
|
|||||||
@@ -588,6 +588,7 @@
|
|||||||
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
||||||
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
||||||
<script src="/assets/ai_review_render.js?v=3"></script>
|
<script src="/assets/ai_review_render.js?v=3"></script>
|
||||||
<script src="/assets/app.js?v=20260612-monitor-desktop-layout"></script>
|
<script src="/assets/time_close_ui.js?v=1"></script>
|
||||||
|
<script src="/assets/app.js?v=20260612-time-close"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* 时间平仓:表单开关 + 持仓倒计时。
|
||||||
|
*/
|
||||||
|
(function (global) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function pad2(n) {
|
||||||
|
return n < 10 ? "0" + n : String(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCountdown(sec) {
|
||||||
|
const s = Math.max(0, parseInt(sec, 10) || 0);
|
||||||
|
const h = Math.floor(s / 3600);
|
||||||
|
const m = Math.floor((s % 3600) / 60);
|
||||||
|
const r = s % 60;
|
||||||
|
return pad2(h) + ":" + pad2(m) + ":" + pad2(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindTimeCloseForm(checkboxId, selectId, wrapId) {
|
||||||
|
const cb = document.getElementById(checkboxId);
|
||||||
|
const sel = document.getElementById(selectId);
|
||||||
|
const wrap = wrapId ? document.getElementById(wrapId) : null;
|
||||||
|
if (!cb || !sel) return;
|
||||||
|
function sync() {
|
||||||
|
const on = !!cb.checked;
|
||||||
|
sel.disabled = !on;
|
||||||
|
if (wrap) wrap.classList.toggle("is-disabled", !on);
|
||||||
|
}
|
||||||
|
cb.addEventListener("change", sync);
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintOrderTimeClose(order) {
|
||||||
|
if (!order || order.id == null) return;
|
||||||
|
const wrap = document.getElementById("order-time-close-wrap-" + order.id);
|
||||||
|
const cd = document.getElementById("order-time-close-cd-" + order.id);
|
||||||
|
if (!wrap || !cd) return;
|
||||||
|
const enabled = !!(order.time_close_enabled || order.time_close_at_ms);
|
||||||
|
if (!enabled) {
|
||||||
|
wrap.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrap.style.display = "";
|
||||||
|
const hours = order.time_close_hours;
|
||||||
|
const label = order.time_close_label || (hours ? "时间平仓 " + hours + "h" : "时间平仓");
|
||||||
|
const labelEl = wrap.querySelector(".pos-time-close-label");
|
||||||
|
if (labelEl) labelEl.textContent = label;
|
||||||
|
let rem =
|
||||||
|
order.time_close_remaining_sec != null
|
||||||
|
? Number(order.time_close_remaining_sec)
|
||||||
|
: null;
|
||||||
|
if ((rem == null || !Number.isFinite(rem)) && order.time_close_at_ms) {
|
||||||
|
rem = Math.max(0, Math.floor((Number(order.time_close_at_ms) - Date.now()) / 1000));
|
||||||
|
}
|
||||||
|
cd.textContent = Number.isFinite(rem) ? formatCountdown(rem) : "--:--:--";
|
||||||
|
wrap.dataset.closeAtMs = order.time_close_at_ms ? String(order.time_close_at_ms) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function tickLocalCountdowns() {
|
||||||
|
document.querySelectorAll("[data-close-at-ms]").forEach(function (wrap) {
|
||||||
|
const closeAtRaw = wrap.dataset.closeAtMs || wrap.getAttribute("data-close-at-ms") || "";
|
||||||
|
const cd = wrap.querySelector(".pos-time-close-cd");
|
||||||
|
if (!cd) return;
|
||||||
|
const closeAt = Number(closeAtRaw);
|
||||||
|
if (!closeAt) return;
|
||||||
|
const rem = Math.max(0, Math.floor((closeAt - Date.now()) / 1000));
|
||||||
|
cd.textContent = formatCountdown(rem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintOrders(orders) {
|
||||||
|
(orders || []).forEach(paintOrderTimeClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncKeyTimeCloseVisibility(show) {
|
||||||
|
const wrap = document.getElementById("key-time-close-wrap");
|
||||||
|
if (!wrap) return;
|
||||||
|
wrap.style.display = show ? "inline-flex" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
global.TimeCloseUI = {
|
||||||
|
bindTimeCloseForm: bindTimeCloseForm,
|
||||||
|
paintOrderTimeClose: paintOrderTimeClose,
|
||||||
|
paintOrders: paintOrders,
|
||||||
|
tickLocalCountdowns: tickLocalCountdowns,
|
||||||
|
syncKeyTimeCloseVisibility: syncKeyTimeCloseVisibility,
|
||||||
|
formatCountdown: formatCountdown,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!global.__timeCloseCountdownTimer) {
|
||||||
|
global.__timeCloseCountdownTimer = setInterval(tickLocalCountdowns, 1000);
|
||||||
|
}
|
||||||
|
})(typeof window !== "undefined" ? window : globalThis);
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""对 binance/okx/gate_bot 应用与 gate 相同的时间平仓代码替换。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
FILES = [
|
||||||
|
ROOT / "crypto_monitor_binance" / "app.py",
|
||||||
|
ROOT / "crypto_monitor_okx" / "app.py",
|
||||||
|
ROOT / "crypto_monitor_gate_bot" / "app.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
REPLACEMENTS: list[tuple[str, str]] = [
|
||||||
|
(
|
||||||
|
"def _market_open_for_key_monitor(\n conn,\n symbol,\n direction,\n exchange_symbol,\n stop_loss,\n take_profit,\n key_signal_type=None,\n breakeven_enabled=0,\n):",
|
||||||
|
"def _market_open_for_key_monitor(\n conn,\n symbol,\n direction,\n exchange_symbol,\n stop_loss,\n take_profit,\n key_signal_type=None,\n breakeven_enabled=0,\n time_close_enabled=0,\n time_close_hours=None,\n):",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"def _add_false_breakout_key_monitor(\n conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,\n):",
|
||||||
|
"def _add_false_breakout_key_monitor(\n conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=0,\n time_close_enabled=0, time_close_hours=None,\n):",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0):",
|
||||||
|
"def _add_fib_key_monitor(\n conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0,\n time_close_enabled=0, time_close_hours=None,\n):",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None\n be_on = breakeven_enabled_from_row(r, 0)\n ok_trade, trade_err, det = _market_open_for_key_monitor(\n conn,\n sym,\n direction,\n exchange_symbol,\n sl_raw,\n tp_raw,\n key_signal_type=key_sig,\n breakeven_enabled=1 if be_on else 0,\n )",
|
||||||
|
" key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None\n be_on = breakeven_enabled_from_row(r, 0)\n tc_en, tc_h, _ = time_close_settings_from_row(r)\n ok_trade, trade_err, det = _market_open_for_key_monitor(\n conn,\n sym,\n direction,\n exchange_symbol,\n sl_raw,\n tp_raw,\n key_signal_type=key_sig,\n breakeven_enabled=1 if be_on else 0,\n time_close_enabled=tc_en,\n time_close_hours=tc_h,\n )",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" res = None\n # 做多\n if direction == \"long\":\n if p >= take_profit: res = \"止盈\"\n elif p <= stop_loss: res = \"止损\"\n # 做空\n elif direction == \"short\":\n if p <= take_profit: res = \"止盈\"\n elif p >= stop_loss: res = \"止损\"",
|
||||||
|
" res = None\n if should_trigger_time_close(r):\n res = TIME_CLOSE_RESULT\n # 做多\n if not res and direction == \"long\":\n if p >= take_profit: res = \"止盈\"\n elif p <= stop_loss: res = \"止损\"\n # 做空\n elif not res and direction == \"short\":\n if p <= take_profit: res = \"止盈\"\n elif p >= stop_loss: res = \"止损\"",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
' "SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage FROM order_monitors WHERE status=\'active\'"',
|
||||||
|
' "SELECT id,symbol,exchange_symbol,direction,trigger_price,stop_loss,initial_stop_loss,take_profit,margin_capital,leverage,"\n "time_close_enabled,time_close_hours,time_close_at_ms,opened_at_ms FROM order_monitors WHERE status=\'active\'"',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" apply_order_price_display_fields(\n payload,\n direction=r[\"direction\"],\n entry_price=entry,\n initial_stop_loss=r[\"initial_stop_loss\"],\n stop_loss=r[\"stop_loss\"],\n take_profit=r[\"take_profit\"],\n calc_rr_ratio_fn=calc_rr_ratio,\n exchange_tpsl=exchange_tpsl,\n format_price_fn=format_price_for_symbol,\n symbol=r[\"symbol\"],\n )\n new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(",
|
||||||
|
" apply_order_price_display_fields(\n payload,\n direction=r[\"direction\"],\n entry_price=entry,\n initial_stop_loss=r[\"initial_stop_loss\"],\n stop_loss=r[\"stop_loss\"],\n take_profit=r[\"take_profit\"],\n calc_rr_ratio_fn=calc_rr_ratio,\n exchange_tpsl=exchange_tpsl,\n format_price_fn=format_price_for_symbol,\n symbol=r[\"symbol\"],\n )\n apply_time_close_to_payload(payload, r)\n new_sl, new_tp, changed = order_monitor_tpsl_needs_sync(",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" be_flag = parse_breakeven_enabled_form(d.get(\"breakeven_enabled\"))\n if is_false_breakout_key_monitor_type(mt):",
|
||||||
|
" be_flag = parse_breakeven_enabled_form(d.get(\"breakeven_enabled\"))\n tc_en = parse_time_close_enabled_form(d.get(\"time_close_enabled\"))\n tc_h = parse_time_close_hours_form(d.get(\"time_close_hours\")) if tc_en else None\n if tc_en and not tc_h:\n tc_en = 0\n if is_false_breakout_key_monitor_type(mt):",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" ok_fb, err_fb = _add_false_breakout_key_monitor(\n conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,\n )",
|
||||||
|
" ok_fb, err_fb = _add_false_breakout_key_monitor(\n conn, symbol, direction_sel, upper_px, lower_px, key_px, breakeven_enabled=be_flag,\n time_close_enabled=tc_en, time_close_hours=tc_h,\n )",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" f\"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}\"\n )",
|
||||||
|
" f\"|有效期 {FALSE_BREAKOUT_VALIDITY_HOURS}h|移动保本:{'开' if be_flag else '关'}\"\n + (f\"|{time_close_label(tc_h)}\" if tc_en else \"\")\n )",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" ok_fib, err_fib = _add_fib_key_monitor(\n conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,\n )",
|
||||||
|
" ok_fib, err_fib = _add_fib_key_monitor(\n conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,\n time_close_enabled=tc_en, time_close_hours=tc_h,\n )",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" f\"|移动保本:{'开' if be_flag else '关'}\"\n )\n return redirect(\"/key_monitor\")",
|
||||||
|
" f\"|移动保本:{'开' if be_flag else '关'}\"\n + (f\"|{time_close_label(tc_h)}\" if tc_en else \"\")\n )\n return redirect(\"/key_monitor\")",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
" if mt in KEY_MONITOR_AUTO_TYPES:\n extra = f\"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}\"",
|
||||||
|
" if mt in KEY_MONITOR_AUTO_TYPES:\n extra = f\"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}\"\n if tc_en:\n extra += f\"|{time_close_label(tc_h)}\"",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
MARKET_OPEN_OLD = """ breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw)
|
||||||
|
be_enabled = 1 if int(breakeven_enabled or 0) != 0 else 0
|
||||||
|
|
||||||
|
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) "
|
||||||
|
"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,
|
||||||
|
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(key_signal_type),
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
MARKET_OPEN_NEW = """ 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
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
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(key_signal_type),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
FIB_INSERT_OLD = """ opened_at_bj = app_now_str()
|
||||||
|
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
||||||
|
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) "
|
||||||
|
"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,
|
||||||
|
1 if breakeven_enabled_from_row(row, 0) else 0,
|
||||||
|
notional_value,
|
||||||
|
position_ratio,
|
||||||
|
base_amount,
|
||||||
|
amount,
|
||||||
|
exchange_order_id or "",
|
||||||
|
opened_at_bj,
|
||||||
|
opened_at_ms,
|
||||||
|
trading_day,
|
||||||
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
|
stored_key_signal_type(typ),
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
FIB_INSERT_NEW = """ opened_at_bj = app_now_str()
|
||||||
|
opened_at_ms = _to_ms_with_fallback(None, opened_at_bj)
|
||||||
|
tc_en, tc_h, _ = time_close_settings_from_row(row)
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
|
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,
|
||||||
|
risk_amount_final,
|
||||||
|
breakeven_rr_trigger,
|
||||||
|
breakeven_offset_pct,
|
||||||
|
breakeven_step_r,
|
||||||
|
0,
|
||||||
|
breakeven_price,
|
||||||
|
1 if breakeven_enabled_from_row(row, 0) else 0,
|
||||||
|
notional_value,
|
||||||
|
position_ratio,
|
||||||
|
base_amount,
|
||||||
|
amount,
|
||||||
|
exchange_order_id or "",
|
||||||
|
opened_at_bj,
|
||||||
|
opened_at_ms,
|
||||||
|
trading_day,
|
||||||
|
ORDER_MONITOR_TYPE_KEY_AUTO,
|
||||||
|
stored_key_signal_type(typ),
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
tc_at,
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
KEY_FB_OLD = """ be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO key_monitors "
|
||||||
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(
|
||||||
|
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
||||||
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
KEY_FB_NEW = """ 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_limit_order_id, 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) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(
|
||||||
|
symbol, FALSE_BREAKOUT_MONITOR_TYPE, direction_sel, upper_px, lower_px,
|
||||||
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
KEY_FIB_OLD = """ be_flag = 1 if int(breakeven_enabled or 0) != 0 else 0
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO key_monitors "
|
||||||
|
"(symbol, monitor_type, direction, upper, lower, "
|
||||||
|
"fib_limit_order_id, fib_entry_price, fib_stop_loss, fib_take_profit, "
|
||||||
|
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(
|
||||||
|
symbol, mt, direction_sel, upper_px, lower_px,
|
||||||
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag,
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
KEY_FIB_NEW = """ 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_limit_order_id, 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) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(
|
||||||
|
symbol, mt, direction_sel, upper_px, lower_px,
|
||||||
|
oid, entry, sl, tp, float(amount), margin_capital, leverage, be_flag, tc_en, tc_h,
|
||||||
|
),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
ADD_KEY_RS_OLD = """ conn.execute(
|
||||||
|
"INSERT INTO key_monitors "
|
||||||
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
|
"max_notify,notify_interval_min) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(
|
||||||
|
symbol,
|
||||||
|
mt,
|
||||||
|
direction_sel,
|
||||||
|
upper_px,
|
||||||
|
lower_px,
|
||||||
|
sl_tp_mode,
|
||||||
|
manual_tp,
|
||||||
|
be_flag,
|
||||||
|
KEY_ALERT_MAX_TIMES,
|
||||||
|
KEY_ALERT_INTERVAL_MINUTES,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO key_monitors "
|
||||||
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?)",
|
||||||
|
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
ADD_KEY_RS_NEW = """ conn.execute(
|
||||||
|
"INSERT INTO key_monitors "
|
||||||
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
|
"max_notify,notify_interval_min,time_close_enabled,time_close_hours) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(
|
||||||
|
symbol,
|
||||||
|
mt,
|
||||||
|
direction_sel,
|
||||||
|
upper_px,
|
||||||
|
lower_px,
|
||||||
|
sl_tp_mode,
|
||||||
|
manual_tp,
|
||||||
|
be_flag,
|
||||||
|
KEY_ALERT_MAX_TIMES,
|
||||||
|
KEY_ALERT_INTERVAL_MINUTES,
|
||||||
|
tc_en,
|
||||||
|
tc_h,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO key_monitors "
|
||||||
|
"(symbol,monitor_type,direction,upper,lower,sl_tp_mode,manual_take_profit,breakeven_enabled,"
|
||||||
|
"time_close_enabled,time_close_hours) "
|
||||||
|
"VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||||
|
(symbol, mt, direction_sel, upper_px, lower_px, sl_tp_mode, manual_tp, be_flag, tc_en, tc_h),
|
||||||
|
)"""
|
||||||
|
|
||||||
|
ADD_ORDER_OLD = """ breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
||||||
|
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) 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,
|
||||||
|
breakeven_enabled,
|
||||||
|
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
||||||
|
ORDER_MONITOR_TYPE_MANUAL,
|
||||||
|
)
|
||||||
|
)"""
|
||||||
|
|
||||||
|
ADD_ORDER_NEW = """ breakeven_enabled = 1 if (d.get("breakeven_enabled") or "").strip() in ("1", "true", "on", "yes") else 0
|
||||||
|
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
|
||||||
|
tc_en, tc_h, tc_at = time_close_insert_values(tc_en, tc_h, opened_at_ms)
|
||||||
|
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, 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,
|
||||||
|
breakeven_enabled,
|
||||||
|
notional_value, position_ratio, base_amount, amount, open_order_id, opened_at_bj, opened_at_ms, trading_day,
|
||||||
|
ORDER_MONITOR_TYPE_MANUAL,
|
||||||
|
tc_en, tc_h, tc_at,
|
||||||
|
)
|
||||||
|
)"""
|
||||||
|
|
||||||
|
BIG_BLOCKS = [
|
||||||
|
(MARKET_OPEN_OLD, MARKET_OPEN_NEW),
|
||||||
|
(FIB_INSERT_OLD, FIB_INSERT_NEW),
|
||||||
|
(KEY_FB_OLD, KEY_FB_NEW),
|
||||||
|
(KEY_FIB_OLD, KEY_FIB_NEW),
|
||||||
|
(ADD_KEY_RS_OLD, ADD_KEY_RS_NEW),
|
||||||
|
(ADD_ORDER_OLD, ADD_ORDER_NEW),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def patch(path: Path) -> None:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
for old, new in REPLACEMENTS + BIG_BLOCKS:
|
||||||
|
if old in text:
|
||||||
|
text = text.replace(old, new, 1)
|
||||||
|
path.write_text(text, encoding="utf-8")
|
||||||
|
print("done", path.name)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
for f in FILES:
|
||||||
|
patch(f)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -341,6 +341,17 @@ html[data-theme="light"] .pos-meta-item::after {
|
|||||||
color: #b8c8d8 !important;
|
color: #b8c8d8 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pos-time-close-meta {
|
||||||
|
color: #8fc8ff;
|
||||||
|
}
|
||||||
|
.pos-time-close-meta .pos-time-close-cd {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
.key-time-close-wrap.is-disabled select,
|
||||||
|
.order-time-close-wrap.is-disabled select {
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
html[data-theme="light"] .pos-meta-on {
|
html[data-theme="light"] .pos-meta-on {
|
||||||
color: #006e9a !important;
|
color: #006e9a !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* 时间平仓:表单开关 + 持仓倒计时。
|
||||||
|
*/
|
||||||
|
(function (global) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function pad2(n) {
|
||||||
|
return n < 10 ? "0" + n : String(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCountdown(sec) {
|
||||||
|
const s = Math.max(0, parseInt(sec, 10) || 0);
|
||||||
|
const h = Math.floor(s / 3600);
|
||||||
|
const m = Math.floor((s % 3600) / 60);
|
||||||
|
const r = s % 60;
|
||||||
|
return pad2(h) + ":" + pad2(m) + ":" + pad2(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindTimeCloseForm(checkboxId, selectId, wrapId) {
|
||||||
|
const cb = document.getElementById(checkboxId);
|
||||||
|
const sel = document.getElementById(selectId);
|
||||||
|
const wrap = wrapId ? document.getElementById(wrapId) : null;
|
||||||
|
if (!cb || !sel) return;
|
||||||
|
function sync() {
|
||||||
|
const on = !!cb.checked;
|
||||||
|
sel.disabled = !on;
|
||||||
|
if (wrap) wrap.classList.toggle("is-disabled", !on);
|
||||||
|
}
|
||||||
|
cb.addEventListener("change", sync);
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintOrderTimeClose(order) {
|
||||||
|
if (!order || order.id == null) return;
|
||||||
|
const wrap = document.getElementById("order-time-close-wrap-" + order.id);
|
||||||
|
const cd = document.getElementById("order-time-close-cd-" + order.id);
|
||||||
|
if (!wrap || !cd) return;
|
||||||
|
const enabled = !!(order.time_close_enabled || order.time_close_at_ms);
|
||||||
|
if (!enabled) {
|
||||||
|
wrap.style.display = "none";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wrap.style.display = "";
|
||||||
|
const hours = order.time_close_hours;
|
||||||
|
const label = order.time_close_label || (hours ? "时间平仓 " + hours + "h" : "时间平仓");
|
||||||
|
const labelEl = wrap.querySelector(".pos-time-close-label");
|
||||||
|
if (labelEl) labelEl.textContent = label;
|
||||||
|
let rem =
|
||||||
|
order.time_close_remaining_sec != null
|
||||||
|
? Number(order.time_close_remaining_sec)
|
||||||
|
: null;
|
||||||
|
if ((rem == null || !Number.isFinite(rem)) && order.time_close_at_ms) {
|
||||||
|
rem = Math.max(0, Math.floor((Number(order.time_close_at_ms) - Date.now()) / 1000));
|
||||||
|
}
|
||||||
|
cd.textContent = Number.isFinite(rem) ? formatCountdown(rem) : "--:--:--";
|
||||||
|
wrap.dataset.closeAtMs = order.time_close_at_ms ? String(order.time_close_at_ms) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function tickLocalCountdowns() {
|
||||||
|
document.querySelectorAll("[data-close-at-ms]").forEach(function (wrap) {
|
||||||
|
const closeAtRaw = wrap.dataset.closeAtMs || wrap.getAttribute("data-close-at-ms") || "";
|
||||||
|
const cd = wrap.querySelector(".pos-time-close-cd");
|
||||||
|
if (!cd) return;
|
||||||
|
const closeAt = Number(closeAtRaw);
|
||||||
|
if (!closeAt) return;
|
||||||
|
const rem = Math.max(0, Math.floor((closeAt - Date.now()) / 1000));
|
||||||
|
cd.textContent = formatCountdown(rem);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function paintOrders(orders) {
|
||||||
|
(orders || []).forEach(paintOrderTimeClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncKeyTimeCloseVisibility(show) {
|
||||||
|
const wrap = document.getElementById("key-time-close-wrap");
|
||||||
|
if (!wrap) return;
|
||||||
|
wrap.style.display = show ? "inline-flex" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
global.TimeCloseUI = {
|
||||||
|
bindTimeCloseForm: bindTimeCloseForm,
|
||||||
|
paintOrderTimeClose: paintOrderTimeClose,
|
||||||
|
paintOrders: paintOrders,
|
||||||
|
tickLocalCountdowns: tickLocalCountdowns,
|
||||||
|
syncKeyTimeCloseVisibility: syncKeyTimeCloseVisibility,
|
||||||
|
formatCountdown: formatCountdown,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!global.__timeCloseCountdownTimer) {
|
||||||
|
global.__timeCloseCountdownTimer = setInterval(tickLocalCountdowns, 1000);
|
||||||
|
}
|
||||||
|
})(typeof window !== "undefined" ? window : globalThis);
|
||||||
@@ -157,6 +157,14 @@
|
|||||||
<label id="key-breakeven-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.85rem;color:#9aa">
|
<label id="key-breakeven-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.85rem;color:#9aa">
|
||||||
<input type="checkbox" name="breakeven_enabled" value="1" id="key-breakeven-cb"> 移动保本
|
<input type="checkbox" name="breakeven_enabled" value="1" id="key-breakeven-cb"> 移动保本
|
||||||
</label>
|
</label>
|
||||||
|
<label id="key-time-close-wrap" class="key-time-close-wrap" style="display:inline-flex;align-items:center;gap:4px;font-size:.85rem;color:#9aa">
|
||||||
|
<input type="checkbox" name="time_close_enabled" value="1" id="key-time-close-cb"> 时间平仓
|
||||||
|
<select name="time_close_hours" id="key-time-close-hours" disabled>
|
||||||
|
<option value="1">1h</option>
|
||||||
|
<option value="2">2h</option>
|
||||||
|
<option value="4" selected>4h</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<button type="submit">添加</button>
|
<button type="submit">添加</button>
|
||||||
</form>
|
</form>
|
||||||
<details class="tip-collapse key-rule-collapse">
|
<details class="tip-collapse key-rule-collapse">
|
||||||
@@ -197,6 +205,9 @@
|
|||||||
<span class="pos-meta-item">方案: {{ key_sl_tp_mode_label(k) }}</span>
|
<span class="pos-meta-item">方案: {{ key_sl_tp_mode_label(k) }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="pos-meta-item">保本: {{ '开' if k.breakeven_enabled else '关' }}</span>
|
<span class="pos-meta-item">保本: {{ '开' if k.breakeven_enabled else '关' }}</span>
|
||||||
|
{% if k.time_close_enabled and k.time_close_hours %}
|
||||||
|
<span class="pos-meta-item pos-meta-on">时间平仓: {{ k.time_close_hours }}h</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="pos-grid">
|
<div class="pos-grid">
|
||||||
<div class="pos-cell"><span class="pos-label">现价</span><span class="pos-value" id="key-price-{{ k.id }}">-</span></div>
|
<div class="pos-cell"><span class="pos-label">现价</span><span class="pos-value" id="key-price-{{ k.id }}">-</span></div>
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
"""持仓时间平仓:开仓后按 1h/2h/4h 定时市价平仓。"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
ALLOWED_TIME_CLOSE_HOURS = (1, 2, 4)
|
||||||
|
TIME_CLOSE_RESULT = "时间平仓"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time_close_enabled_form(form_value: Any) -> int:
|
||||||
|
return 1 if str(form_value or "").strip().lower() in ("1", "true", "on", "yes") else 0
|
||||||
|
|
||||||
|
|
||||||
|
def parse_time_close_hours_form(form_value: Any, *, default: int = 4) -> Optional[int]:
|
||||||
|
raw = str(form_value or "").strip().lower().rstrip("h")
|
||||||
|
if not raw:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
h = int(float(raw))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
if h in ALLOWED_TIME_CLOSE_HOURS:
|
||||||
|
return h
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_time_close_hours(value: Any) -> Optional[int]:
|
||||||
|
try:
|
||||||
|
h = int(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
return h if h in ALLOWED_TIME_CLOSE_HOURS else None
|
||||||
|
|
||||||
|
|
||||||
|
def _row_val(row: Any, key: str, default=None):
|
||||||
|
if row is None:
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
if hasattr(row, "keys") and key in row.keys():
|
||||||
|
return row[key]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if isinstance(row, dict):
|
||||||
|
return row.get(key, default)
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def time_close_settings_from_row(row: Any) -> tuple[int, Optional[int], Optional[int]]:
|
||||||
|
"""返回 (enabled, hours, close_at_ms)。"""
|
||||||
|
enabled = int(_row_val(row, "time_close_enabled", 0) or 0) != 0
|
||||||
|
hours = normalize_time_close_hours(_row_val(row, "time_close_hours"))
|
||||||
|
close_at = _row_val(row, "time_close_at_ms")
|
||||||
|
try:
|
||||||
|
close_at_ms = int(close_at) if close_at not in (None, "") else None
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
close_at_ms = None
|
||||||
|
if enabled and hours and not close_at_ms:
|
||||||
|
opened_ms = _row_val(row, "opened_at_ms")
|
||||||
|
try:
|
||||||
|
opened_ms = int(opened_ms) if opened_ms not in (None, "") else None
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
opened_ms = None
|
||||||
|
close_at_ms = compute_close_at_ms(opened_ms, hours)
|
||||||
|
return (1 if enabled and hours else 0, hours, close_at_ms)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_close_at_ms(opened_at_ms: Any, hours: Any) -> Optional[int]:
|
||||||
|
h = normalize_time_close_hours(hours)
|
||||||
|
try:
|
||||||
|
opened = int(opened_at_ms)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
if not h or opened <= 0:
|
||||||
|
return None
|
||||||
|
return opened + h * 3600 * 1000
|
||||||
|
|
||||||
|
|
||||||
|
def should_trigger_time_close(row: Any, *, now_ms: Optional[int] = None) -> bool:
|
||||||
|
enabled, hours, close_at_ms = time_close_settings_from_row(row)
|
||||||
|
if not enabled or not close_at_ms:
|
||||||
|
return False
|
||||||
|
now = int(now_ms if now_ms is not None else time.time() * 1000)
|
||||||
|
return now >= int(close_at_ms)
|
||||||
|
|
||||||
|
|
||||||
|
def time_close_remaining_seconds(close_at_ms: Any, *, now_ms: Optional[int] = None) -> Optional[int]:
|
||||||
|
try:
|
||||||
|
close_at = int(close_at_ms)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
now = int(now_ms if now_ms is not None else time.time() * 1000)
|
||||||
|
return max(0, int((close_at - now) / 1000))
|
||||||
|
|
||||||
|
|
||||||
|
def format_time_close_countdown(seconds: Any) -> str:
|
||||||
|
try:
|
||||||
|
sec = max(0, int(seconds))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return "--:--:--"
|
||||||
|
h = sec // 3600
|
||||||
|
m = (sec % 3600) // 60
|
||||||
|
s = sec % 60
|
||||||
|
return f"{h:02d}:{m:02d}:{s:02d}"
|
||||||
|
|
||||||
|
|
||||||
|
def time_close_label(hours: Any) -> str:
|
||||||
|
h = normalize_time_close_hours(hours)
|
||||||
|
return f"时间平仓 {h}h" if h else "时间平仓"
|
||||||
|
|
||||||
|
|
||||||
|
def apply_time_close_to_payload(payload: dict[str, Any], row: Any, *, now_ms: Optional[int] = None) -> None:
|
||||||
|
enabled, hours, close_at_ms = time_close_settings_from_row(row)
|
||||||
|
payload["time_close_enabled"] = bool(enabled)
|
||||||
|
payload["time_close_hours"] = hours
|
||||||
|
payload["time_close_at_ms"] = close_at_ms
|
||||||
|
payload["time_close_label"] = time_close_label(hours) if enabled else ""
|
||||||
|
if enabled and close_at_ms:
|
||||||
|
rem = time_close_remaining_seconds(close_at_ms, now_ms=now_ms)
|
||||||
|
payload["time_close_remaining_sec"] = rem
|
||||||
|
payload["time_close_countdown"] = format_time_close_countdown(rem)
|
||||||
|
else:
|
||||||
|
payload["time_close_remaining_sec"] = None
|
||||||
|
payload["time_close_countdown"] = ""
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_time_close_schema(cursor) -> None:
|
||||||
|
ddl_list = (
|
||||||
|
"ALTER TABLE order_monitors ADD COLUMN time_close_enabled INTEGER DEFAULT 0",
|
||||||
|
"ALTER TABLE order_monitors ADD COLUMN time_close_hours INTEGER",
|
||||||
|
"ALTER TABLE order_monitors ADD COLUMN time_close_at_ms INTEGER",
|
||||||
|
"ALTER TABLE key_monitors ADD COLUMN time_close_enabled INTEGER DEFAULT 0",
|
||||||
|
"ALTER TABLE key_monitors ADD COLUMN time_close_hours INTEGER",
|
||||||
|
)
|
||||||
|
for ddl in ddl_list:
|
||||||
|
try:
|
||||||
|
cursor.execute(ddl)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def time_close_insert_values(
|
||||||
|
enabled: int,
|
||||||
|
hours: Optional[int],
|
||||||
|
opened_at_ms: Optional[int],
|
||||||
|
) -> tuple[int, Optional[int], Optional[int]]:
|
||||||
|
en = 1 if int(enabled or 0) != 0 and hours else 0
|
||||||
|
h = normalize_time_close_hours(hours) if en else None
|
||||||
|
close_at = compute_close_at_ms(opened_at_ms, h) if en else None
|
||||||
|
return en, h, close_at
|
||||||
Reference in New Issue
Block a user