fix bug
This commit is contained in:
@@ -137,6 +137,7 @@ class State:
|
||||
amount, date, deal.trade_time, deal.remark, deal.close_profit, 0),
|
||||
)
|
||||
self.load_deals()
|
||||
|
||||
def sync_state(self, positions: list[PositionItem]) -> None:
|
||||
"""同步完整持仓:无状态则插入底仓,已有则保留,清仓则删除。
|
||||
"""
|
||||
@@ -165,20 +166,29 @@ class State:
|
||||
)
|
||||
self.load_state()
|
||||
|
||||
def archiving(self, *, base_order_prefix: str = '') -> dict[str, str]:
|
||||
"""将未归档成交累加到当前底仓和加仓;失败证券保留记录供重试。"""
|
||||
errors = {}
|
||||
def archiving(self) -> None:
|
||||
"""将未归档成交累加到当前底仓和加仓;失败证券保留记录供重试。
|
||||
|
||||
归档是把成交反映到持仓状态中,再标记为已处理,不会删除成交记录。
|
||||
本地委托号以 zt-base- 开头的买入计入底仓,其他买入计入补仓。
|
||||
不返回结果;失败原因记录到日志,对应成交保留未归档标记供重试。
|
||||
"""
|
||||
with closing(self._connect()) as db, db:
|
||||
# 提前取得数据库写入锁,让持仓更新和成交标记在同一事务内完成。
|
||||
db.execute('BEGIN IMMEDIATE')
|
||||
# 只找尚未处理的成交:23、48 是买入,24、49 是卖出。
|
||||
codes = db.execute(
|
||||
'SELECT DISTINCT stock_code FROM deals WHERE is_arch = 0 AND offset_flag IN (23, 24, 48, 49)'
|
||||
'SELECT DISTINCT stock_code FROM deals WHERE is_arch = 0'
|
||||
).fetchall()
|
||||
for entry in codes:
|
||||
code = entry['stock_code']
|
||||
# 每只股票设一个回滚点;这只处理失败时,不撤销其他股票的结果。
|
||||
db.execute('SAVEPOINT archive_stock')
|
||||
try:
|
||||
current = db.execute('SELECT * FROM state WHERE stock_code = ?', (code,)).fetchone()
|
||||
# 已有持仓就接着计算;没有记录则从底仓、补仓均为零开始。
|
||||
state = dict(current) if current else asdict(StateItem(stock_code=code))
|
||||
# 按成交日期、时间、记录编号依次处理,保证先买后卖等顺序正确。
|
||||
deals = db.execute(
|
||||
'SELECT * FROM deals WHERE stock_code = ? AND is_arch = 0 AND offset_flag IN (23, 24, 48, 49) '
|
||||
"ORDER BY trade_date, REPLACE(trade_time, ':', ''), id", (code,)
|
||||
@@ -186,34 +196,43 @@ class State:
|
||||
for deal in deals:
|
||||
qty = deal['volume']
|
||||
if deal['offset_flag'] in (23, 48):
|
||||
bucket = 'base' if base_order_prefix and deal['order_local_id'].startswith(base_order_prefix) else 'added'
|
||||
# 按已有的 ZT 底仓委托号约定识别,无需调用方传入规则。
|
||||
bucket = 'base' if deal['order_local_id'].startswith('zt-base-') else 'added'
|
||||
total = state[f'{bucket}_qty'] + qty
|
||||
# 新均价 =(原数量 × 原均价 + 本次成交金额)÷ 买入后总数量。
|
||||
state[f'{bucket}_price'] = (
|
||||
state[f'{bucket}_qty'] * state[f'{bucket}_price'] + deal['trade_amount']
|
||||
) / total
|
||||
state[f'{bucket}_qty'] = total
|
||||
# 保存这一类仓位最近一次买入的委托号和成交时间。
|
||||
state[f'{bucket}_order_local_id'] = deal['order_local_id']
|
||||
state[f'{bucket}_created_at'] = f"{deal['trade_date']} {deal['trade_time']}".strip()
|
||||
else:
|
||||
total = state['base_qty'] + state['added_qty']
|
||||
# 卖出不能超过本地记录的总持仓;不一致时留待排查后重试。
|
||||
if qty > total:
|
||||
raise ValueError(f'Sell volume {qty} exceeds recorded holdings {total}')
|
||||
# 卖出优先扣补仓;补仓未卖完时只减数量,保留原均价。
|
||||
if qty < state['added_qty']:
|
||||
state['added_qty'] -= qty
|
||||
else:
|
||||
# 补仓全部卖完后,剩余卖出量扣底仓,并清空补仓信息。
|
||||
state['base_qty'] = total - qty
|
||||
state['added_qty'] = 0
|
||||
state['added_price'] = 0.0
|
||||
state['added_order_local_id'] = state['added_created_at'] = ''
|
||||
if state['base_qty'] + state['added_qty'] == 0:
|
||||
# 中途清仓先重置,后续若又买入,就从零重新累计。
|
||||
state = asdict(StateItem(stock_code=code))
|
||||
if state['base_qty'] + state['added_qty'] == 0:
|
||||
# 全部成交处理完仍无持仓,则删除该股票的策略状态。
|
||||
db.execute('DELETE FROM state WHERE stock_code = ?', (code,))
|
||||
else:
|
||||
# 更新持仓,保留策略状态及已有记录主键。
|
||||
state['status'] = current['status'] if current else state['status']
|
||||
state.pop('id', None)
|
||||
columns = tuple(state)
|
||||
# 没有该股票就新增,已有则更新仓位字段,不替换原记录主键。
|
||||
db.execute(
|
||||
f"INSERT INTO state ({', '.join(columns)}) "
|
||||
f"VALUES ({', '.join(':' + key for key in columns)}) "
|
||||
@@ -221,17 +240,19 @@ class State:
|
||||
+ ', '.join(f'{key} = excluded.{key}' for key in columns if key != 'stock_code'),
|
||||
state,
|
||||
)
|
||||
# 持仓处理成功后才标记成交,防止下次重复加仓或重复扣减。
|
||||
db.execute(
|
||||
'UPDATE deals SET is_arch = 1 WHERE stock_code = ? '
|
||||
'AND is_arch = 0 AND offset_flag IN (23, 24, 48, 49)', (code,)
|
||||
)
|
||||
except (ValueError, sqlite3.IntegrityError) as exc:
|
||||
# 撤销这只股票的全部归档修改,成交仍保持未归档,供下次重试。
|
||||
db.execute('ROLLBACK TO archive_stock')
|
||||
errors[code] = str(exc)
|
||||
log.warning('[归档] %s 失败,保留未归档成交:%s', code, exc)
|
||||
finally:
|
||||
# 释放当前股票的回滚点;整个事务在退出外层 with 时提交。
|
||||
db.execute('RELEASE archive_stock')
|
||||
# 数据库提交完成后刷新内存缓存,让策略读到最新持仓和归档标记。
|
||||
self.load()
|
||||
return errors
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user