This commit is contained in:
2026-09-17 00:56:05 +08:00
parent 33693adb66
commit c7d39938a2
14 changed files with 1005 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
"""保存交易意图与网格基准;实际持仓始终以券商快照为准。"""
from dataclasses import asdict, dataclass, field
from pathlib import Path
import json
import math
from libs.lockfile import replace_json
@dataclass
class SymbolState:
volume: int = 0
cost: float = 0.0
last_buy: float = 0.0
armed: bool = False
sell_grid: float = 0.0
peak: int = 0
pending: dict = field(default_factory=dict)
def reset_profit(self):
"""持仓成本或数量改变后,不沿用上轮止盈峰值。"""
self.armed = False
self.sell_grid = 0.0
self.peak = 0
class Store:
def __init__(self, path: Path, account: str):
self.path, self.account = path, account
self.symbols: dict[str, SymbolState] = {}
if path.exists():
try:
raw = json.loads(path.read_text(encoding='utf-8'))
if raw['version'] != 1 or raw['account'] != account:
raise ValueError('版本或账户不一致')
for code, value in raw['symbols'].items():
state = SymbolState(**value)
if type(state.volume) is not int or state.volume < 0 or type(state.peak) is not int:
raise ValueError('状态数量或峰值无效')
if any(not math.isfinite(v) or v < 0 for v in (state.cost, state.last_buy, state.sell_grid)):
raise ValueError('状态价格无效')
if type(state.armed) is not bool or (state.armed and state.sell_grid <= 0):
raise ValueError('止盈状态无效')
if not isinstance(state.pending, dict):
raise ValueError('委托状态无效')
if state.pending:
p = state.pending
if (p['side'] not in ('BUY', 'SELL') or not p['id'].startswith('ETF-')
or type(p['volume']) is not int or p['volume'] <= 0
or (p['side'] == 'BUY' and p['volume'] > 1000)
or type(p['base_volume']) is not int or p['base_volume'] < 0
or not math.isfinite(p['reserved']) or p['reserved'] < 0):
raise ValueError('待确认委托无效')
self.symbols[code] = state
except (ValueError, KeyError, TypeError, AttributeError) as exc:
raise ValueError(f'ETF 状态损坏,禁止自动重建:{path}') from exc
def get(self, code: str) -> SymbolState:
return self.symbols.setdefault(code, SymbolState())
def save(self):
self.path.parent.mkdir(parents=True, exist_ok=True)
replace_json(self.path, dict(version=1, account=self.account,
symbols={k: asdict(v) for k, v in self.symbols.items()}))