This commit is contained in:
2026-09-07 21:22:51 +08:00
parent 9778d54f3d
commit ba61ed5de7
28 changed files with 303 additions and 64 deletions

View File

@@ -0,0 +1,113 @@
"""Offline regression checks for the Python 3.14 performance changes."""
import importlib
import inspect
import unittest
from annotationlib import Format, get_annotations
from concurrent.futures import Future
from datetime import datetime, time, timedelta
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock, patch
from libs.calc import trading_time
from libs.signal import SignalItem
from sdk.models import OrderItem, _parse_datetime
from strategy.trend import boot
from strategy.trend.open import _parse_minutes, check_timezone
class PerformanceRegressionTests(unittest.TestCase):
def test_trading_time_matches_original_for_week_and_boundaries(self):
start = datetime(2026, 9, 7)
for minute in range(7 * 24 * 60):
now = start + timedelta(minutes=minute)
expected = now.weekday() < 5 and (
time(9, 30) <= now.time() <= time(11, 30)
or time(13) <= now.time() <= time(15)
)
self.assertEqual(trading_time(now), expected, now)
for clock in ((11, 30), (15, 0)):
self.assertFalse(trading_time(start.replace(hour=clock[0], minute=clock[1], microsecond=1)))
def test_date_parser_preserves_strptime_acceptance(self):
for date in ('20260907', '2026-09-07', '', '20260229', '20240229', '202691'):
for clock in ('100000', '10:00:00', '93000', '', '240000', 'bad', '1'):
try:
expected = datetime.strptime(date.replace('-', '') + clock.replace(':', '').zfill(6), '%Y%m%d%H%M%S')
except ValueError:
expected = None
self.assertEqual(_parse_datetime(date, clock), expected, (date, clock))
def test_date_cache_tracks_mutable_order_fields(self):
order = OrderItem(insert_date='20260907', insert_time='100000', remark='first|trend')
self.assertEqual(order.created_at, datetime(2026, 9, 7, 10))
order.insert_time = '110000'
order.remark = 'second|trend'
self.assertEqual(order.created_at, datetime(2026, 9, 7, 11))
self.assertEqual(order.local_order_id, 'second')
self.assertEqual(order.get_local_order_id, 'second')
def test_caches_are_bounded(self):
_parse_minutes.cache_clear()
_parse_datetime.cache_clear()
for i in range(4200):
_parse_datetime('invalid', str(i))
_parse_minutes(str(i))
self.assertLessEqual(_parse_datetime.cache_info().currsize, 4096)
self.assertLessEqual(_parse_minutes.cache_info().currsize, 256)
def test_timezone_boundaries_and_current_time_not_cached(self):
for hour in range(24):
for minute in range(60):
now = datetime(2026, 9, 7, hour, minute)
m = hour * 60 + minute
self.assertEqual(check_timezone('9:30-10:30,invalid,23:00-1:00', now),
570 <= m <= 630 or m >= 1380 or m <= 60)
self.assertTrue(check_timezone('*'))
self.assertFalse(check_timezone('24:00-25:00'))
self.assertFalse(check_timezone(''))
def test_signal_order_duplicates_and_request_order_preserved(self):
future = Future()
future.set_result(None)
assets = SimpleNamespace(available=100, total=100)
portfolio = SimpleNamespace(assets=assets, positions={'held': object()}, orders=[])
run = SimpleNamespace(client=Mock(), orders=Mock(), executor=Mock(),
account_cfg=SimpleNamespace(account_id='test', min_cash_ratio=0.1))
run.client.portfolio.return_value = portfolio
run.client.full_tick.return_value = {}
run.executor.submit.return_value = future
signals = [SignalItem(code=c) for c in ('new-b', 'held', 'new-a', 'new-b')]
with patch.object(boot, 'trading_time', return_value=True), \
patch.object(boot, 'market_allow_open', return_value=True), \
patch.object(boot, '_cache_portfolio'), patch('builtins.print'):
boot.RunOnce(run, signals)
run.client.full_tick.assert_called_once_with(['held', 'new-b', 'new-a'])
self.assertEqual(run.executor.submit.call_args_list[1].args,
(boot.open_signal, run, {}, [signals[0], signals[2], signals[3]]))
def test_native_annotations_resolve_for_all_application_modules(self):
root = Path(__file__).resolve().parents[1]
paths = [p for name in ('config', 'sdk', 'libs', 'strategy')
for p in (root / name).rglob('*.py')]
for path in paths:
parts = list(path.relative_to(root).with_suffix('').parts)
if parts[-1] == '__init__':
parts.pop()
module = importlib.import_module('.'.join(parts))
for obj in vars(module).values():
if (inspect.isclass(obj) or inspect.isfunction(obj)) and obj.__module__ == module.__name__:
get_annotations(obj, format=Format.VALUE)
if inspect.isclass(obj):
for member in vars(obj).values():
if isinstance(member, (classmethod, staticmethod)):
member = member.__func__
elif isinstance(member, property):
member = member.fget
if inspect.isfunction(member):
get_annotations(member, format=Format.VALUE)
if __name__ == '__main__':
unittest.main()