update etc/*

This commit is contained in:
2026-09-07 18:18:00 +08:00
parent e320de3241
commit 8eb44440d3
24 changed files with 217 additions and 52 deletions

View File

@@ -3,7 +3,18 @@ qmt_token: QMTbyYanweidong
api_host: http://139.224.247.176:13499 api_host: http://139.224.247.176:13499
qmt_data_dir: D:/qmt_strategy_data qmt_data_dir: D:/qmt_strategy_data
hosts: hosts:
DESKTOP-39H91QV: dev.yaml desktop-t5lh34v: dev.yaml
t8zznqs49f1ju7q: liao.yaml
3zaewgoemkkhvst: zhang.yaml
ba0wpr7xkrbr5l7: hu.yaml
f7tib45aqk4n10h: test.yaml
dpxrcond71s657r: long.yaml
hwoy9gjt1612wq6: wen_ting.yaml
n31pqopr0xlunui: tong_zhao.yaml
3xrfluszqg98sgm: cai_cai.yaml
rfo1dc7c5nn2f1c: fu_xing.yaml
rfo1dc7ucpwgmvk: xiao_dong.yaml
243wwi09wfwllhg: yin_fei.yaml
signals: signals:
dcm: {url: /a/dcm_signal, timezone: "*", gt_last_price_is_open: false} dcm: {url: /a/dcm_signal, timezone: "*", gt_last_price_is_open: false}
morning: {url: /a/morning_signal, timezone: "9:30-10:30", gt_last_price_is_open: true} morning: {url: /a/morning_signal, timezone: "9:30-10:30", gt_last_price_is_open: true}

View File

@@ -0,0 +1,13 @@
account_id: 8886966846
host_key: cai_cai
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

View File

@@ -0,0 +1,13 @@
account_id: 8886120710
host_key: fu_xing
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

13
py-client/etc/hu.yaml Normal file
View File

@@ -0,0 +1,13 @@
account_id: 8889975553
host_key: hu
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

13
py-client/etc/liao.yaml Normal file
View File

@@ -0,0 +1,13 @@
account_id: 8889399698
host_key: liao
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

13
py-client/etc/long.yaml Normal file
View File

@@ -0,0 +1,13 @@
account_id: 8886508526
host_key: long
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

13
py-client/etc/test.yaml Normal file
View File

@@ -0,0 +1,13 @@
account_id: 88017860
host_key: test
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

View File

@@ -0,0 +1,13 @@
account_id: 8886225815
host_key: tong_zhao
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

View File

@@ -0,0 +1,13 @@
account_id: 8886441125
host_key: wen_ting
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

View File

@@ -0,0 +1,13 @@
account_id: 8887377770
host_key: xiao_dong
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

View File

@@ -0,0 +1,13 @@
account_id: 8889292292
host_key: yanweidong
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

View File

@@ -0,0 +1,13 @@
account_id: 8891110937
host_key: yin_fei
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

13
py-client/etc/zhang.yaml Normal file
View File

@@ -0,0 +1,13 @@
account_id: 8889616198
host_key: zhang
buy_value: 5000
min_cash_ratio: 0.10
loss_trigger_pct: -10
grid_step_pct: 1
min_profit_pct: 2
strategy: trend
signal_allow: ["morning","tail","arbitrage"]
enable_loss_add_position: True
enable_auto_ipo: True
excluded_codes:
- "00000.SZ"

View File

@@ -60,6 +60,7 @@ CREATE INDEX IF NOT EXISTS idx_deals_date_time ON deals (trade_date);
# SQL is assembled once; order_local_id is derived from the SDK remark property. # SQL is assembled once; order_local_id is derived from the SDK remark property.
POSITION_FIELDS = tuple(field.name for field in fields(PositionItem)) POSITION_FIELDS = tuple(field.name for field in fields(PositionItem))
DEAL_FIELDS = tuple(field.name for field in fields(DealItem)) + ('order_local_id',) DEAL_FIELDS = tuple(field.name for field in fields(DealItem)) + ('order_local_id',)
POSITION_DEFAULTS = asdict(PositionItem())
POSITION_UPSERT = ( POSITION_UPSERT = (
f"INSERT INTO positions ({', '.join(POSITION_FIELDS)}) " f"INSERT INTO positions ({', '.join(POSITION_FIELDS)}) "
f"VALUES ({', '.join(':' + key for key in POSITION_FIELDS)}) " f"VALUES ({', '.join(':' + key for key in POSITION_FIELDS)}) "
@@ -98,20 +99,17 @@ class OrderBook:
self.positions = positions self.positions = positions
self.deals = deals self.deals = deals
self.deals_sys_ids = set(deals) self.deals_sys_ids = set(deals)
self._deal_count = len(deals)
@staticmethod
def _insert_deals(db: sqlite3.Connection, deals: list[dict]) -> None:
db.executemany(DEAL_INSERT, deals)
@staticmethod @staticmethod
def deal_record(deal: DealItem) -> dict: def deal_record(deal: DealItem) -> dict:
if not deal.order_sys_id or deal.volume <= 0: if not deal.order_sys_id or deal.volume <= 0:
raise ValueError('System order ID and positive volume are required') raise ValueError('System order ID and positive volume are required')
if not deal.local_order_id:
raise ValueError('Local order ID is required')
row = asdict(deal) row = asdict(deal)
row['order_local_id'] = deal.local_order_id row['order_local_id'] = deal.get_local_order_id()
if not row['order_local_id']:
raise ValueError('Local order ID is required')
if any(isinstance(value, float) and not math.isfinite(value) for value in row.values()): if any(isinstance(value, float) and not math.isfinite(value) for value in row.values()):
raise ValueError('Numeric values must be finite') raise ValueError('Numeric values must be finite')
amount = deal.trade_amount if deal.trade_amount > 0 else deal.price * deal.volume amount = deal.trade_amount if deal.trade_amount > 0 else deal.price * deal.volume
@@ -125,35 +123,38 @@ class OrderBook:
return row return row
def sync_deals(self, deals: list[DealItem]) -> None: def sync_deals(self, deals: list[DealItem]) -> None:
new_deals = [] new_deals = {}
seen = self.deals_sys_ids.copy()
for deal in deals: for deal in deals:
if deal.order_sys_id in seen: if deal.order_sys_id not in self.deals_sys_ids and deal.order_sys_id not in new_deals:
continue new_deals[deal.order_sys_id] = self.deal_record(deal)
new_deals.append(self.deal_record(deal))
seen.add(deal.order_sys_id)
if not new_deals: if not new_deals:
return return
with closing(self._connect()) as db, db: with closing(self._connect()) as db, db:
self._insert_deals(db, new_deals) db.executemany(DEAL_INSERT, new_deals.values())
self.load() self.load()
def sync_positions(self, positions: list[PositionItem]) -> None: def sync_positions(self, positions: list[PositionItem]) -> None:
"""Replace the complete position snapshot, retaining IDs for existing stocks.""" """Replace the complete position snapshot, retaining IDs for existing stocks."""
self.save({item.stock_code: asdict(item) for item in positions}, list(self.deals.values())) self.save({item.stock_code: asdict(item) for item in positions})
def save(self, items: dict, deals: list[dict]) -> None: def save(self, items: dict, deals: list[dict] | None = None) -> None:
if len(deals) < self._deal_count: """保存持仓快照,可同时追加成交;省略 deals 时仅更新持仓。"""
new_deals = []
if deals is not None:
if len(deals) < len(self.deals):
raise ValueError('Execution history is append-only') raise ValueError('Execution history is append-only')
new_deals = deals[self._deal_count:] new_deals = deals[len(self.deals):]
positions = [{**asdict(PositionItem()), **item} for item in items.values()] positions = [{**POSITION_DEFAULTS, **item} for item in items.values()]
for row in [*positions, *new_deals]: for row in [*positions, *new_deals]:
if any(isinstance(value, float) and not math.isfinite(value) for value in row.values()): if any(isinstance(value, float) and not math.isfinite(value) for value in row.values()):
raise ValueError('Numeric values must be finite') raise ValueError('Numeric values must be finite')
with closing(self._connect()) as db, db: with closing(self._connect()) as db, db:
for row in db.execute('SELECT stock_code FROM positions').fetchall(): removed = [
if row['stock_code'] not in items: (row['stock_code'],)
db.execute('DELETE FROM positions WHERE stock_code = ?', (row['stock_code'],)) for row in db.execute('SELECT stock_code FROM positions')
if row['stock_code'] not in items
]
db.executemany('DELETE FROM positions WHERE stock_code = ?', removed)
db.executemany(POSITION_UPSERT, positions) db.executemany(POSITION_UPSERT, positions)
self._insert_deals(db, new_deals) db.executemany(DEAL_INSERT, new_deals)
self.load() self.load()

View File

@@ -37,7 +37,7 @@ class OrderItem:
return _side(self.offset_flag) return _side(self.offset_flag)
@property @property
def local_order_id(self) -> str: def get_local_order_id(self) -> str:
return self.remark.split("|", 1)[0] return self.remark.split("|", 1)[0]
@property @property
@@ -68,7 +68,7 @@ class DealItem:
return _side(self.offset_flag) return _side(self.offset_flag)
@property @property
def local_order_id(self) -> str: def get_local_order_id(self) -> str:
return self.remark.split("|", 1)[0] return self.remark.split("|", 1)[0]

View File

@@ -85,18 +85,15 @@ def manage_positions(
elif runtime.account_cfg.enable_loss_add_position: elif runtime.account_cfg.enable_loss_add_position:
loss_add_action = "大盘信号不允许" loss_add_action = "大盘信号不允许"
if pnl_rate >= 0: strTag = "-"
if pnl_rate >= minimum_profit:
strTag = ""
elif pnl_rate< LOSS_TIERS[0]:
strTag = ""
log.info( log.info(
"[Position ↑ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s", "[Position %s ] %s %s,盈亏=%.2f%%,止盈=%s,补仓=%s",
code, strTag,
position.stock_name,
pnl_rate,
profit_action,
loss_add_action,
)
else:
log.info(
"[Position ↓ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s",
code, code,
position.stock_name, position.stock_name,
pnl_rate, pnl_rate,

View File

@@ -73,10 +73,7 @@ class OrderBookTests(unittest.TestCase):
self.assertEqual((book.positions, book.deals, book.deals_sys_ids), ({}, {}, set())) self.assertEqual((book.positions, book.deals, book.deals_sys_ids), ({}, {}, set()))
first = self.deal('base', 'd1', 40, 10, '20260901') first = self.deal('base', 'd1', 40, 10, '20260901')
second = self.deal('base', 'd2', 60, 12) second = self.deal('base', 'd2', 60, 12)
with patch.object(book, '_insert_deals', wraps=book._insert_deals) as insert:
book.sync_deals([first, first, second]) book.sync_deals([first, first, second])
insert.assert_called_once()
self.assertEqual(len(insert.call_args.args[1]), 2)
self.assertEqual(book.deals_sys_ids, {'d1', 'd2'}) self.assertEqual(book.deals_sys_ids, {'d1', 'd2'})
self.assertEqual(book.deals['d1']['trade_date'], '2026-09-01') self.assertEqual(book.deals['d1']['trade_date'], '2026-09-01')
self.assertEqual(book.deals['d2']['volume'], 60) self.assertEqual(book.deals['d2']['volume'], 60)
@@ -84,10 +81,10 @@ class OrderBookTests(unittest.TestCase):
book = OrderBook(self.path) book = OrderBook(self.path)
self.assertEqual(book.deals_sys_ids, {'d1', 'd2'}) self.assertEqual(book.deals_sys_ids, {'d1', 'd2'})
self.assertEqual(book.deals['d1']['order_local_id'], 'zt-base-order1') self.assertEqual(book.deals['d1']['order_local_id'], 'zt-base-order1')
with patch.object(book, '_insert_deals') as insert: with patch.object(book, '_connect') as connect:
book.sync_deals([first, second]) book.sync_deals([first, second])
book.sync_deals([]) book.sync_deals([])
insert.assert_not_called() connect.assert_not_called()
self.assertEqual(len(book.deals), 2) self.assertEqual(len(book.deals), 2)
def test_sync_deals_failure_rolls_back_entire_batch_and_cache(self): def test_sync_deals_failure_rolls_back_entire_batch_and_cache(self):
@@ -169,6 +166,8 @@ class OrderBookTests(unittest.TestCase):
def test_position_columns_defaults_indexes_and_stable_id(self): def test_position_columns_defaults_indexes_and_stable_id(self):
store = OrderBook(self.path) store = OrderBook(self.path)
store.sync_deals([self.deal('base', 'd1', 100, 10)])
saved_deals = dict(store.deals)
with closing(sqlite3.connect(self.path)) as db: with closing(sqlite3.connect(self.path)) as db:
columns = {row[1] for row in db.execute('PRAGMA table_info(positions)')} columns = {row[1] for row in db.execute('PRAGMA table_info(positions)')}
self.assertEqual(columns, {'id', *(field.name for field in fields(PositionItem))}) self.assertEqual(columns, {'id', *(field.name for field in fields(PositionItem))})
@@ -186,6 +185,7 @@ class OrderBookTests(unittest.TestCase):
self.assertEqual(store.positions[position.stock_code]['volume'], 200) self.assertEqual(store.positions[position.stock_code]['volume'], 200)
store.sync_positions([]) store.sync_positions([])
self.assertEqual(store.positions, {}) self.assertEqual(store.positions, {})
self.assertEqual(store.deals, saved_deals)
store.sync_positions([PositionItem(stock_code='600001.SH')]) store.sync_positions([PositionItem(stock_code='600001.SH')])
self.assertGreater(store.positions['600001.SH']['id'], first_id) self.assertGreater(store.positions['600001.SH']['id'], first_id)