88b7fb7a0a
Forked from crypto_monitor OKX instance (copy-only). Keeps standalone options, perp-options and options-options hedge; drops standalone perp entrypoints; forces coin margin; adds sim wallets; default port 5010. Co-authored-by: Cursor <cursoragent@cursor.com>
158 lines
5.6 KiB
Python
158 lines
5.6 KiB
Python
"""期权复盘自动 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()
|