"""完整55/20窗口的事件突破、下一开盘、失败/到期退出与合成限制。""" from copy import deepcopy from decimal import Decimal as D import json from math import isfinite def make_bars(): bars = [{'open': 10, 'high': 11, 'low': 9, 'close': 10, 'volume': 100, 'buy_allowed': True, 'sell_allowed': True} for _ in range(55)] bars.append({'open': 10, 'high': 12.5, 'low': 10, 'close': 12, 'volume': 200, 'buy_allowed': True, 'sell_allowed': True}) bars += [{'open': 12.2, 'high': 12.5, 'low': 11.5, 'close': 12, 'volume': 100, 'buy_allowed': True, 'sell_allowed': True} for _ in range(22)] bars[76]['open'] = 12 return bars def run(bars): cash, quantity, entered, fees = D(10000), 0, None, D(0) pending_entry, pending_exit, previous_condition, ledger = False, None, False, [] for i, bar in enumerate(bars): for key in ['open', 'high', 'low', 'close']: if bar[key] is None or not isfinite(bar[key]) or bar[key] <= 0: raise ValueError('invalid_price') if bar['volume'] is None or not isfinite(bar['volume']) or bar['volume'] < 0: raise ValueError('invalid_volume') orders, rejected, price = [], [], D(str(bar['open'])) exit_reason = pending_exit or ('max_holding' if quantity and i >= entered + 20 else None) if quantity and exit_reason: if bar['sell_allowed'] and i > entered: cash += quantity * price - 1 fees += 1 orders.append(['sell', quantity, str(price), 1, exit_reason]) quantity, entered, pending_exit = 0, None, None else: rejected.append(['sell', exit_reason]) if pending_entry: if quantity == 0 and bar['buy_allowed'] and 100 * price + 1 <= cash: cash -= 100 * price + 1 quantity, entered, fees = 100, i, fees + 1 orders.append(['buy', 100, str(price), 1, 'prior_close_breakout']) else: rejected.append(['buy', 'opening_limit_or_cash_block']) pending_entry = False high55 = max(row['high'] for row in bars[i-55:i]) if i >= 55 else None volume20 = sum(row['volume'] for row in bars[i-20:i]) / 20 if i >= 55 else None low20 = min(row['low'] for row in bars[i-20:i]) if i >= 55 else None condition = bool(i >= 55 and bar['close'] > high55 and bar['volume'] > 1.5 * volume20) event = bool(condition and not previous_condition) if not quantity and event: pending_entry = True if quantity and bar['close'] < low20: pending_exit = 'close_below_prior_low20' previous_condition = condition equity = cash + quantity * D(str(bar['close'])) ledger.append({'i': i, 'prior_high55': high55, 'prior_volume20': volume20, 'prior_low20': low20, 'event': event, 'orders': orders, 'rejected': rejected, 'quantity': quantity, 'entry': entered, 'cash': str(cash), 'fees': str(fees), 'equity': str(equity)}) return ledger bars = make_bars() rows = run(bars) assert all(not row['event'] for row in rows[:55]) assert rows[55]['prior_high55'] == 11 and rows[55]['prior_volume20'] == 100 and rows[55]['event'] assert rows[55]['orders'] == [] assert rows[56]['orders'] == [['buy', 100, '12.2', 1, 'prior_close_breakout']] assert rows[56]['cash'] == '8779.0' and rows[56]['quantity'] == 100 assert rows[76]['orders'] == [['sell', 100, '12', 1, 'max_holding']] assert rows[-1]['cash'] == rows[-1]['equity'] == '9978.0' assert rows[-1]['quantity'] == 0 and rows[-1]['fees'] == '2' failure = deepcopy(bars) failure[57].update(close=8, low=7.5) failure[58]['open'] = 7.8 failed = run(failure) assert failed[57]['prior_low20'] == 9 and failed[57]['orders'] == [] assert failed[58]['orders'] == [['sell', 100, '7.8', 1, 'close_below_prior_low20']] assert failed[58]['cash'] == '9558.0' and failed[58]['quantity'] == 0 blocked_bars = deepcopy(bars) blocked_bars[56]['buy_allowed'] = False blocked = run(blocked_bars) assert blocked[56]['orders'] == [] and len(blocked[56]['rejected']) == 1 assert blocked[-1]['cash'] == '10000' and blocked[-1]['fees'] == '0' and blocked[-1]['quantity'] == 0 blocked_exit_bars = deepcopy(bars) blocked_exit_bars[76]['sell_allowed'] = False late_exit = run(blocked_exit_bars) assert late_exit[76]['orders'] == [] and late_exit[76]['quantity'] == 100 assert late_exit[76]['cash'] == '8779.0' and late_exit[76]['fees'] == '1' assert late_exit[77]['orders'] == [['sell', 100, '12.2', 1, 'max_holding']] assert late_exit[77]['cash'] == '9998.0' and late_exit[77]['quantity'] == 0 assert run(bars[:60]) == rows[:60] for cutoff in [54, 55, 56, 57, 76, 77]: assert run(bars[:cutoff]) == rows[:cutoff] future = deepcopy(bars) future[70].update(close=1000, high=1001) assert run(future)[:60] == rows[:60] print(json.dumps({'trigger': rows[55], 'entry': rows[56], 'expiry_exit': rows[76], 'failure_exit': failed[58], 'blocked_entry': blocked[56], 'blocked_final': blocked[-1], 'prefix_equal': True, 'blocked_due_exit': late_exit[76], 'later_exit': late_exit[77], 'future_mutation_past_equal': True, 'limit_scope': 'supplied synthetic permission; not queue or full board rules'}))