This commit is contained in:
2026-09-19 19:45:43 +08:00
parent 7183cb45f8
commit 8131b158b4
60 changed files with 7669 additions and 909 deletions

View File

@@ -3,7 +3,7 @@ import io
import logging
import unittest
from concurrent.futures import Future
from contextlib import ExitStack, redirect_stdout
from contextlib import ExitStack, redirect_stderr, redirect_stdout
from types import SimpleNamespace
from unittest.mock import Mock, patch
@@ -77,7 +77,7 @@ class TrendCollectorTests(unittest.TestCase):
self.assertEqual(payload['deals'][0]['volume'], 100)
def test_main_registers_five_minute_collector_job(self):
for strategy in ('trend', 'zt'):
for strategy in ('trend', 'zt', 'etf'):
with self.subTest(strategy=strategy), ExitStack() as stack:
scheduler = Mock(running=True)
stack.enter_context(patch.object(self.app, 'BackgroundScheduler', return_value=scheduler))
@@ -101,6 +101,63 @@ class TrendCollectorTests(unittest.TestCase):
scheduler.start.assert_called_once()
scheduler.shutdown.assert_called_once_with(wait=True)
def test_main_rejects_unknown_strategy_before_starting_the_scheduler(self):
scheduler = Mock(running=True)
with patch.object(self.app, 'BackgroundScheduler', return_value=scheduler), \
patch.object(self.app, 'require_windows', return_value=True), \
patch.object(self.app, 'check_single_instance', return_value=True), \
patch.object(self.app, 'wait_for_qmt_api') as wait_api, \
patch.object(self.app.config, 'load'), \
patch.object(self.app.config, 'global_config', SimpleNamespace(api_host='unused')), \
patch.object(self.app.config, 'account_config', SimpleNamespace(strategy='bogus')), \
patch.object(self.app, 'wait_for_any_key'), \
patch('sys.stdin', io.StringIO('\n')), redirect_stderr(io.StringIO()) as err:
self.assertEqual(self.app.main(), 1)
self.assertIn('bogus', err.getvalue())
scheduler.start.assert_not_called()
wait_api.assert_not_called()
def test_main_reports_a_missing_global_config(self):
scheduler = Mock(running=True)
with patch.object(self.app, 'BackgroundScheduler', return_value=scheduler), \
patch.object(self.app, 'require_windows', return_value=True), \
patch.object(self.app, 'check_single_instance', return_value=True), \
patch.object(self.app, 'wait_for_qmt_api') as wait_api, \
patch.object(self.app.config, 'load'), \
patch.object(self.app.config, 'global_config', None), \
patch.object(self.app.config, 'account_config', None), \
patch.object(self.app, 'wait_for_any_key'), \
patch('sys.stdin', io.StringIO('\n')), redirect_stderr(io.StringIO()) as err:
self.assertEqual(self.app.main(), 1)
self.assertIn('config.load', err.getvalue())
scheduler.add_job.assert_not_called()
wait_api.assert_not_called()
class ETFEtfConfigSummaryTests(unittest.TestCase):
"""启动日志里的 ETF 配置概览:缺文件必须说清楚,且不抛异常。"""
def summary(self, etf_cfg):
app = importlib.import_module('main')
with patch.object(app.config, 'etf_config', etf_cfg, create=True):
return app.describe_etf_config()
def test_missing_file_is_described_not_raised(self):
self.assertIn('_etf.yaml', self.summary(None))
def test_symbols_and_codes_are_listed_in_config_order(self):
from config import EtfConfig, EtfDefaults, EtfSymbolConfig
cfg = EtfConfig(
defaults=EtfDefaults(),
symbols={
'159915.SZ': EtfSymbolConfig(is_t0=True, buy_shares=1000, atr_multiplier=1.0, inner_step=0.7),
'510300.SH': EtfSymbolConfig(is_t0=False, buy_shares=1000, atr_multiplier=1.0, inner_step=0.7),
},
)
summary = self.summary(cfg)
self.assertIn('2 只', summary)
self.assertLess(summary.index('159915.SZ'), summary.index('510300.SH'))
if __name__ == '__main__':
unittest.main()