This commit is contained in:
2026-09-11 10:44:34 +08:00
parent 4e693d773a
commit 6e554e1565
10 changed files with 140 additions and 36 deletions

View File

@@ -2,8 +2,10 @@
import logging as log
import time
from contextlib import closing
from datetime import datetime
from pathlib import Path
from tempfile import TemporaryDirectory
import config
from libs.calc import trading_time
@@ -45,7 +47,8 @@ def StartZT() -> None:
log.exception('[ZT] 本轮失败,下一轮重试')
time.sleep(30 - time.time() % 30)
# 收盘后补记最后一轮成交,不再下单。
sync_account_state(state, list(client.portfolio().positions.values()), client.deals())
deals = client.deals()
sync_account_state(state, list(client.portfolio().positions.values()), deals)
def RunOnce(run: Runtime, state: State, signals: list[SignalItem]) -> None:
@@ -85,12 +88,31 @@ def sync_account_state(
) -> None:
"""初次持仓作为底仓;后续只按成交减仓,避免延迟快照删除持仓。"""
zt_deals = [d for d in deals if d.get_local_order_id.startswith('zt-')]
state.sync_deals(zt_deals)
if initialize:
state.sync_state(positions)
if state.state or state.deals:
raise ValueError('ZT 初始化仅允许空状态库,禁止覆盖已有持仓和成交')
# 在同目录临时库完成初始化,再原子替换空库,避免中途退出留下半份快照。
with TemporaryDirectory(dir=state.path.parent, prefix='zt-init-') as directory:
initial = State(Path(directory) / 'state.db')
initial.sync_deals(zt_deals)
initial.sync_state(positions)
# 初始快照已包含历史成交;保留成交计算做 T 欠仓,但不再累加持仓。
with closing(initial._connect()) as db, db:
db.execute('UPDATE deals SET is_arch = 1')
initial.path.replace(state.path)
state.load()
return
errors = state.archiving(base_order_prefix='zt-base-')
if errors:
raise ValueError(f'ZT 成交归档失败:{errors}')
state.sync_deals(zt_deals)
# 在外层统一归档:新增成交写入后处理,也重试此前失败的未归档成交。
state.archiving()
# 从刷新后的成交缓存检查失败记录,避免归档未完成时继续交易。
pending = sorted({d['stock_code'] for d in state.deals.values() if d['is_arch'] == 0})
if pending:
raise ValueError(f'ZT 成交归档未完成:{pending},原因见归档日志')
traded = {d['stock_code'] for d in state.deals.values()}
state.sync_state([p for p in positions if p.stock_code not in traded], remove_missing=False)
# sync_state 要求完整持仓。保留成交账本中的现有仓位,避免延迟快照删仓;
# 只从账户快照补入没有策略成交历史的股票,避免刚卖完又被旧快照重建。
holdings = [PositionItem(stock_code=code, volume=row['base_qty'] + row['added_qty'])
for code, row in state.state.items()]
holdings.extend(p for p in positions if p.stock_code not in traded and p.stock_code not in state.state)
state.sync_state(holdings)

View File

@@ -42,7 +42,7 @@ def open_signal(run: Runtime, ticks, signals, available: float) -> float:
# 等待价格从观察低点反弹,防止直接接下跌中的“飞刀”。
if not run.open_watch.triggered("ZT 建仓", item.code, price):
continue
order_id = run.orders.new_order_id("ZT","base")
order_id = run.orders.new_order_id("zt", "base")
request = PlaceOrderRequest(
OP_BUY, item.code, volume, order_id, "zt"
)

View File

@@ -74,7 +74,7 @@ def manage_positions(
if not force_buy_back and not run.add_watch.triggered('ZT 买回', code, price):
continue
available -= amount
request = PlaceOrderRequest(OP_BUY, code, volume, run.orders.new_order_id('t-buy'), 'zt')
request = PlaceOrderRequest(OP_BUY, code, volume, run.orders.new_order_id('zt', 't-buy'), 'zt')
if run.orders.place(run.client, request):
run.add_watch.forget(code)
log.info('[ZT 买回] %s %d%s', code, volume, ',尾盘买回' if force_buy_back else '')
@@ -93,7 +93,7 @@ def manage_positions(
volume = int(min(position.can_use_volume, recorded * run.account_cfg.zt_sell_ratio)) // 100 * 100
if volume < (200 if code.startswith('688') else 100):
continue
request = PlaceOrderRequest(OP_SELL, code, volume, run.orders.new_order_id('t-sell'), 'zt')
request = PlaceOrderRequest(OP_SELL, code, volume, run.orders.new_order_id('zt', 't-sell'), 'zt')
if run.orders.place(run.client, request):
log.info('[ZT 卖出] %s %d 股,按实际成交买回', code, volume)
except Exception: