feat libs,sdk,trend

This commit is contained in:
2026-09-07 14:04:26 +08:00
parent d37f9edefc
commit e320de3241
30 changed files with 515 additions and 507 deletions

View File

@@ -0,0 +1,88 @@
import importlib
import io
import logging
import unittest
from concurrent.futures import Future
from contextlib import ExitStack, redirect_stdout
from types import SimpleNamespace
from unittest.mock import Mock, patch
from libs import collector
from sdk import Assets, PositionItem
from strategy.trend import boot
class TrendCollectorTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
with patch('logging.FileHandler', return_value=logging.NullHandler()):
cls.app = importlib.import_module('main')
def setUp(self):
old_snapshot = boot._collector_snapshot
self.addCleanup(setattr, boot, '_collector_snapshot', old_snapshot)
boot._collector_snapshot = None
def test_submission_reads_latest_cache_and_skips_empty(self):
with patch.object(collector, 'collector_push') as push:
collector.submit_trend_data()
push.assert_not_called()
boot._cache_portfolio('account', Assets(available=100), [])
assets = Assets(available=200)
positions = [PositionItem(stock_code='600000.SH', volume=100)]
boot._cache_portfolio('account', assets, positions)
collector.submit_trend_data()
push.assert_called_once_with('account', assets, positions)
uploaded = push.call_args.args
uploaded[1].available = 0
uploaded[2].clear()
self.assertEqual(boot.get_collector_snapshot()[1].available, 200)
self.assertEqual(len(boot.get_collector_snapshot()[2]), 1)
def test_run_once_caches_portfolio_without_submitting_data(self):
completed = Future()
completed.set_result(None)
run = SimpleNamespace(
client=Mock(), orders=Mock(), executor=Mock(),
account_cfg=SimpleNamespace(account_id='account', min_cash_ratio=0.1),
)
assets = Assets(available=100, total=1000)
run.client.portfolio.return_value = SimpleNamespace(assets=assets, positions={}, orders=[])
run.client.full_tick.return_value = {}
run.executor.submit.return_value = completed
with patch.object(boot, 'trading_time', return_value=True), \
patch.object(boot, 'market_allow_open', return_value=True), \
patch.object(collector, 'collector_push') as push, redirect_stdout(io.StringIO()):
boot.RunOnce(run, [])
self.assertEqual(boot.get_collector_snapshot(), ('account', assets, []))
push.assert_not_called()
run.executor.submit.assert_called_once_with(boot.manage_positions, run, {}, [], True, 100)
def test_main_registers_five_minute_collector_job(self):
for strategy in ('trend', 'zt'):
with self.subTest(strategy=strategy), ExitStack() as stack:
scheduler = Mock(running=True)
stack.enter_context(patch.object(self.app, 'BackgroundScheduler', return_value=scheduler))
stack.enter_context(patch.object(self.app, 'require_windows', return_value=True))
stack.enter_context(patch.object(self.app, 'check_single_instance', return_value=True))
stack.enter_context(patch.object(self.app, 'wait_for_qmt_api'))
stack.enter_context(patch.object(self.app.config, 'load'))
stack.enter_context(patch.object(self.app.config, 'global_config', SimpleNamespace(api_host='unused')))
stack.enter_context(patch.object(self.app.config, 'account_config', SimpleNamespace(strategy=strategy)))
stack.enter_context(patch.dict(self.app.STRATEGIES, {
strategy: SimpleNamespace(start_strategy=Mock()),
}))
self.assertEqual(self.app.main(), 0)
jobs = [call for call in scheduler.add_job.call_args_list
if call.kwargs.get('id') == 'trend_collector']
self.assertEqual(len(jobs), 1)
if jobs:
self.assertIs(jobs[0].args[0], collector.submit_trend_data)
self.assertEqual(jobs[0].kwargs['trigger'], 'interval')
self.assertEqual(jobs[0].kwargs['minutes'], 5)
scheduler.start.assert_called_once()
scheduler.shutdown.assert_called_once_with(wait=True)
if __name__ == '__main__':
unittest.main()