feat(trend): 趋势回调保本移交下单监控并统一写交易记录
保本后结束趋势计划,持仓转入下单监控(备注趋势回调),交易所同时挂保本止损与计划止盈;中控或交易所平仓均经下单监控写入交易记录(trend_plan_id、开仓类型),四所共用 strategy_trend_register。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -60,7 +60,9 @@ from journal_chart_lib import (
|
||||
from hub_auth import request_allowed as hub_request_allowed
|
||||
from strategy_trade_labels import (
|
||||
STRATEGY_ENTRY_REASON_OPTIONS,
|
||||
handoff_trade_miss_reason,
|
||||
trade_record_monitor_type as resolve_trade_record_monitor_type,
|
||||
trend_plan_id_from_monitor_row,
|
||||
)
|
||||
from history_window_lib import (
|
||||
PRESET_CUSTOM,
|
||||
@@ -4151,6 +4153,7 @@ def reconcile_external_closes(conn, days=None):
|
||||
conn,
|
||||
symbol=r["symbol"],
|
||||
monitor_type=trade_record_monitor_type(conn, r),
|
||||
trend_plan_id=trend_plan_id_from_monitor_row(r),
|
||||
direction=r["direction"],
|
||||
trigger_price=r["trigger_price"],
|
||||
stop_loss=r["stop_loss"],
|
||||
@@ -4165,7 +4168,7 @@ def reconcile_external_closes(conn, days=None):
|
||||
planned_rr=calc_rr_ratio(r["direction"], r["trigger_price"], r["initial_stop_loss"] or r["stop_loss"], r["take_profit"]),
|
||||
actual_rr=calc_actual_rr(pnl_amount, r["risk_amount"]),
|
||||
result=result,
|
||||
miss_reason=miss_reason,
|
||||
miss_reason=handoff_trade_miss_reason(miss_reason, r),
|
||||
opened_at=opened_at,
|
||||
closed_at=closed_at,
|
||||
)
|
||||
@@ -4545,66 +4548,6 @@ def _trend_refresh_stop_only(exchange_symbol, direction, stop_loss):
|
||||
_gate_place_stop_loss_only_position(exchange_symbol, direction, stop_loss)
|
||||
|
||||
|
||||
def apply_trend_pullback_manual_breakeven(conn, row, offset_pct=None):
|
||||
"""运行中趋势计划:将交易所止损移至均价+偏移(默认 0.3%),仅当新止损更优时生效。"""
|
||||
if (row["status"] or "").strip() != "active":
|
||||
return False, "计划已结束"
|
||||
if not int(row["first_order_done"] or 0):
|
||||
return False, "尚未完成首仓,无法保本"
|
||||
avg_e = float(row["avg_entry_price"] or 0)
|
||||
if avg_e <= 0:
|
||||
return False, "缺少有效持仓均价"
|
||||
direction = (row["direction"] or "long").lower()
|
||||
ex_sym = row["exchange_symbol"] or normalize_exchange_symbol(row["symbol"])
|
||||
pos = get_live_position_contracts(ex_sym, direction)
|
||||
if pos is None or float(pos) <= 0:
|
||||
return False, "交易所当前无该方向持仓"
|
||||
new_sl_raw = calc_trend_manual_breakeven_stop(direction, avg_e, offset_pct)
|
||||
if new_sl_raw is None:
|
||||
return False, "保本价计算失败"
|
||||
new_sl = round_price_to_exchange(ex_sym, new_sl_raw)
|
||||
if new_sl is None:
|
||||
return False, "保本价经交易所精度舍入后无效"
|
||||
new_sl = float(new_sl)
|
||||
cur_sl = float(row["stop_loss"] or 0)
|
||||
if direction == "long":
|
||||
if new_sl <= cur_sl:
|
||||
return False, f"新止损 {new_sl} 未高于当前止损 {cur_sl}(多仓需上移)"
|
||||
else:
|
||||
if new_sl >= cur_sl:
|
||||
return False, f"新止损 {new_sl} 未低于当前止损 {cur_sl}(空仓需下移)"
|
||||
try:
|
||||
_trend_refresh_stop_only(ex_sym, direction, new_sl)
|
||||
except Exception as e:
|
||||
return False, friendly_exchange_error(e)
|
||||
now_s = app_now_str()
|
||||
conn.execute(
|
||||
"UPDATE trend_pullback_plans SET stop_loss=?, breakeven_applied=1, breakeven_applied_at=? WHERE id=?",
|
||||
(new_sl, now_s, row["id"]),
|
||||
)
|
||||
pct_used = float(
|
||||
offset_pct
|
||||
if offset_pct is not None
|
||||
else TREND_PULLBACK_MANUAL_BREAKEVEN_OFFSET_PCT
|
||||
)
|
||||
sym = row["symbol"]
|
||||
send_wechat_msg(
|
||||
"\n".join(
|
||||
[
|
||||
f"# ✅ {sym} 趋势回调手动保本",
|
||||
f"**账户:{_wechat_account_label()}**",
|
||||
f"- 计划 ID:**{row['id']}**",
|
||||
f"- 方向:{_wechat_direction_text(direction)}",
|
||||
f"- 持仓均价:{format_price_for_symbol(sym, avg_e)}",
|
||||
f"- 偏移:{pct_used}%(相对均价)",
|
||||
f"- 新止损:{format_price_for_symbol(sym, new_sl)}",
|
||||
f"- 交易所:已更新仓位止损触发单",
|
||||
]
|
||||
)
|
||||
)
|
||||
return True, None
|
||||
|
||||
|
||||
def _trend_weighted_avg(old_avg, old_amt, fill_px, add_amt):
|
||||
try:
|
||||
oa, aa = float(old_amt), float(add_amt)
|
||||
@@ -5086,6 +5029,7 @@ def check_order_monitors():
|
||||
conn,
|
||||
symbol=sym,
|
||||
monitor_type=trade_record_monitor_type(conn, r),
|
||||
trend_plan_id=trend_plan_id_from_monitor_row(r),
|
||||
direction=direction,
|
||||
trigger_price=trigger_price,
|
||||
stop_loss=stop_loss,
|
||||
@@ -5100,7 +5044,10 @@ def check_order_monitors():
|
||||
planned_rr=calc_rr_ratio(direction, trigger_price, r["initial_stop_loss"] or stop_loss, take_profit),
|
||||
actual_rr=calc_actual_rr(pnl_amount, r["risk_amount"]),
|
||||
result=res,
|
||||
miss_reason="触发价已触达,仓位已由交易所止盈/止损或其他方式平掉(本地补记)",
|
||||
miss_reason=handoff_trade_miss_reason(
|
||||
"触发价已触达,仓位已由交易所止盈/止损或其他方式平掉(本地补记)",
|
||||
r,
|
||||
),
|
||||
opened_at=opened_at,
|
||||
closed_at=closed_at,
|
||||
)
|
||||
@@ -5144,6 +5091,7 @@ def check_order_monitors():
|
||||
conn,
|
||||
symbol=sym,
|
||||
monitor_type=trade_record_monitor_type(conn, r),
|
||||
trend_plan_id=trend_plan_id_from_monitor_row(r),
|
||||
direction=direction,
|
||||
trigger_price=trigger_price,
|
||||
stop_loss=stop_loss,
|
||||
@@ -5158,7 +5106,7 @@ def check_order_monitors():
|
||||
planned_rr=calc_rr_ratio(direction, trigger_price, r["initial_stop_loss"] or stop_loss, take_profit),
|
||||
actual_rr=calc_actual_rr(record_pnl, r["risk_amount"]),
|
||||
result=record_res,
|
||||
miss_reason=record_miss,
|
||||
miss_reason=handoff_trade_miss_reason(record_miss, r),
|
||||
opened_at=opened_at,
|
||||
closed_at=record_closed,
|
||||
)
|
||||
@@ -5212,6 +5160,7 @@ def check_order_monitors():
|
||||
conn,
|
||||
symbol=sym,
|
||||
monitor_type=trade_record_monitor_type(conn, r),
|
||||
trend_plan_id=trend_plan_id_from_monitor_row(r),
|
||||
direction=direction,
|
||||
trigger_price=trigger_price,
|
||||
stop_loss=stop_loss,
|
||||
@@ -5226,6 +5175,7 @@ def check_order_monitors():
|
||||
planned_rr=calc_rr_ratio(direction, trigger_price, r["initial_stop_loss"] or stop_loss, take_profit),
|
||||
actual_rr=calc_actual_rr(pnl_amount, r["risk_amount"]),
|
||||
result=res,
|
||||
miss_reason=handoff_trade_miss_reason(None, r),
|
||||
opened_at=opened_at,
|
||||
closed_at=closed_at,
|
||||
)
|
||||
@@ -5277,6 +5227,7 @@ def force_close_before_reset():
|
||||
conn,
|
||||
symbol=r["symbol"],
|
||||
monitor_type=trade_record_monitor_type(conn, r),
|
||||
trend_plan_id=trend_plan_id_from_monitor_row(r),
|
||||
direction=direction,
|
||||
trigger_price=trigger_price,
|
||||
stop_loss=r["stop_loss"],
|
||||
@@ -5291,7 +5242,10 @@ def force_close_before_reset():
|
||||
planned_rr=calc_rr_ratio(direction, trigger_price, r["initial_stop_loss"] or r["stop_loss"], r["take_profit"]),
|
||||
actual_rr=calc_actual_rr(pnl_amount, r["risk_amount"]),
|
||||
result="强制清仓",
|
||||
miss_reason=f"北京时间 {FORCE_CLOSE_BJ_HOUR}:00 整点风控清仓",
|
||||
miss_reason=handoff_trade_miss_reason(
|
||||
f"北京时间 {FORCE_CLOSE_BJ_HOUR}:00 整点风控清仓",
|
||||
r,
|
||||
),
|
||||
opened_at=opened_at,
|
||||
closed_at=closed_at,
|
||||
)
|
||||
@@ -6660,13 +6614,17 @@ def trend_pullback_breakeven(pid):
|
||||
conn.close()
|
||||
flash("未找到运行中的趋势回调计划")
|
||||
return redirect(url_for("strategy_trading_page"))
|
||||
ok, err = apply_trend_pullback_manual_breakeven(conn, row, offset_pct=offset_pct)
|
||||
from strategy_trend_register import apply_manual_breakeven, build_trend_config
|
||||
|
||||
cfg = build_trend_config(sys.modules[__name__])
|
||||
ok, err = apply_manual_breakeven(cfg, conn, row, offset_pct=offset_pct)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
if ok:
|
||||
flash("已手动保本:交易所止损已按均价+偏移更新")
|
||||
else:
|
||||
flash(err or "手动保本失败")
|
||||
flash(
|
||||
"已保本:趋势计划已结束,持仓已移交下单监控并挂止盈止损;平仓后将写入交易记录"
|
||||
if ok
|
||||
else (err or "保本移交失败")
|
||||
)
|
||||
return redirect(url_for("strategy_trend_page"))
|
||||
|
||||
|
||||
@@ -6988,6 +6946,7 @@ def del_order(id):
|
||||
conn,
|
||||
symbol=row["symbol"],
|
||||
monitor_type=trade_record_monitor_type(conn, row),
|
||||
trend_plan_id=trend_plan_id_from_monitor_row(row),
|
||||
direction=row["direction"],
|
||||
trigger_price=row["trigger_price"],
|
||||
stop_loss=row["stop_loss"],
|
||||
@@ -7002,7 +6961,7 @@ def del_order(id):
|
||||
planned_rr=calc_rr_ratio(row["direction"], row["trigger_price"], row["initial_stop_loss"] or row["stop_loss"], row["take_profit"]),
|
||||
actual_rr=calc_actual_rr(pnl_amount, row["risk_amount"]),
|
||||
result="手动平仓",
|
||||
miss_reason="用户手动删除订单触发平仓",
|
||||
miss_reason=handoff_trade_miss_reason("用户手动删除订单触发平仓", row),
|
||||
opened_at=opened_at,
|
||||
closed_at=closed_at,
|
||||
)
|
||||
@@ -7056,7 +7015,7 @@ def del_order(id):
|
||||
planned_rr=calc_rr_ratio(row["direction"], row["trigger_price"], row["initial_stop_loss"] or row["stop_loss"], row["take_profit"]),
|
||||
actual_rr=calc_actual_rr(pnl_amount, row["risk_amount"]),
|
||||
result=result,
|
||||
miss_reason=miss_reason,
|
||||
miss_reason=handoff_trade_miss_reason(miss_reason, row),
|
||||
opened_at=opened_at,
|
||||
closed_at=closed_at,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user