Files
qihuo/trade_notify.py
T
dekun 840e88daad Add key-level auto trade, AI analysis, and trading UX improvements.
Key monitors use 5m close triggers with WeChat alerts and box/convergence auto orders; add pending-order worker, structured WeChat notify, AI settings/messages, session clock, CTP margin sizing, and dual-layer position limits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 10:36:56 +08:00

226 lines
6.7 KiB
Python

# Copyright (c) 2025-2026 马建军. All rights reserved.
# 专有软件 — 未经授权禁止复制、传播、转售。
# 严禁用于:带单/代客理财、向他人推荐期货品种或买卖建议、融资配资等业务。
# 详见 LICENSE.zh-CN.txt 与 docs/软件购买与使用协议.md
"""交易事件推送:企业微信 + AI 分析。"""
from __future__ import annotations
from typing import Callable, Optional
from contract_specs import calc_position_metrics, get_contract_spec
from sl_tp_guard import monitor_source_label
from wechat_notify import format_close_done, format_key_open_success, format_open_success
def _risk_amount(capital: float, risk_percent: float) -> Optional[float]:
try:
return round(float(capital) * float(risk_percent) / 100.0, 2)
except (TypeError, ValueError):
return None
def notify_manual_open_filled(
*,
send_wechat: Callable[[str], None],
get_setting: Callable[[str, str], str],
mode_label: str,
sym: str,
symbol_name: str,
direction: str,
entry: float,
sl: Optional[float],
tp: Optional[float],
lots: int,
capital: float,
order_id: str = "",
trailing_be: bool = False,
be_tick_buffer: int = 2,
schedule_ai_fn=None,
db_path: str = "",
) -> None:
if not sl:
return
spec = get_contract_spec(sym)
tick = float(spec.get("tick_size") or 1.0)
try:
rp = float(get_setting("risk_percent", "1") or 1)
except (TypeError, ValueError):
rp = 1.0
metrics = calc_position_metrics(direction, entry, sl, tp or entry, lots, entry, capital, sym)
msg = format_open_success(
symbol_name=symbol_name,
symbol=sym,
direction=direction,
mode_label=mode_label,
order_id=order_id,
entry=entry,
stop_loss=float(sl),
take_profit=float(tp) if tp else None,
lots=lots,
capital=capital,
margin=metrics.get("margin"),
margin_pct=metrics.get("position_pct"),
risk_percent=rp,
risk_amount=_risk_amount(capital, rp),
trailing_be=trailing_be,
be_tick_buffer=be_tick_buffer,
tick_size=tick,
source="期货下单",
)
send_wechat(msg)
if schedule_ai_fn and db_path:
schedule_ai_fn(
db_path=db_path,
get_setting_fn=get_setting,
kind="open",
title=f"{symbol_name or sym} 开仓",
payload={
"symbol": sym,
"direction": direction,
"entry": entry,
"stop_loss": sl,
"take_profit": tp,
"lots": lots,
"capital": capital,
},
send_wechat_fn=None,
)
def notify_key_breakout_open(
*,
send_wechat: Callable[[str], None],
get_setting: Callable[[str, str], str],
mode_label: str,
row: dict,
break_side: str,
bar_time: str,
direction: str,
entry: float,
sl: float,
tp: float,
lots: int,
capital: float,
order_id: str = "",
schedule_ai_fn=None,
db_path: str = "",
) -> None:
sym = row.get("symbol") or ""
name = row.get("symbol_name") or sym
trailing_be = bool(int(row.get("trailing_be") or 0))
try:
rp = float(get_setting("risk_percent", "1") or 1)
be_buf = int(float(get_setting("trailing_be_tick_buffer", "2") or 2))
except (TypeError, ValueError):
rp, be_buf = 1.0, 2
spec = get_contract_spec(sym)
tick = float(spec.get("tick_size") or 1.0)
metrics = calc_position_metrics(direction, entry, sl, tp, lots, entry, capital, sym)
msg = format_key_open_success(
symbol_name=name,
symbol=sym,
monitor_type=row.get("monitor_type") or "",
trade_mode=row.get("trade_mode") or "顺势",
bar_time=bar_time,
break_side=break_side,
direction=direction,
mode_label=mode_label,
order_id=order_id,
entry=entry,
stop_loss=sl,
take_profit=tp,
lots=lots,
capital=capital,
margin=metrics.get("margin"),
margin_pct=metrics.get("position_pct"),
risk_percent=rp,
risk_amount=_risk_amount(capital, rp),
trailing_be=trailing_be,
be_tick_buffer=be_buf,
tick_size=tick,
)
send_wechat(msg)
if schedule_ai_fn and db_path:
schedule_ai_fn(
db_path=db_path,
get_setting_fn=get_setting,
kind="key_open",
title=f"{name} 关键位开仓",
payload={
"monitor_type": row.get("monitor_type"),
"trade_mode": row.get("trade_mode"),
"break_side": break_side,
"entry": entry,
"stop_loss": sl,
"take_profit": tp,
"lots": lots,
},
)
def notify_trade_log_close(
*,
send_wechat: Callable[[str], None],
get_setting: Callable[[str, str], str],
mode_label: str,
capital: float,
sym: str,
symbol_name: str,
direction: str,
entry: float,
close_price: float,
sl: Optional[float],
tp: Optional[float],
lots: float,
pnl_net: float,
equity_after: Optional[float],
holding_minutes: int,
result: str,
monitor_type: str = "",
schedule_ai_fn=None,
db_path: str = "",
) -> None:
src = monitor_source_label(monitor_type) if monitor_type else "期货下单"
note = ""
if tp and sl:
if direction == "long":
if close_price > tp or close_price < sl:
note = "成交价不在计划止盈/止损带内(可能为手动或其他类型平仓)"
else:
if close_price < tp or close_price > sl:
note = "成交价不在计划止盈/止损带内(可能为手动或其他类型平仓)"
msg = format_close_done(
symbol_name=symbol_name,
symbol=sym,
mode_label=mode_label,
direction=direction,
result=result,
pnl_net=pnl_net,
equity_after=equity_after,
capital=capital,
entry=entry,
close_price=close_price,
stop_loss=sl,
take_profit=tp,
lots=lots,
holding_minutes=holding_minutes,
note=note,
)
send_wechat(msg)
if schedule_ai_fn and db_path:
schedule_ai_fn(
db_path=db_path,
get_setting_fn=get_setting,
kind="close",
title=f"{symbol_name or sym} 平仓",
payload={
"source": src,
"result": result,
"pnl_net": pnl_net,
"entry": entry,
"close_price": close_price,
"lots": lots,
},
)