期权复盘支持纯期权自动生成标的永续双周期K线,按时间标记进场出场。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
"""期权复盘自动 K 线:后台线程生成标的永续双周期拼图,写回 images_json."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from lib.options.options_review_images_lib import (
|
||||
images_json_dumps,
|
||||
parse_options_review_images_json,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
OPTIONS_REVIEW_AUTO_CHART_TF = "auto"
|
||||
|
||||
|
||||
def underlying_to_okx_perp_symbol(
|
||||
underlying: str,
|
||||
*,
|
||||
normalize_symbol_input_fn: Callable[[str], str],
|
||||
normalize_okx_symbol_fn: Callable[[str], str],
|
||||
) -> str:
|
||||
"""ETH / ETH-USD / ETH-USDT-SWAP → ETH/USDT:USDT."""
|
||||
raw = str(underlying or "").strip().upper()
|
||||
if not raw:
|
||||
return ""
|
||||
base = raw.split("-", 1)[0].split("/", 1)[0].strip()
|
||||
if not base:
|
||||
return ""
|
||||
guess = normalize_symbol_input_fn(base) or f"{base}/USDT"
|
||||
return str(normalize_okx_symbol_fn(guess) or "").strip()
|
||||
|
||||
|
||||
def merge_auto_chart_into_images(
|
||||
existing: Optional[list],
|
||||
filename: str,
|
||||
) -> list[dict[str, str]]:
|
||||
"""保留手动槽位,用 tf=auto 放置拼图(方案甲一张总图)."""
|
||||
fname = str(filename or "").strip()
|
||||
out: list[dict[str, str]] = []
|
||||
for item in existing or []:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
tf = str(item.get("tf") or "").strip()
|
||||
file = str(item.get("file") or "").strip()
|
||||
if not file or tf == OPTIONS_REVIEW_AUTO_CHART_TF:
|
||||
continue
|
||||
out.append({"tf": tf, "file": file})
|
||||
if fname:
|
||||
out.insert(0, {"tf": OPTIONS_REVIEW_AUTO_CHART_TF, "file": fname})
|
||||
return out
|
||||
|
||||
|
||||
def build_options_review_chart_filename(trade_id: int) -> str:
|
||||
return f"options_journal_chart_{int(trade_id)}.png"
|
||||
|
||||
|
||||
def schedule_options_review_exchange_chart(
|
||||
*,
|
||||
trade_id: int,
|
||||
exchange_symbol: str,
|
||||
title_prefix: str,
|
||||
journal_tfs: list[str],
|
||||
journal_limit: int,
|
||||
marker_payload: dict[str, Any],
|
||||
upload_folder: str,
|
||||
generate_chart_fn: Callable[..., str | None],
|
||||
get_db_fn: Callable[[], Any],
|
||||
now_str_fn: Callable[[], str],
|
||||
) -> None:
|
||||
"""保存后立即返回;线程内画图并合并进 options_review_entries.images_json."""
|
||||
tid = int(trade_id or 0)
|
||||
if tid <= 0 or not callable(generate_chart_fn) or not callable(get_db_fn):
|
||||
return
|
||||
symbol = str(exchange_symbol or "").strip()
|
||||
if not symbol:
|
||||
return
|
||||
tfs = [str(x).strip() for x in (journal_tfs or []) if str(x).strip()]
|
||||
if not tfs:
|
||||
return
|
||||
|
||||
def _run() -> None:
|
||||
try:
|
||||
chart_fname = build_options_review_chart_filename(tid)
|
||||
saved = generate_chart_fn(
|
||||
symbol,
|
||||
title_prefix,
|
||||
timeframes=tfs,
|
||||
limit=journal_limit,
|
||||
out_dir=upload_folder,
|
||||
filename=chart_fname,
|
||||
filename_prefix="options_journal_chart",
|
||||
marker_payload=marker_payload,
|
||||
marker_timeframes={x.lower() for x in tfs},
|
||||
layout="vertical",
|
||||
)
|
||||
if not saved:
|
||||
logger.warning("options review chart async empty trade_id=%s", tid)
|
||||
return
|
||||
conn = get_db_fn()
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT images_json FROM options_review_entries WHERE trade_id=?",
|
||||
(tid,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
logger.warning(
|
||||
"options review chart async missing entry trade_id=%s", tid
|
||||
)
|
||||
return
|
||||
images = parse_options_review_images_json(row["images_json"])
|
||||
merged = merge_auto_chart_into_images(images, saved)
|
||||
primary = merged[0]["file"] if merged else saved
|
||||
conn.execute(
|
||||
"""UPDATE options_review_entries
|
||||
SET images_json=?, image=?, updated_at=?
|
||||
WHERE trade_id=?""",
|
||||
(
|
||||
images_json_dumps(merged),
|
||||
primary,
|
||||
now_str_fn(),
|
||||
tid,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception:
|
||||
logger.exception("options review chart async failed trade_id=%s", tid)
|
||||
|
||||
threading.Thread(
|
||||
target=_run,
|
||||
name=f"options-review-chart-{tid}",
|
||||
daemon=True,
|
||||
).start()
|
||||
Reference in New Issue
Block a user