期权复盘支持纯期权自动生成标的永续双周期K线,按时间标记进场出场。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"""期权复盘自动 K 线:单元测试."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from lib.options.options_review_chart_async_lib import (
|
||||
OPTIONS_REVIEW_AUTO_CHART_TF,
|
||||
build_options_review_chart_filename,
|
||||
merge_auto_chart_into_images,
|
||||
schedule_options_review_exchange_chart,
|
||||
underlying_to_okx_perp_symbol,
|
||||
)
|
||||
from lib.options.options_review_db import init_options_review_tables
|
||||
from lib.options.options_review_lib import save_review_entry, upsert_option_history_row
|
||||
|
||||
|
||||
def _conn() -> sqlite3.Connection:
|
||||
c = sqlite3.connect(":memory:")
|
||||
c.row_factory = sqlite3.Row
|
||||
init_options_review_tables(c)
|
||||
return c
|
||||
|
||||
|
||||
class OptionsReviewChartAsyncTests(unittest.TestCase):
|
||||
def test_underlying_to_okx_perp_symbol(self):
|
||||
self.assertEqual(
|
||||
underlying_to_okx_perp_symbol(
|
||||
"ETH-USD",
|
||||
normalize_symbol_input_fn=lambda s: f"{s}/USDT",
|
||||
normalize_okx_symbol_fn=lambda s: f"{s.split('/')[0]}/USDT:USDT"
|
||||
if "/" in s
|
||||
else s,
|
||||
),
|
||||
"ETH/USDT:USDT",
|
||||
)
|
||||
|
||||
def test_merge_auto_chart_keeps_manual_slots(self):
|
||||
merged = merge_auto_chart_into_images(
|
||||
[
|
||||
{"tf": "auto", "file": "old.png"},
|
||||
{"tf": "15m", "file": "m15.png"},
|
||||
],
|
||||
"new_auto.png",
|
||||
)
|
||||
self.assertEqual(merged[0]["tf"], OPTIONS_REVIEW_AUTO_CHART_TF)
|
||||
self.assertEqual(merged[0]["file"], "new_auto.png")
|
||||
self.assertEqual(merged[1], {"tf": "15m", "file": "m15.png"})
|
||||
|
||||
def test_chart_filename(self):
|
||||
self.assertEqual(
|
||||
build_options_review_chart_filename(12), "options_journal_chart_12.png"
|
||||
)
|
||||
|
||||
@patch("lib.options.options_review_chart_async_lib.threading.Thread")
|
||||
def test_schedule_starts_daemon_thread(self, mock_thread_cls):
|
||||
thread = MagicMock()
|
||||
mock_thread_cls.return_value = thread
|
||||
schedule_options_review_exchange_chart(
|
||||
trade_id=7,
|
||||
exchange_symbol="ETH/USDT:USDT",
|
||||
title_prefix="t",
|
||||
journal_tfs=["15m", "1h"],
|
||||
journal_limit=100,
|
||||
marker_payload={},
|
||||
upload_folder="/tmp",
|
||||
generate_chart_fn=MagicMock(),
|
||||
get_db_fn=MagicMock(),
|
||||
now_str_fn=lambda: "2026-09-15 12:00:00",
|
||||
)
|
||||
mock_thread_cls.assert_called_once()
|
||||
self.assertTrue(mock_thread_cls.call_args.kwargs.get("daemon"))
|
||||
thread.start.assert_called_once()
|
||||
|
||||
def test_writeback_merges_auto_image(self):
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from lib.options.options_review_images_lib import parse_options_review_images_json
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
db_path = Path(td) / "t.db"
|
||||
|
||||
def get_db():
|
||||
c = sqlite3.connect(str(db_path))
|
||||
c.row_factory = sqlite3.Row
|
||||
return c
|
||||
|
||||
conn = get_db()
|
||||
init_options_review_tables(conn)
|
||||
upsert_option_history_row(
|
||||
conn,
|
||||
{
|
||||
"history_key": "ex:chart1",
|
||||
"inst_id": "ETH-USD-260328-2000-C",
|
||||
"underlying": "ETH",
|
||||
"opt_type": "C",
|
||||
"realized_pnl": 1.0,
|
||||
"created_at": "2026-09-01 10:00:00",
|
||||
"closed_at": "2026-09-01 12:00:00",
|
||||
},
|
||||
)
|
||||
trade_id = conn.execute(
|
||||
"SELECT id FROM options_review_trades WHERE history_key='ex:chart1'"
|
||||
).fetchone()["id"]
|
||||
save_review_entry(
|
||||
conn,
|
||||
trade_id,
|
||||
{
|
||||
"strategy_tag": "测试",
|
||||
"images": [{"tf": "15m", "file": "options_journal_x_15m.png"}],
|
||||
},
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
chart_name = build_options_review_chart_filename(trade_id)
|
||||
generate = MagicMock(return_value=chart_name)
|
||||
|
||||
with patch(
|
||||
"lib.options.options_review_chart_async_lib.threading.Thread"
|
||||
) as mock_thread_cls:
|
||||
thread = MagicMock()
|
||||
mock_thread_cls.return_value = thread
|
||||
schedule_options_review_exchange_chart(
|
||||
trade_id=trade_id,
|
||||
exchange_symbol="ETH/USDT:USDT",
|
||||
title_prefix="ETH review",
|
||||
journal_tfs=["15m", "1h"],
|
||||
journal_limit=200,
|
||||
marker_payload={"entry_ts_ms": 1, "exit_ts_ms": 2},
|
||||
upload_folder=td,
|
||||
generate_chart_fn=generate,
|
||||
get_db_fn=get_db,
|
||||
now_str_fn=lambda: "2026-09-15 12:00:00",
|
||||
)
|
||||
target = mock_thread_cls.call_args.kwargs["target"]
|
||||
target()
|
||||
|
||||
conn = get_db()
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT images_json, image FROM options_review_entries WHERE trade_id=?",
|
||||
(trade_id,),
|
||||
).fetchone()
|
||||
self.assertEqual(row["image"], chart_name)
|
||||
images = parse_options_review_images_json(row["images_json"])
|
||||
self.assertEqual(images[0]["tf"], "auto")
|
||||
self.assertEqual(images[0]["file"], chart_name)
|
||||
self.assertTrue(any(x.get("tf") == "15m" for x in images))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user