feat libs,sdk,trend
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -8,15 +8,16 @@ from __future__ import annotations
|
||||
import time
|
||||
import logging as log
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from threading import Lock
|
||||
|
||||
import config
|
||||
from libs.calc import trading_time
|
||||
from libs.market import market_allow_open
|
||||
from libs.overview import Overview
|
||||
from libs.signal import init_signals, SignalItem
|
||||
from libs.collector import collector_push
|
||||
from sdk import Client
|
||||
from sdk import Assets, Client, PositionItem
|
||||
from libs.grid_take_profit import GridTrailingTracker
|
||||
from libs.order import OrderBook
|
||||
from libs.watch import DipWatch
|
||||
@@ -24,6 +25,23 @@ from libs.runtime import Runtime
|
||||
from .open import open_signal
|
||||
from .positions import manage_positions
|
||||
|
||||
_collector_lock = Lock()
|
||||
_collector_snapshot: tuple[str, Assets, list[PositionItem]] | None = None
|
||||
|
||||
|
||||
def _cache_portfolio(account_id: str, assets: Assets, positions: list[PositionItem]) -> None:
|
||||
"""整体替换最新快照,策略线程不执行序列化和网络上报。"""
|
||||
global _collector_snapshot
|
||||
with _collector_lock:
|
||||
_collector_snapshot = (account_id, assets, positions)
|
||||
|
||||
|
||||
def get_collector_snapshot() -> tuple[str, Assets, list[PositionItem]] | None:
|
||||
"""供 scheduler 读取;复制在锁外执行,不阻塞下一轮缓存更新。"""
|
||||
with _collector_lock:
|
||||
snapshot = _collector_snapshot
|
||||
return deepcopy(snapshot)
|
||||
|
||||
|
||||
def StartTrend() -> None:
|
||||
"""初始化趋势策略,并以 30 秒间隔持续执行。"""
|
||||
@@ -37,6 +55,7 @@ def StartTrend() -> None:
|
||||
portfolio = client.portfolio()
|
||||
assets = portfolio.assets
|
||||
positions = list(portfolio.positions.values())
|
||||
_cache_portfolio(config.account_config.account_id, assets, positions)
|
||||
order_book = OrderBook("trend")
|
||||
order_book.refresh(client, portfolio.orders)
|
||||
|
||||
@@ -51,7 +70,7 @@ def StartTrend() -> None:
|
||||
len(signals),
|
||||
len(positions),
|
||||
)
|
||||
executor = ThreadPoolExecutor(max_workers=3, thread_name_prefix="trend")
|
||||
executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="trend")
|
||||
run = Runtime(
|
||||
client=client,
|
||||
global_cfg=config.global_config,
|
||||
@@ -116,22 +135,13 @@ def RunOnce(run: Runtime, signals: list[SignalItem]) -> None:
|
||||
assets = portfolio.assets
|
||||
position_codes = list(portfolio.positions)
|
||||
positions = list(portfolio.positions.values())
|
||||
_cache_portfolio(run.account_cfg.account_id, assets, positions)
|
||||
run.orders.refresh(run.client, portfolio.orders)
|
||||
except Exception:
|
||||
log.exception("[Portfolio] 刷新账户快照失败")
|
||||
return
|
||||
|
||||
futures: list[tuple[str, Future]] = [
|
||||
(
|
||||
"数据提交",
|
||||
run.executor.submit(
|
||||
collector_push,
|
||||
run.account_cfg.account_id,
|
||||
assets,
|
||||
positions,
|
||||
),
|
||||
)
|
||||
]
|
||||
futures: list[tuple[str, Future]] = []
|
||||
|
||||
# 2. 验证可用资金;低于资金安全线时禁止开新仓。
|
||||
allow_open_by_cash = (
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -60,9 +60,9 @@ class TState:
|
||||
@classmethod
|
||||
def _apply_t_deal(cls, item: TStateItem, deal: dict) -> None:
|
||||
"""实时入账与重启恢复共用同一套做 T 轮次计算。"""
|
||||
cls._reset(item, deal['insert_date'])
|
||||
qty, amount = deal['traded_volume'], deal['trade_amount']
|
||||
if deal['side'] == 'SELL':
|
||||
cls._reset(item, deal['trade_date'])
|
||||
qty, amount = deal['volume'], deal['trade_amount']
|
||||
if str(deal['offset_flag']) in ('24', '49'):
|
||||
total = item.sell_qty + qty
|
||||
item.sell_price = (item.sell_qty * item.sell_price + amount) / total
|
||||
item.sell_qty = total
|
||||
@@ -72,32 +72,32 @@ class TState:
|
||||
item.buy_cost = (item.buy_qty * item.buy_cost + amount) / total
|
||||
item.buy_qty = total
|
||||
item.phase = DONE if total >= item.sell_qty else SOLD
|
||||
item.trade_date = deal['insert_date']
|
||||
item.trade_date = deal['trade_date']
|
||||
|
||||
def reconcile(
|
||||
self, positions: list[PositionItem], deals: list[DealItem]
|
||||
) -> None:
|
||||
"""Deduplicate each fill; partial fills do not wait for order completion."""
|
||||
today = datetime.now().date().isoformat()
|
||||
seen = {row['sys_order_id'] for row in self.deals}
|
||||
seen = {row['order_sys_id'] for row in self.deals}
|
||||
rows = []
|
||||
for deal in deals:
|
||||
if not self._is_zt_deal(deal) or deal.sys_order_id in seen:
|
||||
if not self._is_zt_deal(deal) or deal.order_sys_id in seen:
|
||||
continue
|
||||
try:
|
||||
row = self._store.deal_record(deal)
|
||||
except ValueError:
|
||||
continue
|
||||
rows.append(row)
|
||||
seen.add(deal.sys_order_id)
|
||||
rows.sort(key=lambda r: (r['insert_date'], r['insert_time']))
|
||||
seen.add(deal.order_sys_id)
|
||||
rows.sort(key=lambda r: (r['trade_date'], r['trade_time']))
|
||||
modified = False
|
||||
try:
|
||||
# Snapshot includes these fills: subtract their net quantity before replay.
|
||||
net = {}
|
||||
for row in rows:
|
||||
net[row['code']] = net.get(row['code'], 0) + (
|
||||
row['traded_volume'] if row['side'] == 'BUY' else -row['traded_volume']
|
||||
net[row['stock_code']] = net.get(row['stock_code'], 0) + (
|
||||
row['volume'] if str(row['offset_flag']) in ('23', '48') else -row['volume']
|
||||
)
|
||||
for position in positions:
|
||||
code = position.stock_code
|
||||
@@ -115,14 +115,14 @@ class TState:
|
||||
self.items[code] = TStateItem(code, -delta)
|
||||
|
||||
for row in rows:
|
||||
item = self.items.setdefault(row['code'], TStateItem(row['code']))
|
||||
self._reset(item, row['insert_date'])
|
||||
qty, amount = row['traded_volume'], row['trade_amount']
|
||||
if row['local_order_id'].startswith('zt-base-'):
|
||||
item = self.items.setdefault(row['stock_code'], TStateItem(row['stock_code']))
|
||||
self._reset(item, row['trade_date'])
|
||||
qty, amount = row['volume'], row['trade_amount']
|
||||
if row['order_local_id'].startswith('zt-base-'):
|
||||
total = item.base_qty + qty
|
||||
item.base_cost = (item.base_qty * item.base_cost + amount) / total
|
||||
item.base_qty = total
|
||||
item.base_order_id = row['local_order_id']
|
||||
item.base_order_id = row['order_local_id']
|
||||
else:
|
||||
self._apply_t_deal(item, row)
|
||||
self.deals.append(row)
|
||||
@@ -141,15 +141,10 @@ class TState:
|
||||
self._store.save(
|
||||
{
|
||||
code: {
|
||||
'code': item.code,
|
||||
'base_order_id': item.base_order_id,
|
||||
'base_qty': item.base_qty,
|
||||
'base_cost': item.base_cost,
|
||||
'added_order_id': item.added_order_id,
|
||||
'added_num': item.added_num,
|
||||
'added_qty': item.added_qty,
|
||||
'added_cost': item.added_cost,
|
||||
'status': item.phase,
|
||||
'stock_code': item.code,
|
||||
'volume': item.base_qty,
|
||||
'open_price': item.base_cost,
|
||||
'open_cost': item.base_qty * item.base_cost,
|
||||
}
|
||||
for code, item in self.items.items()
|
||||
},
|
||||
@@ -163,19 +158,21 @@ class TState:
|
||||
self._store.load()
|
||||
self.items = {}
|
||||
for code, position in self._store.positions.items():
|
||||
position = dict(position)
|
||||
position['phase'] = position.pop('status')
|
||||
self.items[code] = TStateItem(**position)
|
||||
self.items[code] = TStateItem(code, position['volume'], position['open_price'], id=position['id'])
|
||||
self.deals = [
|
||||
{key: value for key, value in deal.items() if key != 'id'}
|
||||
for deal in self._store.deals.values()
|
||||
]
|
||||
# 轮次明细不占用持仓表字段,从已保存的逐笔成交重建。
|
||||
for deal in self.deals:
|
||||
if not deal['local_order_id'].startswith('zt-base-') and deal['code'] in self.items:
|
||||
self._apply_t_deal(self.items[deal['code']], deal)
|
||||
for code, item in self.items.items():
|
||||
if self._store.positions[code]['status'] == READY:
|
||||
item.phase, item.trade_date = READY, ''
|
||||
item.sell_qty = item.buy_qty = 0
|
||||
item.sell_price = item.buy_cost = 0.0
|
||||
item = self.items.get(deal['stock_code'])
|
||||
if item is None:
|
||||
continue
|
||||
local_order_id = deal['order_local_id']
|
||||
if local_order_id.startswith('zt-base-'):
|
||||
item.base_order_id = local_order_id
|
||||
else:
|
||||
self._apply_t_deal(item, deal)
|
||||
today = datetime.now().date().isoformat()
|
||||
for item in self.items.values():
|
||||
self._reset(item, today)
|
||||
|
||||
Reference in New Issue
Block a user