diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 62a3f4b..c642488 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -4163,7 +4163,6 @@ def _process_key_rs_level_alert(conn, row): notify_index=notify_index, notify_max=max_n, interval_min=interval, - extra_note="OKX 本实例为提醒模式,不自动市价开仓", ) send_wechat_msg(msg) conn.execute( @@ -4639,6 +4638,196 @@ def _add_fib_key_monitor(conn, symbol, direction_sel, mt, upper_px, lower_px, br return True, None +def _market_open_for_key_monitor( + conn, + symbol, + direction, + exchange_symbol, + stop_loss, + take_profit, + key_signal_type=None, + breakeven_enabled=0, +): + """ + 与手动「实盘下单」对齐的市价开仓与 order_monitors 写入(OKX 永续)。 + 返回 (ok: bool, err_msg: Optional[str], detail: Optional[dict]) + """ + now = app_now() + ok, reason = precheck_risk(conn, symbol, direction) + if not ok: + return False, f"风控拒绝下单:{reason}", None + ok_live, reason_live = ensure_exchange_live_ready() + if not ok_live: + return False, reason_live, None + + default_leverage = get_synced_leverage(exchange_symbol, direction) or infer_leverage(symbol) + leverage = int(default_leverage) if default_leverage else 5 + if leverage <= 0: + leverage = 5 + + trading_day = get_trading_day(now) + opens_today_before = conn.execute( + "SELECT COUNT(*) FROM order_monitors WHERE session_date=?", + (trading_day,), + ).fetchone()[0] + session_row = ensure_session(conn, trading_day) + _, trading_capital_live = get_exchange_capitals(force=True) + live_capital = float(trading_capital_live) if trading_capital_live is not None else float(session_row["current_capital"]) + capital_base = resolve_capital_base_for_key_open(conn, trading_day, live_capital) + + trade_style = (DEFAULT_TRADE_STYLE or "trend").strip().lower() + if trade_style not in ("trend", "swing"): + trade_style = "trend" + + available_usdt = get_available_trading_usdt() + live_price = get_price(symbol) + if live_price is None: + return False, "获取交易所实时价格失败(以损定仓需要当前价)", None + try: + ensure_markets_loaded() + except Exception: + pass + lp_r = round_price_to_exchange(exchange_symbol, live_price) + if lp_r is not None: + live_price = lp_r + + sl_adj = round_price_to_exchange(exchange_symbol, float(stop_loss)) + tp_adj = round_price_to_exchange(exchange_symbol, float(take_profit)) + if sl_adj is not None: + stop_loss = float(sl_adj) + if tp_adj is not None: + take_profit = float(tp_adj) + + risk_fraction = calc_risk_fraction(direction, live_price, stop_loss) + if risk_fraction is None: + return False, "止损方向不合法(相对当前市价);请核对上下沿与方向", None + risk_percent = max(0.01, float(RISK_PERCENT)) + risk_amount = round(capital_base * risk_percent / 100.0, 4) + notional_value = round(risk_amount / risk_fraction, 4) + margin_capital = round(notional_value / leverage, 4) + + if capital_base and margin_capital > capital_base: + return False, "以损定仓后保证金超过当前交易资金", None + + if available_usdt is not None: + max_margin = round(max(available_usdt * FULL_MARGIN_BUFFER_RATIO, 0), 4) + if margin_capital > max_margin: + return ( + False, + f"保证金不足:交易账户可用约 {round(available_usdt, 2)}U,当前最多建议 {round(max_margin, 2)}U", + None, + ) + + position_ratio = round(margin_capital / capital_base * 100, 2) if capital_base else 0 + + try: + amount, quote_price = prepare_order_amount(exchange_symbol, margin_capital, leverage, live_price) + contract_size = get_contract_size(exchange_symbol) + base_amount = round(float(amount) * contract_size, 8) + order_resp = place_exchange_order( + exchange_symbol, direction, amount, leverage, + stop_loss=stop_loss, take_profit=take_profit, + ) + open_order_id = order_resp.get("id", "") + tpsl_attached = bool(order_resp.get("tpsl_attached")) + trigger_price = resolve_order_entry_price(order_resp, exchange_symbol, quote_price) + except Exception as e: + return False, friendly_okx_error(e, available_usdt=available_usdt), None + + trigger_price = round_price_to_exchange(exchange_symbol, trigger_price) + stop_loss = round_price_to_exchange(exchange_symbol, stop_loss) + take_profit = round_price_to_exchange(exchange_symbol, take_profit) + + opened_at_bj = app_now_str() + opened_at_ms = _to_ms_with_fallback(None, opened_at_bj) + + planned_rr = calc_rr_ratio(direction, trigger_price, stop_loss, take_profit) + breakeven_rr_trigger = float(BREAKEVEN_RR_TRIGGER) + breakeven_offset_pct = float(BREAKEVEN_OFFSET_PCT) + breakeven_step_r = float(BREAKEVEN_STEP_R) if float(BREAKEVEN_STEP_R) > 0 else 1.0 + risk_amount_final = calc_risk_amount_from_plan(direction, trigger_price, stop_loss, margin_capital, leverage) + if risk_amount_final is None: + risk_amount_final = risk_amount + else: + try: + risk_amount_final = round(float(risk_amount_final), 4) + except (TypeError, ValueError): + risk_amount_final = risk_amount + + if direction == "short": + breakeven_raw = float(trigger_price) * (1 - breakeven_offset_pct / 100.0) + else: + breakeven_raw = float(trigger_price) * (1 + breakeven_offset_pct / 100.0) + breakeven_price = round_price_to_exchange(exchange_symbol, breakeven_raw) + be_enabled = 1 if int(breakeven_enabled or 0) != 0 else 0 + + 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), + ), + ) + new_order_id = int(conn.execute("SELECT last_insert_rowid()").fetchone()[0]) + opens_today_after = conn.execute( + "SELECT COUNT(*) FROM order_monitors WHERE session_date=?", + (trading_day,), + ).fetchone()[0] + + return True, None, { + "new_order_id": new_order_id, + "open_order_id": open_order_id, + "trigger_price": trigger_price, + "planned_rr_fill": planned_rr, + "risk_amount_final": risk_amount_final, + "margin_capital": margin_capital, + "leverage": leverage, + "amount": amount, + "base_amount": base_amount, + "notional_value": notional_value, + "position_ratio": position_ratio, + "tpsl_attached": tpsl_attached, + "opens_today_before": opens_today_before, + "opens_today_after": opens_today_after, + "trading_day": trading_day, + "risk_percent": risk_percent, + "breakeven_rr_trigger": breakeven_rr_trigger, + "breakeven_price": breakeven_price, + "capital_base_at_open": capital_base, + } + + def can_notify_key_monitor(row, now_dt): max_notify = int(row["max_notify"] or KEY_ALERT_MAX_TIMES) if int(row["notification_count"] or 0) >= max_notify: @@ -4664,7 +4853,7 @@ def breakout_too_far(p, edge_price, limit_pct): return False -# 关键位监控(箱体/收敛:硬门控后提醒;阻力/支撑:5m 双向突破 + 三次提醒) +# 关键位监控(箱体/收敛可自动开仓;阻力/支撑为双向 5m 收盘突破 + 三次提醒) def check_key_monitors(): conn = get_db() rows = conn.execute("SELECT * FROM key_monitors").fetchall() @@ -4679,85 +4868,175 @@ def check_key_monitors(): except Exception as e: print(f"[key_rs_level_alert] {sym} id={r['id']}: {e}") continue - if typ not in KEY_MONITOR_AUTO_TYPES: - continue + direction = (r["direction"] or "long").lower() if direction == KEY_DIRECTION_WATCH: continue - now_dt = app_now() - if not can_notify_key_monitor(r, now_dt): - continue try: checks = _key_hard_checks(sym, direction, up, low, typ) except Exception: checks = {"ok": False} if not checks.get("ok"): continue + btc8h_status, _, _ = _status_by_ema55("BTC/USDT", "8h") coin4h_status, _, _ = _status_by_ema55(sym, "4h") risk_tip = None if (direction == "long" and coin4h_status == "空头") or (direction == "short" and coin4h_status == "多头"): risk_tip = "当前信号与本币4h(EMA55)主趋势逆势,建议降低仓位并严格执行止损。" + key_price = float(low) if direction == "long" else float(up) - 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 = _key_hard_lines_from_checks(checks) - 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 = format_wechat_scalar_2dp(planned_rr) 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:`{format_wechat_scalar_2dp(sl_raw)}`|计划 TP:`{format_price_for_symbol(sym, tp_raw)}`|计划 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, - direction=direction, - monitor_type=typ, - trigger_time=trigger_time, - key_price=key_price, - confirm_close=checks["confirm_close"], - hard_lines=hard_lines, - btc8h_status=btc8h_status, - coin4h_status=coin4h_status, - swing4h_pct=checks.get("swing4h_pct") or 0.0, - op_lines=op_lines, - risk_tip=risk_tip, - ) - send_wechat_msg(msg) - new_count = int(r["notification_count"] or 0) + 1 - max_n = int(r["max_notify"] or KEY_ALERT_MAX_TIMES) - conn.execute( - "UPDATE key_monitors SET notification_count = ?, last_notified_at = ? WHERE id = ?", - (new_count, app_now_str(), r["id"]), - ) - if new_count >= max_n: - insert_key_monitor_history(conn, r, new_count, msg, "alerts_complete") - conn.execute("DELETE FROM key_monitors WHERE id = ?", (r["id"],)) - send_wechat_msg( - "\n".join( - [ - f"# 🧾 {r['symbol']} 关键位监控结束", - f"**账户:{_wechat_account_label()}**", - "", - f"- 原因:已满 {max_n} 次提醒", - "- 状态:已自动结束并记入历史", - ] - ) + + if typ not in KEY_MONITOR_AUTO_TYPES: + continue + + plan_tuple, sl_tp_mode = _key_plan_sl_tp_for_row(r, direction, up, low, checks) + if not plan_tuple: + fmt_rr = "无法计算(止损/止盈与确认价几何关系无效)" + rr_msg = ( + f"# ⚠️ {sym} 关键位自动单:计划无效\n" + f"**账户:{_wechat_account_label()}**\n" + f"- 类型:{typ}|方案:{sl_tp_mode_label(sl_tp_mode)}\n" + f"- 方向:**{_wechat_direction_text(direction)}**\n" + f"- 触发时间:`{trigger_time}`\n" + f"- 确认K收盘(E):`{format_price_for_symbol(sym, checks.get('confirm_close'))}`\n" + f"- **{fmt_rr}**(未开仓)\n" + "---\n" + "### 硬条件\n" + + "\n".join(f"- {x}" for x in hard_lines) ) + if risk_tip: + rr_msg += f"\n---\n### 逆势风险提示\n- {risk_tip}" + send_wechat_msg(rr_msg) + _finalize_key_monitor_one_shot(conn, r, rr_msg, "rr_insufficient") + continue + E, sl_raw, tp_raw, box_h = plan_tuple + exchange_symbol = normalize_okx_symbol(sym) + try: + ensure_markets_loaded() + except Exception: + pass + sl_px = round_price_to_exchange(exchange_symbol, sl_raw) + tp_px = round_price_to_exchange(exchange_symbol, tp_raw) + if sl_px is not None: + sl_raw = float(sl_px) + if tp_px is not None: + tp_raw = float(tp_px) + + planned_rr = calc_rr_ratio(direction, E, sl_raw, tp_raw) + rr_ok = planned_rr is not None and planned_rr > KEY_AUTO_MIN_PLANNED_RR + + if not rr_ok: + fmt_rr = f"{planned_rr:.4f}" if planned_rr is not None else "无法计算(止损/止盈与确认价几何关系无效)" + plan_line = 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, + ) + rr_msg = ( + f"# ⚠️ {sym} 关键位自动单:计划 RR 未达标\n" + f"**账户:{_wechat_account_label()}**\n" + f"- 类型:{typ}|{plan_line}\n" + f"- 方向:**{_wechat_direction_text(direction)}**\n" + f"- 触发时间:`{trigger_time}`\n" + f"- 确认K收盘(E):`{format_price_for_symbol(sym, E)}`\n" + f"- 箱体高 H:`{format_price_for_symbol(sym, box_h)}`\n" + f"- 计划止损:`{format_wechat_scalar_2dp(sl_raw)}`\n" + f"- 计划止盈:`{format_price_for_symbol(sym, tp_raw)}`\n" + f"- **计划 RR(按确认收盘 E):{fmt_rr} : 1**(要求 **>{KEY_AUTO_MIN_PLANNED_RR}:1**,未开仓)\n" + "---\n" + "### 硬条件\n" + + "\n".join(f"- {x}" for x in hard_lines) + ) + if risk_tip: + rr_msg += f"\n---\n### 逆势风险提示\n- {risk_tip}" + send_wechat_msg(rr_msg) + _finalize_key_monitor_one_shot(conn, r, rr_msg, "rr_insufficient") + continue + + key_sig = typ if typ in KEY_MONITOR_AUTO_TYPES else None + be_on = breakeven_enabled_from_row(r, 0) + ok_trade, trade_err, det = _market_open_for_key_monitor( + conn, + sym, + direction, + exchange_symbol, + sl_raw, + tp_raw, + key_signal_type=key_sig, + breakeven_enabled=1 if be_on else 0, + ) + planned_rr_txt = ( + format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-" + ) + if not ok_trade: + fail_msg = ( + f"# ❌ {sym} 关键位自动单失败\n" + f"**账户:{_wechat_account_label()}**\n" + f"- 类型:{typ}\n" + f"- 方向:**{_wechat_direction_text(direction)}**\n" + f"- 触发时间:`{trigger_time}`\n" + f"- 确认K收盘(E):`{format_price_for_symbol(sym, E)}`\n" + f"- 计划止损:`{format_wechat_scalar_2dp(sl_raw)}`\n" + f"- 计划止盈:`{format_price_for_symbol(sym, tp_raw)}`\n" + f"- **计划 RR(按 E):{planned_rr_txt} : 1**(已通过 RR 阈值)\n" + f"- **失败原因:{trade_err}**\n" + "---\n" + "### 硬条件\n" + + "\n".join(f"- {x}" for x in hard_lines) + ) + if risk_tip: + fail_msg += f"\n---\n### 逆势风险提示\n- {risk_tip}" + send_wechat_msg(fail_msg) + _finalize_key_monitor_one_shot(conn, r, fail_msg, "exchange_failed") + continue + + tpsl_txt = ( + "已在交易所挂止盈/止损触发单(OKX 条件单)" + if det.get("tpsl_attached") + else "⚠️ 条件单挂接状态异常或未挂上" + ) + rr_fill = det.get("planned_rr_fill") + rr_fill_txt = format_wechat_scalar_2dp(rr_fill) if rr_fill is not None else "-" + + succ_msg_lines = [ + f"# ✅ {sym} 关键位自动开仓成功", + f"**账户:{_wechat_account_label()}**", + f"- **来源:**{ORDER_MONITOR_TYPE_KEY_AUTO}(市价)", + f"- 页面订单 ID:**{det['new_order_id']}**", + f"- 交易所订单 ID:`{det.get('open_order_id') or '-'}`", + f"- 类型:{typ}|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_on else '关'}", + f"- 方向:**{_wechat_direction_text(direction)}**", + f"- 触发时间:`{trigger_time}`", + f"- 确认K收盘(E):{format_price_for_symbol(sym, E)}(RR 阈值按此计价)", + f"- **计划 RR(E):{planned_rr_txt}:1**", + f"- 开仓成交价:**{format_price_for_symbol(sym, det['trigger_price'])}**", + f"- **成交价侧计划 RR:**{rr_fill_txt}:1", + f"- 止损:{format_wechat_scalar_2dp(sl_raw)}", + f"- 止盈:{format_price_for_symbol(sym, tp_raw)}", + f"- 风险:{det.get('risk_percent')}%≈{format_wechat_scalar_2dp(det.get('risk_amount_final'))}U|基数 {format_wechat_scalar_2dp(det.get('margin_capital'))}U|杠杆 {det.get('leverage')}x", + f"- 名义 {format_wechat_scalar_2dp(det.get('notional_value'))}U|张数 {format_wechat_scalar_2dp(det.get('amount'))}|折算标的 {det.get('base_amount')}", + f"- **{tpsl_txt}**", + f"- 保本触发:{det.get('breakeven_rr_trigger')}R→{format_price_for_symbol(sym, det.get('breakeven_price'))}", + f"- 当日开仓次数:**{det.get('opens_today_after')}** / {DAILY_OPEN_ALERT_THRESHOLD}(提醒阈值)", + ] + succ_msg_lines.extend(["---", "### 硬条件"] + [f"- {x}" for x in hard_lines]) + if risk_tip: + succ_msg_lines.extend(["---", "### 逆势风险提示", f"- {risk_tip}"]) + succ_msg = "\n".join(succ_msg_lines) + send_wechat_msg(succ_msg) + _finalize_key_monitor_one_shot(conn, r, succ_msg, "auto_opened") + + if det.get("opens_today_before", 0) < DAILY_OPEN_ALERT_THRESHOLD <= det.get("opens_today_after", 0): + advice = ai_short_advice( + f"用户在北京时间交易日 {det['trading_day']} 已累计开仓 {det['opens_today_after']} 次(阈值 {DAILY_OPEN_ALERT_THRESHOLD})。" + f"最新一笔来源为关键位自动单:{sym} {direction},杠杆{det['leverage']}x。" + f"用户自述“上头了”。请给克制提醒。" + ) + if advice: + send_wechat_msg(f"【AI提醒】今日开仓次数已达 {det['opens_today_after']}\n{advice[:800]}") conn.commit() conn.close() @@ -5335,8 +5614,8 @@ def render_main_page(page="trade"): key_gate_rule_text = ( f"【箱体/收敛】{KLINE_TIMEFRAME} 两根闭合K|突破越过关键位 > {KEY_BREAKOUT_AMP_MIN_PCT}%|" f"确认K收于箱外|量能>前{KEY_VOLUME_MA_BARS}均量×{KEY_VOLUME_RATIO_MIN}|" - f"计划RR参考>{KEY_AUTO_MIN_PLANNED_RR}|日成交前{KEY_DAILY_VOLUME_RANK_MAX}|OKX 提醒模式不自动开仓|" - f"【阻力/支撑】填上/下沿,5m 收盘突破任一侧提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分),不选方向" + f"RR>{KEY_AUTO_MIN_PLANNED_RR}|日成交前{KEY_DAILY_VOLUME_RANK_MAX}|" + f"【阻力/支撑】填上/下沿,5m 收盘突破任一侧即提醒 {KEY_ALERT_MAX_TIMES} 次(间隔 {KEY_ALERT_INTERVAL_MINUTES} 分),不选方向、不自动开仓" ) strategy_extra = {} if page in ("strategy", "strategy_trend", "strategy_roll"): @@ -6216,6 +6495,14 @@ def add_key(): extra = "" if mt in KEY_MONITOR_AUTO_TYPES: extra = f"|方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'开' if be_flag else '关'}" + ctr = False + try: + coin4h_status, _, _ = _status_by_ema55(symbol, "4h") + ctr = (direction_sel == "long" and coin4h_status == "空头") or ( + direction_sel == "short" and coin4h_status == "多头" + ) + except Exception: + pass if mt in KEY_MONITOR_RS_TYPES: flash( f"添加成功({symbol} 日成交量排名 {rank}/{total})|阻力/支撑:双向监控上/下沿," @@ -6223,6 +6510,10 @@ def add_key(): ) else: flash(f"添加成功({symbol} 日成交量排名 {rank}/{total}){extra}") + if ctr and mt in KEY_MONITOR_AUTO_TYPES: + flash( + "⚠️ 4h EMA55 提示:当前与所选方向逆势;「箱体突破/收敛突破」在条件满足时仍会按计划自动市价开仓,请注意仓位。" + ) return redirect("/key_monitor") @app.route("/add_order", methods=["POST"]) diff --git a/crypto_monitor_okx/关键位自动下单说明.md b/crypto_monitor_okx/关键位自动下单说明.md index fb201f4..7218e11 100644 --- a/crypto_monitor_okx/关键位自动下单说明.md +++ b/crypto_monitor_okx/关键位自动下单说明.md @@ -1,7 +1,7 @@ # 关键位监控说明(自动开仓 + 人工盯盘) **适用:`crypto_monitor_okx`(OKX 永续)** -本实例 **箱体/收敛** 为微信提醒 + 自行下单,**不自动市价开仓**;阻力/支撑规则与 Binance/Gate 相同。共享逻辑见 `key_monitor_lib.py`。 +箱体/收敛与 Binance、Gate 相同:**门控通过后自动市价开仓**(须 `LIVE_TRADING_ENABLED=true`)。阻力/支撑仍为微信提醒。共享逻辑见 `key_monitor_lib.py`。 本文档与 `.env`、`check_key_monitors`、`add_key`、`_key_hard_checks`、`_process_key_rs_level_alert` 一致。 diff --git a/crypto_monitor_okx/更新文档.md b/crypto_monitor_okx/更新文档.md index 224c53b..bbbe57f 100644 --- a/crypto_monitor_okx/更新文档.md +++ b/crypto_monitor_okx/更新文档.md @@ -70,7 +70,7 @@ ## 与 Gate 的差异(其余) - 无独立「关键位监控」导航页(斐波在 **交易执行** 页添加)。 -- 箱体/收敛仍为 **提醒** 模式,不自动市价开仓(Gate/Binance 主站为自动开仓)。 +- 箱体/收敛与 Gate/Binance 相同:**门控 + RR 达标后自动市价开仓**(须 `LIVE_TRADING_ENABLED=true`)。 ## 交易所已实现盈亏(与 Gate 一致) diff --git a/关键位止盈止损与移动保本更新说明.md b/关键位止盈止损与移动保本更新说明.md index 5e25a61..42a9783 100644 --- a/关键位止盈止损与移动保本更新说明.md +++ b/关键位止盈止损与移动保本更新说明.md @@ -117,7 +117,7 @@ KEY_TREND_STOP_OUTSIDE_PCT=1 | 实例 | 箱体/收敛触发后 | |------|----------------| | **Binance / Gate** | 门控通过 → 按方案算 SL/TP → 市价开仓 → 挂交易所 TP/SL → 写入下单监控 | -| **OKX** | 门控通过 → **企业微信提醒**(推送中含录入方案的计划 SL/TP/RR),**不自动市价开仓** | +| **OKX** | 门控通过 → **自动市价开仓**(与 Gate/Binance 相同;须 `LIVE_TRADING_ENABLED=true`) | OKX 用户按推送中的计划价自行下单;斐波仍为限价 + 成交后挂 TP/SL(与原先一致)。 @@ -147,7 +147,7 @@ OKX 用户按推送中的计划价自行下单;斐波仍为限价 + 成交后 3. **验证 Binance/Gate** - 添加箱体突破,选「箱体1R·止盈1.5H」,不勾保本 → 触发后微信应显示方案名、保本关、SL/TP 符合 E±H / E±1.5H。 - 添加趋势单,填止盈,勾保本 → 成交后持仓卡片「移动保本:开」。 -4. **验证 OKX**:门控通过时微信应含「录入方案」与计划 SL/TP,并注明提醒模式不自动开仓。 +4. **验证 OKX**:门控通过且 RR 达标时应自动市价开仓;失败时微信说明 `exchange_failed` / `rr_insufficient`。 5. 旧关键位条目:列表应显示「方案:标准突破」「保本:关」(除非库中已有新字段值)。 --- @@ -159,6 +159,6 @@ OKX 用户按推送中的计划价自行下单;斐波仍为限价 + 成交后 | 计划 SL/TP | `plan_key_sl_tp()` in `key_sl_tp_lib.py` | | 按监控行计算 | `_key_plan_sl_tp_for_row()` in各 `app.py` | | 添加关键位 | `add_key()` | -| 箱体/收敛轮询 | `check_key_monitors()`(OKX 仅提醒) | +| 箱体/收敛轮询 | `check_key_monitors()`(四所共用自动开仓逻辑) | | 斐波添加 | `_add_fib_key_monitor(..., breakeven_enabled=)` | | 自动开仓写监控 | `_market_open_for_key_monitor(..., breakeven_enabled=)` |