修改okx推送

This commit is contained in:
dekun
2026-05-27 06:49:12 +08:00
parent b26647060e
commit 3c461a8068
2 changed files with 120 additions and 58 deletions
+113 -58
View File
@@ -293,6 +293,22 @@ def send_wechat_msg(content):
pass pass
_BREAKEVEN_EXCHANGE_WARNED_IDS = set()
def _send_breakeven_exchange_warn_once(order_id, message):
"""移动保本同步交易所失败:同一笔监控单只推送一次,避免轮询刷屏。"""
oid = int(order_id)
if oid in _BREAKEVEN_EXCHANGE_WARNED_IDS:
return
_BREAKEVEN_EXCHANGE_WARNED_IDS.add(oid)
send_wechat_msg(message)
def _clear_breakeven_exchange_warn(order_id):
_BREAKEVEN_EXCHANGE_WARNED_IDS.discard(int(order_id))
def _wechat_account_label(): def _wechat_account_label():
return (os.getenv("OKX_ACCOUNT_LABEL") or "okx实盘子账户").strip() return (os.getenv("OKX_ACCOUNT_LABEL") or "okx实盘子账户").strip()
@@ -308,15 +324,25 @@ def _wechat_trading_capital_text(fallback=None):
except Exception: except Exception:
trading_capital = None trading_capital = None
if trading_capital is not None: if trading_capital is not None:
return f"{round(float(trading_capital), 4)}U" return f"{round(float(trading_capital), 2)}U"
if fallback is not None: if fallback is not None:
try: try:
return f"{round(float(fallback), 4)}U" return f"{round(float(fallback), 2)}U"
except Exception: except Exception:
pass pass
return "-" return "-"
def format_wechat_scalar_2dp(value):
"""企业微信推送:数值统一两位小数(与交易所 tick 无关)。"""
if value in (None, ""):
return "-"
try:
return f"{float(value):.2f}"
except (TypeError, ValueError):
return str(value)
def build_wechat_close_message( def build_wechat_close_message(
symbol, symbol,
direction, direction,
@@ -332,29 +358,40 @@ def build_wechat_close_message(
session_capital_fallback=None, session_capital_fallback=None,
): ):
hold_txt = format_hold_minutes(calc_hold_minutes(hold_seconds)) if hold_seconds is not None else "-" hold_txt = format_hold_minutes(calc_hold_minutes(hold_seconds)) if hold_seconds is not None else "-"
ep = format_price_for_symbol(symbol, trigger_price)
cp = format_price_for_symbol(symbol, current_price)
tp = format_price_for_symbol(symbol, take_profit)
sl = format_wechat_scalar_2dp(stop_loss)
cap_txt = _wechat_trading_capital_text(session_capital_fallback)
try:
if pnl_amount is not None:
pv = float(pnl_amount)
pnl_disp = f"{'+' if pv > 0 else ''}{round(pv, 2)} U"
else:
pnl_disp = "-"
except (TypeError, ValueError):
pnl_disp = "-"
lines = [ lines = [
f"# ✅ {symbol} 实盘平仓记录", f"📉 {symbol} 平仓完成",
f"**账户:{_wechat_account_label()}**", f"💼 账户:{_wechat_account_label()}",
"", "",
"---", "🧾 平仓概要",
f"🔖 平仓单号:{close_order_id or '-'}",
f"📌 方向:{_wechat_direction_text(direction)}",
f"📌 平仓结果:{result or '-'}",
f"💰 本单盈亏:{pnl_disp}",
f"⏱ 持仓时长:{hold_txt}",
f"💵 交易账户资金:{cap_txt}",
"", "",
"### 平仓基础信息", "🎯 价位(计划)",
f"- 方向:**{_wechat_direction_text(direction)}**", f"开仓成交价:{ep}",
f"- 平仓结果:**{result or '-'}**", f"离场参考价:{cp}",
f"- 交易所平仓ID`{close_order_id or '-'}`", f"止盈价位:{tp}",
f"- 持仓时长:`{hold_txt}`", f"止损价位:{sl}",
f"- 本单盈亏:`{round(float(pnl_amount), 4) if pnl_amount is not None else '-'}U`",
f"- 交易账户资金:`{_wechat_trading_capital_text(session_capital_fallback)}`",
"",
"---",
"",
"### 价格信息",
f"- 入场价:`{trigger_price if trigger_price is not None else '-'}`",
f"- 平仓参考价:`{current_price if current_price is not None else '-'}`",
f"- 止盈/止损:`{take_profit if take_profit is not None else '-'}` / `{stop_loss if stop_loss is not None else '-'}`",
] ]
if extra_note: if extra_note:
lines.extend(["", f"*备注:{extra_note}*"]) lines.extend(["", "📎 备注", extra_note])
return "\n".join(lines) return "\n".join(lines)
@@ -371,7 +408,7 @@ def build_wechat_breakeven_message(symbol, direction, arm_txt, now_rr, locked_r,
f"- 类型:**{arm_txt}**", f"- 类型:**{arm_txt}**",
f"- 当前RR`{round(float(now_rr), 2)}R`", f"- 当前RR`{round(float(now_rr), 2)}R`",
f"- 锁定RR`{round(float(locked_r), 2)}R`", f"- 锁定RR`{round(float(locked_r), 2)}R`",
f"- 新保护位:`{new_sl}`", f"- 新保护位:`{format_wechat_scalar_2dp(new_sl)}`",
] ]
) )
@@ -2262,12 +2299,12 @@ def auto_transfer_once_per_day():
if from_balance is not None and from_balance < needed: if from_balance is not None and from_balance < needed:
conn.execute( conn.execute(
"INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)", "INSERT INTO transfer_logs (transfer_type, transfer_day, amount, from_account, to_account, status, message) VALUES (?,?,?,?,?,?,?)",
("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{needed}U,当前{round(from_balance,4)}U") ("auto_daily", transfer_day, needed, AUTO_TRANSFER_FROM, AUTO_TRANSFER_TO, "failed", f"{AUTO_TRANSFER_FROM}账户USDT不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U")
) )
conn.commit() conn.commit()
conn.close() conn.close()
send_wechat_msg( send_wechat_msg(
f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{needed}U,当前{round(from_balance,4)}U\n" f"自动划转失败:{AUTO_TRANSFER_FROM}余额不足,需{round(needed, 2)}U,当前{round(from_balance, 2)}U\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}" f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
) )
return return
@@ -2281,13 +2318,13 @@ def auto_transfer_once_per_day():
conn.close() conn.close()
if ok: if ok:
send_wechat_msg( send_wechat_msg(
f"自动划转成功:补足到{target_amount}U,实际划转{needed}U " f"自动划转成功:补足到{round(float(target_amount), 2)}U,实际划转{round(needed, 2)}U "
f"{AUTO_TRANSFER_FROM}->{AUTO_TRANSFER_TO}\n" f"{AUTO_TRANSFER_FROM}->{AUTO_TRANSFER_TO}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}" f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
) )
else: else:
send_wechat_msg( send_wechat_msg(
f"自动划转失败:计划补足到{target_amount}U,需划转{needed}U\n原因:{msg}\n" f"自动划转失败:计划补足到{round(float(target_amount), 2)}U,需划转{round(needed, 2)}U\n原因:{msg}\n"
f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}" f"账簿日(UTC){transfer_day}|触发时刻(北京){app_now_str()}"
) )
@@ -4301,7 +4338,7 @@ def _finalize_fib_key_fill(conn, row):
base_amount, base_amount,
oid, oid,
) )
rr_txt = f"{planned_rr:.4f}" if planned_rr is not None else "-" rr_txt = format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
succ = ( succ = (
f"# ✅ {symbol} 斐波限价成交\n" f"# ✅ {symbol} 斐波限价成交\n"
f"**账户:{_wechat_account_label()}**\n" f"**账户:{_wechat_account_label()}**\n"
@@ -4309,7 +4346,7 @@ def _finalize_fib_key_fill(conn, row):
f"- 类型:{typ}{_wechat_direction_text(direction)}\n" f"- 类型:{typ}{_wechat_direction_text(direction)}\n"
f"- 订单 ID**{new_order_id}**\n" f"- 订单 ID**{new_order_id}**\n"
f"- 成交价:{format_price_for_symbol(symbol, trigger_price)}\n" f"- 成交价:{format_price_for_symbol(symbol, trigger_price)}\n"
f"- 止损:{sl}|止盈:{format_price_for_symbol(symbol, tp)}\n" f"- 止损:{format_wechat_scalar_2dp(sl)}|止盈:{format_price_for_symbol(symbol, tp)}\n"
f"- 计划 RR{rr_txt}:1\n" f"- 计划 RR{rr_txt}:1\n"
f"- {'已挂交易所 TP/SL' if tpsl_attached else 'TP/SL 未挂上'}\n" f"- {'已挂交易所 TP/SL' if tpsl_attached else 'TP/SL 未挂上'}\n"
) )
@@ -4515,7 +4552,7 @@ def check_key_monitors():
if plan_tuple: if plan_tuple:
E, sl_raw, tp_raw, box_h = plan_tuple E, sl_raw, tp_raw, box_h = plan_tuple
planned_rr = calc_rr_ratio(direction, E, sl_raw, tp_raw) planned_rr = calc_rr_ratio(direction, E, sl_raw, tp_raw)
rr_txt = f"{planned_rr:.4f}" if planned_rr is not None else "-" rr_txt = format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else "-"
op_lines = [ op_lines = [
f"录入方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'' if be_on else ''}", f"录入方案:{sl_tp_mode_label(sl_tp_mode)}|移动保本:{'' if be_on else ''}",
sl_tp_plan_summary_text( sl_tp_plan_summary_text(
@@ -4523,7 +4560,7 @@ def check_key_monitors():
outside_pct=KEY_STOP_OUTSIDE_BREAKOUT_PCT, outside_pct=KEY_STOP_OUTSIDE_BREAKOUT_PCT,
trend_outside_pct=KEY_TREND_STOP_OUTSIDE_PCT, trend_outside_pct=KEY_TREND_STOP_OUTSIDE_PCT,
), ),
f"计划 SL`{round(sl_raw, 8)}`|计划 TP`{round(tp_raw, 8)}`|计划 RRE):{rr_txt}:1", f"计划 SL`{format_wechat_scalar_2dp(sl_raw)}`|计划 TP`{format_price_for_symbol(sym, tp_raw)}`|计划 RRE):{rr_txt}:1",
"说明:OKX 本实例为提醒模式,不自动市价开仓;请按方案自行下单。", "说明:OKX 本实例为提醒模式,不自动市价开仓;请按方案自行下单。",
] ]
else: else:
@@ -4560,6 +4597,7 @@ def check_key_monitors():
"\n".join( "\n".join(
[ [
f"# 🧾 {r['symbol']} 关键位监控结束", f"# 🧾 {r['symbol']} 关键位监控结束",
f"**账户:{_wechat_account_label()}**",
"", "",
f"- 原因:已满 {max_n} 次提醒", f"- 原因:已满 {max_n} 次提醒",
"- 状态:已自动结束并记入历史", "- 状态:已自动结束并记入历史",
@@ -4614,6 +4652,7 @@ def check_order_monitors():
direction == "long" and new_sl > float(stop_loss) direction == "long" and new_sl > float(stop_loss)
) )
if should_move: if should_move:
was_armed = breakeven_armed
ex_sym = resolve_monitor_exchange_symbol(r) ex_sym = resolve_monitor_exchange_symbol(r)
new_sl = round_price_to_exchange(ex_sym, new_sl) new_sl = round_price_to_exchange(ex_sym, new_sl)
tp_ex = float(take_profit or 0) tp_ex = float(take_profit or 0)
@@ -4623,13 +4662,15 @@ def check_order_monitors():
try: try:
replace_active_monitor_tpsl_on_exchange(r, new_sl, tp_ex) replace_active_monitor_tpsl_on_exchange(r, new_sl, tp_ex)
synced_ex = True synced_ex = True
_clear_breakeven_exchange_warn(pid)
except Exception as e: except Exception as e:
print( print(
f"[breakeven] exchange tpsl replace failed order={pid} {sym}: {e}", f"[breakeven] exchange tpsl replace failed order={pid} {sym}: {e}",
flush=True, flush=True,
) )
send_wechat_msg( _send_breakeven_exchange_warn_once(
f"⚠️ {sym} 移动保本止损未同步交易所:{friendly_okx_error(e)}" pid,
f"⚠️ {sym} 移动保本止损未同步交易所:{friendly_okx_error(e)}",
) )
elif ok_live: elif ok_live:
print( print(
@@ -4642,18 +4683,20 @@ def check_order_monitors():
(new_sl, new_sl, pid), (new_sl, new_sl, pid),
) )
stop_loss = new_sl stop_loss = new_sl
arm_txt = "保本止盈" if not breakeven_armed else "移动止盈" breakeven_armed = 1
be_msg = build_wechat_breakeven_message( if not was_armed:
sym, arm_txt = "保本止盈"
direction, be_msg = build_wechat_breakeven_message(
arm_txt, sym,
now_rr, direction,
locked_r, arm_txt,
new_sl, now_rr,
) locked_r,
if ok_live: new_sl,
be_msg += "\n- 交易所:已先撤后挂止盈止损" )
send_wechat_msg(be_msg) if ok_live:
be_msg += "\n- 交易所:已先撤后挂止盈止损"
send_wechat_msg(be_msg)
res = None res = None
# 做多 # 做多
@@ -6198,14 +6241,26 @@ def add_order():
_, trading_capital_after = get_exchange_capitals(force=True) _, trading_capital_after = get_exchange_capitals(force=True)
account_base_display = ( account_base_display = (
round(float(trading_capital_after), 4) round(float(trading_capital_after), 2)
if trading_capital_after is not None if trading_capital_after is not None
else round(float(capital_base), 4) else round(float(capital_base), 2)
) )
account_name = (os.getenv("OKX_ACCOUNT_LABEL") or "okx实盘子账户").strip()
dir_text = "多头(long" if direction == "long" else "空头(short" dir_text = "多头(long" if direction == "long" else "空头(short"
order_state_text = "交易所止盈止损已挂单" if tpsl_attached else "未挂单(已拦截)" order_state_text = (
"已在交易所挂条件委托(止盈、止损各一张触发单)"
if tpsl_attached
else "条件委托未挂上(已拦截)"
)
rr_show = planned_rr if planned_rr is not None else "-" rr_show = planned_rr if planned_rr is not None else "-"
try:
rr_show_fmt = f"{float(planned_rr):.2f}" if planned_rr is not None else None
except (TypeError, ValueError):
rr_show_fmt = None
rr_line = f"RR {rr_show_fmt} : 1" if rr_show_fmt is not None else f"RR {rr_show} : 1"
ep_wx = format_price_for_symbol(symbol, trigger_price)
sl_wx = format_wechat_scalar_2dp(stop_loss)
tp_wx = format_price_for_symbol(symbol, take_profit)
be_wx = format_price_for_symbol(symbol, breakeven_price)
style_zh = "Swing 波段" if trade_style == "swing" else "Trend 趋势" style_zh = "Swing 波段" if trade_style == "swing" else "Trend 趋势"
wx_lines = [ wx_lines = [
f"📈 {symbol} 开仓成功", f"📈 {symbol} 开仓成功",
@@ -6213,22 +6268,22 @@ def add_order():
"🧾 订单基础信息", "🧾 订单基础信息",
f"🔖 交易所订单 ID{open_order_id}", f"🔖 交易所订单 ID{open_order_id}",
f"📈 交易风格:{style_zh}", f"📈 交易风格:{style_zh}",
f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 4)} U", f"⚠️ 单笔风控风险:{risk_percent}% ≈ {round(float(risk_amount_final), 2)} U",
"📊 仓位配置详情", "📊 仓位配置详情",
f"账户基数:{account_base_display} USDT", f"账户基数:{account_base_display} USDT",
f"合约杠杆:{leverage}", f"合约杠杆:{leverage}",
f"名义仓位:{notional_value} USDT", f"名义仓位:{format_wechat_scalar_2dp(notional_value)} USDT",
f"仓位占比:{position_ratio}%", f"仓位占比:{position_ratio}%",
f"合约张数:{amount}", f"合约张数:{format_wechat_scalar_2dp(amount)}",
f"折算标的:{base_amount} {journal_coin_from_symbol(symbol)}", f"折算标的:{base_amount} {journal_coin_from_symbol(symbol)}",
"🎯 价位 & 盈亏比", "🎯 价位 & 盈亏比",
f"开仓成交价:{trigger_price}", f"开仓成交价:{ep_wx}",
f"止损价位:{stop_loss}", f"止损价位:{sl_wx}",
f"止盈价位:{take_profit}", f"止盈价位:{tp_wx}",
f"计划盈亏比:RR {rr_show} : 1", f"计划盈亏比:{rr_line}",
f"移动保本位:{breakeven_rr_trigger}R → {breakeven_price}", f"移动保本位:{breakeven_rr_trigger}R → {be_wx}",
"📌 状态统计", "📌 状态统计",
f"止盈止损{order_state_text}", f"条件委托{order_state_text}",
f"📅 当日开仓次数:{opens_today_after} / {DAILY_OPEN_ALERT_THRESHOLD} 次(风控阈值提醒)", f"📅 当日开仓次数:{opens_today_after} / {DAILY_OPEN_ALERT_THRESHOLD} 次(风控阈值提醒)",
] ]
if chart_url: if chart_url:
@@ -6236,8 +6291,8 @@ def add_order():
send_wechat_msg("\n".join(wx_lines)) send_wechat_msg("\n".join(wx_lines))
flash_lines = [ flash_lines = [
f"实盘开单成功:风格 {trade_style};风险 {risk_percent}%≈{risk_amount_final}U;基数 {margin_capital}U,杠杆 {leverage}x,名义仓位 {notional_value}U,仓位占比 {position_ratio}%,合约张数 {amount}(折算标的 {base_amount})," f"实盘开单成功:风格 {trade_style};风险 {risk_percent}%≈{round(float(risk_amount_final), 2)}U;基数 {round(float(margin_capital), 2)}U,杠杆 {leverage}x,名义仓位 {format_wechat_scalar_2dp(notional_value)}U,仓位占比 {position_ratio}%,合约张数 {format_wechat_scalar_2dp(amount)}(折算标的 {base_amount}),"
f"计划RR {planned_rr if planned_rr is not None else '-'};止盈止损已挂交易所", f"计划RR {format_wechat_scalar_2dp(planned_rr) if planned_rr is not None else '-'}已在交易所挂条件止盈/止损委托(非仓位绑定型)",
f"本交易日累计开仓:{opens_today_after}", f"本交易日累计开仓:{opens_today_after}",
] ]
if chart_url: if chart_url:
+7
View File
@@ -79,6 +79,13 @@
- 手动强制同步:`GET /api/sync_exchange_pnl`(需登录)。 - 手动强制同步:`GET /api/sync_exchange_pnl`(需登录)。
- 可选 `.env``EXCHANGE_POSITION_SYNC_FROM_BJ`(北京时间起点)、`EXCHANGE_POSITION_HISTORY_LIMIT`(默认 200)。 - 可选 `.env``EXCHANGE_POSITION_SYNC_FROM_BJ`(北京时间起点)、`EXCHANGE_POSITION_HISTORY_LIMIT`(默认 200)。
## 企业微信推送(与 Gate 对齐)
- 平仓:`📉 … 平仓完成` 模板(盈亏 ±X.XX U、价位两位/按币价精度、账户资金 2 位小数)。
- 开仓成功:与 Gate 相同的 emoji 分段(条件委托状态文案、RR/张数/名义 2 位小数)。
- 移动保本:仅首次触发推送;交易所同步失败同一监控单只告警一次。
- 斐波/关键位/划转等推送数值格式与 Gate 一致(`format_wechat_scalar_2dp`)。
## 配置与部署 ## 配置与部署
- 详见 `.env.example` 中 OKX`OKX_*`)与通用风控项。 - 详见 `.env.example` 中 OKX`OKX_*`)与通用风控项。