条件单未触发至周四23:00自动取消,下发/取消/开仓均推企业微信。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -358,7 +358,12 @@ def register_hub_bridge_routes(app: Flask, app_module: Any) -> None:
|
|||||||
conn = cfg["get_db"]()
|
conn = cfg["get_db"]()
|
||||||
try:
|
try:
|
||||||
init_options_tables(conn)
|
init_options_tables(conn)
|
||||||
out = create_cond_order(conn, data)
|
out = create_cond_order(
|
||||||
|
conn,
|
||||||
|
data,
|
||||||
|
send_wechat=cfg.get("send_wechat"),
|
||||||
|
account_label=str(cfg.get("account_label") or ""),
|
||||||
|
)
|
||||||
out["version"] = 1
|
out["version"] = 1
|
||||||
return jsonify(out)
|
return jsonify(out)
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
# armed:待命穿越; triggering:已穿越等偏离区内开仓; open:持仓中;
|
# armed:待命穿越; triggering:已穿越等偏离区内开仓; open:持仓中;
|
||||||
# done:终态(leave_up/leave_down/cancel/closed)
|
# done:终态(leave_up/leave_down/cancel/timeout/closed)
|
||||||
ACTIVE_STATUSES = ("armed", "triggering", "open")
|
ACTIVE_STATUSES = ("armed", "triggering", "open")
|
||||||
|
|
||||||
|
|
||||||
@@ -87,6 +88,78 @@ def _now() -> str:
|
|||||||
return time.strftime("%Y-%m-%d %H:%M:%S")
|
return time.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
|
def _notify_cond(
|
||||||
|
send_wechat: Any,
|
||||||
|
*,
|
||||||
|
title: str,
|
||||||
|
order: dict[str, Any],
|
||||||
|
account_label: str = "",
|
||||||
|
extra: list[str] | None = None,
|
||||||
|
) -> None:
|
||||||
|
if not callable(send_wechat) or not order:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
lines = [
|
||||||
|
title,
|
||||||
|
f"账户:{account_label or 'OKX期权'}",
|
||||||
|
f"单号:{order.get('hub_order_id') or '—'}",
|
||||||
|
f"标的:{order.get('underlying') or '—'} {order.get('opt_type') or '—'}",
|
||||||
|
f"触发价:{order.get('trigger_px') if order.get('trigger_px') is not None else '—'}",
|
||||||
|
]
|
||||||
|
for line in extra or []:
|
||||||
|
if line:
|
||||||
|
lines.append(line)
|
||||||
|
send_wechat("\n".join(lines))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def next_thursday_2300(created_at: Any) -> datetime | None:
|
||||||
|
"""下发后第一个周四 23:00(业务时区).周四 23:00 之后下发的顺延到下周四."""
|
||||||
|
from lib.options.options_time_lib import options_app_tz, options_wall_clock_to_ms
|
||||||
|
|
||||||
|
ms = options_wall_clock_to_ms(created_at)
|
||||||
|
if ms is None:
|
||||||
|
return None
|
||||||
|
tz = options_app_tz()
|
||||||
|
dt = datetime.fromtimestamp(ms / 1000.0, tz=timezone.utc).astimezone(tz)
|
||||||
|
days = (3 - dt.weekday()) % 7
|
||||||
|
cand = dt.replace(hour=23, minute=0, second=0, microsecond=0) + timedelta(days=days)
|
||||||
|
if cand <= dt:
|
||||||
|
cand += timedelta(days=7)
|
||||||
|
return cand
|
||||||
|
|
||||||
|
|
||||||
|
def expire_untriggered_cond_orders(
|
||||||
|
conn: sqlite3.Connection,
|
||||||
|
*,
|
||||||
|
send_wechat: Any = None,
|
||||||
|
account_label: str = "",
|
||||||
|
now: datetime | None = None,
|
||||||
|
) -> int:
|
||||||
|
"""仅取消仍待命(未触发)的条件单;持仓/触发中不动."""
|
||||||
|
from lib.options.options_time_lib import options_app_tz
|
||||||
|
|
||||||
|
now_dt = now or datetime.now(options_app_tz())
|
||||||
|
n = 0
|
||||||
|
for order in list_cond_orders(conn, active_only=True):
|
||||||
|
if str(order.get("status") or "") != "armed":
|
||||||
|
continue
|
||||||
|
deadline = next_thursday_2300(order.get("created_at"))
|
||||||
|
if deadline is None or now_dt < deadline:
|
||||||
|
continue
|
||||||
|
_end_order(
|
||||||
|
conn,
|
||||||
|
order,
|
||||||
|
end_reason="timeout",
|
||||||
|
message="周四23:00未触发自动取消",
|
||||||
|
send_wechat=send_wechat,
|
||||||
|
account_label=account_label,
|
||||||
|
)
|
||||||
|
n += 1
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
def _row_to_dict(row: sqlite3.Row | None) -> dict[str, Any] | None:
|
def _row_to_dict(row: sqlite3.Row | None) -> dict[str, Any] | None:
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
@@ -156,7 +229,13 @@ def machine_has_option_position(cfg: dict[str, Any], ex: Any) -> bool:
|
|||||||
return count_live_option_positions(raw) > 0
|
return count_live_option_positions(raw) > 0
|
||||||
|
|
||||||
|
|
||||||
def create_cond_order(conn: sqlite3.Connection, payload: dict[str, Any]) -> dict[str, Any]:
|
def create_cond_order(
|
||||||
|
conn: sqlite3.Connection,
|
||||||
|
payload: dict[str, Any],
|
||||||
|
*,
|
||||||
|
send_wechat: Any = None,
|
||||||
|
account_label: str = "",
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""接收中控下发;同机仅允许一张活跃条件单."""
|
"""接收中控下发;同机仅允许一张活跃条件单."""
|
||||||
ensure_cond_order_table(conn)
|
ensure_cond_order_table(conn)
|
||||||
hub_order_id = (payload.get("hub_order_id") or "").strip()
|
hub_order_id = (payload.get("hub_order_id") or "").strip()
|
||||||
@@ -224,6 +303,18 @@ def create_cond_order(conn: sqlite3.Connection, payload: dict[str, Any]) -> dict
|
|||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
order = get_cond_order_by_hub_id(conn, hub_order_id)
|
order = get_cond_order_by_hub_id(conn, hub_order_id)
|
||||||
|
_notify_cond(
|
||||||
|
send_wechat,
|
||||||
|
title="【OKX期权·条件单下发】",
|
||||||
|
order=order or {},
|
||||||
|
account_label=account_label,
|
||||||
|
extra=[
|
||||||
|
f"方向:{'Put 上穿' if opt_type == 'P' else 'Call 下穿'}",
|
||||||
|
f"偏离:{deviation_pct:g}%",
|
||||||
|
f"最短到期:{float(min_expiry_hours):g}h",
|
||||||
|
"状态:待命",
|
||||||
|
],
|
||||||
|
)
|
||||||
return {"ok": True, "msg": "条件单已 armed", "order": order}
|
return {"ok": True, "msg": "条件单已 armed", "order": order}
|
||||||
|
|
||||||
|
|
||||||
@@ -435,20 +526,20 @@ def _end_order(
|
|||||||
"leave_up": "上破偏离区结束",
|
"leave_up": "上破偏离区结束",
|
||||||
"leave_down": "下破偏离区结束",
|
"leave_down": "下破偏离区结束",
|
||||||
"cancel": "中控取消",
|
"cancel": "中控取消",
|
||||||
|
"timeout": "周四23:00未触发自动取消",
|
||||||
"closed": "出场完成",
|
"closed": "出场完成",
|
||||||
}
|
}
|
||||||
lines = [
|
_notify_cond(
|
||||||
"【OKX期权·条件单结束】",
|
send_wechat,
|
||||||
f"账户:{account_label or 'OKX期权'}",
|
title="【OKX期权·条件单结束】",
|
||||||
f"单号:{order.get('hub_order_id')}",
|
order=order,
|
||||||
f"标的:{order.get('underlying')} {order.get('opt_type')}",
|
account_label=account_label,
|
||||||
f"触发价:{order.get('trigger_px')}",
|
extra=[
|
||||||
f"原因:{reason_map.get(end_reason, end_reason)}",
|
f"原因:{reason_map.get(end_reason, end_reason)}",
|
||||||
f"说明:{message}",
|
f"说明:{message}",
|
||||||
]
|
]
|
||||||
if index_px is not None:
|
+ ([f"当前指数:{index_px:g}"] if index_px is not None else []),
|
||||||
lines.append(f"当前指数:{index_px:g}")
|
)
|
||||||
send_wechat("\n".join(lines))
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -583,8 +674,11 @@ def run_cond_orders_tick(
|
|||||||
send_wechat: Any = None,
|
send_wechat: Any = None,
|
||||||
account_label: str = "",
|
account_label: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""监控循环单步:穿越/偏离区/开仓/出场收尾."""
|
"""监控循环单步:周四未触发取消 / 穿越 / 偏离区 / 开仓 / 出场收尾."""
|
||||||
ensure_cond_order_table(conn)
|
ensure_cond_order_table(conn)
|
||||||
|
expire_untriggered_cond_orders(
|
||||||
|
conn, send_wechat=send_wechat, account_label=account_label
|
||||||
|
)
|
||||||
ex = cfg.get("exchange_options")
|
ex = cfg.get("exchange_options")
|
||||||
if ex is None:
|
if ex is None:
|
||||||
return
|
return
|
||||||
@@ -747,8 +841,18 @@ def _tick_one(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
# 确保出场规则挂上
|
opened = get_cond_order_by_hub_id(conn, order["hub_order_id"]) or order
|
||||||
order2 = get_cond_order_by_hub_id(conn, order["hub_order_id"]) or order
|
_notify_cond(
|
||||||
|
send_wechat,
|
||||||
|
title="【OKX期权·条件单开仓】",
|
||||||
|
order=opened,
|
||||||
|
account_label=account_label,
|
||||||
|
extra=[
|
||||||
|
f"合约:{inst_id}",
|
||||||
|
f"说明:已开仓 {inst_id}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
order2 = opened
|
||||||
order2["opened_inst_id"] = inst_id
|
order2["opened_inst_id"] = inst_id
|
||||||
_apply_exit_to_position(
|
_apply_exit_to_position(
|
||||||
conn,
|
conn,
|
||||||
|
|||||||
Reference in New Issue
Block a user