关键位监控
This commit is contained in:
@@ -116,6 +116,9 @@ AI_MODEL=huihui_ai/deepseek-r1-abliterated:latest
|
||||
# ORDER_CHART_LIMIT=100
|
||||
# ORDER_CHART_DIR=static/images/order_charts
|
||||
# DAILY_OPEN_ALERT_THRESHOLD=5
|
||||
# 关键位:标准方案止损外侧%、趋势单方案止损外侧%(默认 0.5 / 1)
|
||||
# KEY_STOP_OUTSIDE_BREAKOUT_PCT=0.5
|
||||
# KEY_TREND_STOP_OUTSIDE_PCT=1
|
||||
# 以损定仓(按交易账户资金的百分比)
|
||||
# RISK_PERCENT=2
|
||||
# 移动保本触发(达到多少R触发)与偏移(百分比)
|
||||
|
||||
+97
-27
@@ -44,6 +44,15 @@ from fib_key_monitor_lib import (
|
||||
key_signal_type_for_trade_record,
|
||||
stored_key_signal_type,
|
||||
)
|
||||
from key_sl_tp_lib import (
|
||||
breakeven_enabled_from_row,
|
||||
normalize_sl_tp_mode,
|
||||
parse_breakeven_enabled_form,
|
||||
plan_key_sl_tp,
|
||||
sl_tp_mode_from_row,
|
||||
sl_tp_mode_label,
|
||||
sl_tp_plan_summary_text,
|
||||
)
|
||||
from history_window_lib import (
|
||||
PRESET_CUSTOM,
|
||||
PRESET_UTC_LAST24H,
|
||||
@@ -166,6 +175,8 @@ ORDER_MONITOR_TYPE_KEY_AUTO = "关键位监控"
|
||||
KEY_MONITOR_AUTO_TYPES = frozenset({"箱体突破", "收敛突破"})
|
||||
KEY_MONITOR_ALERT_ONLY_TYPES = frozenset({"关键阻力位", "关键支撑位"})
|
||||
KEY_AUTO_MIN_PLANNED_RR = float(os.getenv("KEY_AUTO_MIN_PLANNED_RR", "1.5"))
|
||||
KEY_STOP_OUTSIDE_BREAKOUT_PCT = float(os.getenv("KEY_STOP_OUTSIDE_BREAKOUT_PCT", "0.5"))
|
||||
KEY_TREND_STOP_OUTSIDE_PCT = float(os.getenv("KEY_TREND_STOP_OUTSIDE_PCT", "1"))
|
||||
KEY_DAILY_VOLUME_RANK_MAX = int(os.getenv("KEY_DAILY_VOLUME_RANK_MAX", "30"))
|
||||
KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT = os.getenv("KEY_SIZING_USE_ZERO_POSITION_SNAPSHOT", "true").lower() in (
|
||||
"1",
|
||||
@@ -1223,6 +1234,9 @@ def init_db():
|
||||
"ALTER TABLE key_monitors ADD COLUMN fib_order_amount REAL",
|
||||
"ALTER TABLE key_monitors ADD COLUMN fib_margin_capital REAL",
|
||||
"ALTER TABLE key_monitors ADD COLUMN fib_leverage INTEGER",
|
||||
"ALTER TABLE key_monitors ADD COLUMN sl_tp_mode TEXT DEFAULT 'standard'",
|
||||
"ALTER TABLE key_monitors ADD COLUMN manual_take_profit REAL",
|
||||
"ALTER TABLE key_monitors ADD COLUMN breakeven_enabled INTEGER DEFAULT 0",
|
||||
):
|
||||
try:
|
||||
c.execute(ddl)
|
||||
@@ -2985,6 +2999,21 @@ def _key_hard_checks(symbol, direction, upper, lower, monitor_type):
|
||||
return out
|
||||
|
||||
|
||||
def _key_plan_sl_tp_for_row(row, direction, upper, lower, checks):
|
||||
mode = sl_tp_mode_from_row(row, "standard")
|
||||
manual_tp = _sqlite_row_val(row, "manual_take_profit")
|
||||
return plan_key_sl_tp(
|
||||
mode,
|
||||
direction,
|
||||
upper,
|
||||
lower,
|
||||
checks,
|
||||
outside_pct=KEY_STOP_OUTSIDE_BREAKOUT_PCT,
|
||||
trend_outside_pct=KEY_TREND_STOP_OUTSIDE_PCT,
|
||||
manual_take_profit=manual_tp,
|
||||
), mode
|
||||
|
||||
|
||||
def calc_price_diff_pct(current_price, target_price):
|
||||
try:
|
||||
if target_price is None:
|
||||
@@ -3238,7 +3267,7 @@ def _insert_order_monitor_from_fib_fill(
|
||||
breakeven_step_r,
|
||||
0,
|
||||
breakeven_price,
|
||||
1,
|
||||
1 if breakeven_enabled_from_row(row, 0) else 0,
|
||||
notional_value,
|
||||
position_ratio,
|
||||
base_amount,
|
||||
@@ -3399,7 +3428,7 @@ def check_fib_key_monitors():
|
||||
conn.close()
|
||||
|
||||
|
||||
def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px):
|
||||
def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=0):
|
||||
if _fib_key_exists_for_symbol(conn, symbol):
|
||||
return False, f"{symbol} 已有斐波监控(同币仅允许一条 0.618/0.786)"
|
||||
ratio = fib_ratio_from_type(mt)
|
||||
@@ -3459,12 +3488,13 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px):
|
||||
return False, "交易所未返回限价单 ID"
|
||||
except Exception as e:
|
||||
return False, friendly_okx_error(e, available_usdt=available_usdt)
|
||||
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) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
"fib_order_amount, fib_margin_capital, fib_leverage, breakeven_enabled) "
|
||||
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
(
|
||||
symbol,
|
||||
mt,
|
||||
@@ -3478,6 +3508,7 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px):
|
||||
float(amount),
|
||||
margin_capital,
|
||||
leverage,
|
||||
be_flag,
|
||||
),
|
||||
)
|
||||
return True, None
|
||||
@@ -3532,21 +3563,10 @@ def check_key_monitors():
|
||||
risk_tip = None
|
||||
if (direction == "long" and coin4h_status == "空头") or (direction == "short" and coin4h_status == "多头"):
|
||||
risk_tip = "当前信号与本币4h(EMA55)主趋势逆势,建议降低仓位并严格执行止损。"
|
||||
box_h = abs(float(up) - float(low)) if up is not None and low is not None else 0.0
|
||||
c_close = float(checks.get("confirm_close") or 0)
|
||||
b_high = float(checks.get("breakout_high") or 0)
|
||||
b_low = float(checks.get("breakout_low") or 0)
|
||||
key_price = float(low) if direction == "long" else float(up)
|
||||
if direction == "long":
|
||||
tp1 = c_close + box_h
|
||||
tp2 = c_close + box_h * 1.5
|
||||
sl1 = b_low * (1 - 0.002) if b_low > 0 else None
|
||||
sl2 = key_price * (1 - 0.002) if key_price > 0 else None
|
||||
else:
|
||||
tp1 = c_close - box_h
|
||||
tp2 = c_close - box_h * 1.5
|
||||
sl1 = b_high * (1 + 0.002) if b_high > 0 else None
|
||||
sl2 = key_price * (1 + 0.002) if key_price > 0 else None
|
||||
sl_tp_mode = sl_tp_mode_from_row(r, "standard")
|
||||
be_on = breakeven_enabled_from_row(r, 0)
|
||||
plan_tuple, _mode = _key_plan_sl_tp_for_row(r, direction, up, low, checks)
|
||||
hard_lines = [
|
||||
f"量能:{'通过' if checks['vol_ok'] else '不通过'}(突破K量 {round(checks['vol_break'], 4)} / 前20均量 {round(checks['avg20'], 4)},阈值1.3x)",
|
||||
f"突破价位:{'通过' if checks['breakout_ok'] else '不通过'}(突破K收盘 {round(float(checks['breakout_close']), 8)},关键位 {checks['edge_price']})",
|
||||
@@ -3554,10 +3574,25 @@ def check_key_monitors():
|
||||
f"第二根确认:{'通过' if checks['confirm_ok'] else '不通过'}(确认收盘 {checks['confirm_close']},关键位 {checks['edge_price']})",
|
||||
f"日成交量排名:{'通过' if checks['rank_ok'] else '不通过'}({checks['rank']}/{checks['rank_total']},要求前30)",
|
||||
]
|
||||
op_lines = [
|
||||
f"方案A:止盈=箱体1.0倍({round(tp1, 8) if tp1 else '-' }),止损=突破K极值外0.2%({round(sl1, 8) if sl1 else '-' })",
|
||||
f"方案B:止盈=箱体1.5倍({round(tp2, 8) if tp2 else '-' }),止损=箱体关键位外0.2%({round(sl2, 8) if sl2 else '-' })",
|
||||
]
|
||||
if plan_tuple:
|
||||
E, sl_raw, tp_raw, box_h = plan_tuple
|
||||
planned_rr = calc_rr_ratio(direction, E, sl_raw, tp_raw)
|
||||
rr_txt = f"{planned_rr:.4f}" if planned_rr is not None else "-"
|
||||
op_lines = [
|
||||
f"录入方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_on else '关'}",
|
||||
sl_tp_plan_summary_text(
|
||||
sl_tp_mode, direction, E, sl_raw, tp_raw, box_h,
|
||||
outside_pct=KEY_STOP_OUTSIDE_BREAKOUT_PCT,
|
||||
trend_outside_pct=KEY_TREND_STOP_OUTSIDE_PCT,
|
||||
),
|
||||
f"计划 SL:`{round(sl_raw, 8)}`|计划 TP:`{round(tp_raw, 8)}`|计划 RR(E):{rr_txt}:1",
|
||||
"说明:OKX 本实例为提醒模式,不自动市价开仓;请按方案自行下单。",
|
||||
]
|
||||
else:
|
||||
op_lines = [
|
||||
f"录入方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_on else '关'}",
|
||||
"计划 SL/TP 几何无效,请检查上下沿或趋势单止盈价。",
|
||||
]
|
||||
trigger_time = ms_to_app_local_str(int(checks["confirm_ts"])) if checks.get("confirm_ts") else app_now_str()
|
||||
msg = build_wechat_key_monitor_message(
|
||||
symbol=sym,
|
||||
@@ -4601,22 +4636,57 @@ def add_key():
|
||||
lw = round_price_to_exchange(ex_sym_key, float(d["lower"]))
|
||||
upper_px = float(uh) if uh is not None else float(d["upper"])
|
||||
lower_px = float(lw) if lw is not None else float(d["lower"])
|
||||
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
|
||||
if is_fib_key_monitor_type(mt):
|
||||
ok_fib, err_fib = _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px)
|
||||
ok_fib, err_fib = _add_fib_key_monitor(
|
||||
conn, symbol, direction_sel, mt, upper_px, lower_px, breakeven_enabled=be_flag,
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
if not ok_fib:
|
||||
flash(err_fib or "斐波监控添加失败")
|
||||
return redirect("/")
|
||||
flash(f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})")
|
||||
flash(
|
||||
f"斐波监控已添加,限价单已挂出({symbol} 日成交量排名 {rank}/{total})"
|
||||
f"|移动保本:{'开' if be_flag else '关'}"
|
||||
)
|
||||
return redirect("/")
|
||||
sl_tp_mode = "standard"
|
||||
manual_tp = None
|
||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||
sl_tp_mode = normalize_sl_tp_mode(d.get("sl_tp_mode"))
|
||||
if sl_tp_mode == "trend_manual":
|
||||
try:
|
||||
manual_tp = float(d.get("manual_take_profit") or 0)
|
||||
except (TypeError, ValueError):
|
||||
manual_tp = 0
|
||||
if manual_tp <= 0:
|
||||
conn.close()
|
||||
flash("趋势单方案须填写有效止盈价")
|
||||
return redirect("/")
|
||||
if direction_sel == "long" and manual_tp <= upper_px:
|
||||
conn.close()
|
||||
flash("做多趋势单:止盈价应高于上沿(阻力)")
|
||||
return redirect("/")
|
||||
if direction_sel == "short" and manual_tp >= lower_px:
|
||||
conn.close()
|
||||
flash("做空趋势单:止盈价应低于下沿(支撑)")
|
||||
return redirect("/")
|
||||
mtpx = round_price_to_exchange(ex_sym_key, manual_tp)
|
||||
if mtpx is not None:
|
||||
manual_tp = float(mtpx)
|
||||
conn.execute(
|
||||
"INSERT INTO key_monitors (symbol,monitor_type,direction,upper,lower) VALUES (?,?,?,?,?)",
|
||||
(symbol, mt, direction_sel, upper_px, lower_px),
|
||||
"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),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total})")
|
||||
extra = ""
|
||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||
extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}"
|
||||
flash(f"添加成功({symbol} 日成交量排名 {rank}/{total}){extra}")
|
||||
return redirect("/")
|
||||
|
||||
@app.route("/add_order", methods=["POST"])
|
||||
|
||||
@@ -208,6 +208,15 @@
|
||||
</select>
|
||||
<input name="upper" step="0.0001" placeholder="上沿/阻力" required>
|
||||
<input name="lower" step="0.0001" placeholder="下沿/支撑" required>
|
||||
<select name="sl_tp_mode" id="key-sl-tp-mode" title="止盈止损方案">
|
||||
<option value="standard">标准突破</option>
|
||||
<option value="box_1p5">箱体1R·止盈1.5H</option>
|
||||
<option value="trend_manual">趋势单·自填止盈</option>
|
||||
</select>
|
||||
<input name="manual_take_profit" id="key-manual-tp" step="0.0001" placeholder="趋势单止盈价" style="display:none">
|
||||
<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"> 移动保本
|
||||
</label>
|
||||
<button type="submit">添加</button>
|
||||
</form>
|
||||
<div class="rule-tip">{{ key_gate_rule_text }}</div>
|
||||
@@ -219,6 +228,8 @@
|
||||
上:{{ k.upper }} 下:{{ k.lower }}
|
||||
{% if k.fib_entry_price %}| 挂E:{{ k.fib_entry_price }}{% endif %}
|
||||
| 已提醒:{{ k.notification_count or 0 }}/{{ k.max_notify or 3 }}
|
||||
{% if k.monitor_type in ['箱体突破','收敛突破'] %}| 方案:{{ '标准' if (k.sl_tp_mode or 'standard') == 'standard' else ('1.5H' if k.sl_tp_mode == 'box_1p5' else '趋势') }}{% endif %}
|
||||
| 保本:{{ '开' if k.breakeven_enabled else '关' }}
|
||||
| 现价:<span id="key-price-{{ k.id }}">-</span>
|
||||
| 距上沿:<span id="key-up-diff-{{ k.id }}">-</span>
|
||||
| 距下沿:<span id="key-low-diff-{{ k.id }}">-</span>
|
||||
@@ -1168,6 +1179,31 @@ if(journalForm){
|
||||
});
|
||||
}
|
||||
|
||||
function syncKeyMonitorFormFields(){
|
||||
const typeEl = document.querySelector('#key-form [name="type"]');
|
||||
const modeEl = document.getElementById("key-sl-tp-mode");
|
||||
const manualTp = document.getElementById("key-manual-tp");
|
||||
const beWrap = document.getElementById("key-breakeven-wrap");
|
||||
if(!typeEl) return;
|
||||
const t = (typeEl.value || "").trim();
|
||||
const autoTypes = new Set(["箱体突破","收敛突破"]);
|
||||
const fibTypes = new Set(["斐波回调0.618","斐波回调0.786"]);
|
||||
const showAuto = autoTypes.has(t);
|
||||
const showBe = showAuto || fibTypes.has(t);
|
||||
if(modeEl) modeEl.style.display = showAuto ? "" : "none";
|
||||
if(manualTp){
|
||||
const trend = showAuto && modeEl && modeEl.value === "trend_manual";
|
||||
manualTp.style.display = trend ? "" : "none";
|
||||
manualTp.required = !!trend;
|
||||
}
|
||||
if(beWrap) beWrap.style.display = showBe ? "inline-flex" : "none";
|
||||
}
|
||||
const keyTypeSel = document.querySelector('#key-form [name="type"]');
|
||||
const keyModeSel = document.getElementById("key-sl-tp-mode");
|
||||
if(keyTypeSel) keyTypeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||
if(keyModeSel) keyModeSel.addEventListener("change", syncKeyMonitorFormFields);
|
||||
syncKeyMonitorFormFields();
|
||||
|
||||
const keyForm = document.getElementById("key-form");
|
||||
if(keyForm){
|
||||
keyForm.addEventListener("submit", (e)=>{
|
||||
|
||||
Reference in New Issue
Block a user