diff --git a/py-client/etc/_global.yaml b/py-client/etc/_global.yaml index 14dcd85..4ccfb88 100644 --- a/py-client/etc/_global.yaml +++ b/py-client/etc/_global.yaml @@ -3,7 +3,18 @@ qmt_token: QMTbyYanweidong api_host: http://139.224.247.176:13499 qmt_data_dir: D:/qmt_strategy_data 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: 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} diff --git a/py-client/etc/cai_cai.yaml b/py-client/etc/cai_cai.yaml new file mode 100644 index 0000000..0a4e119 --- /dev/null +++ b/py-client/etc/cai_cai.yaml @@ -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" diff --git a/py-client/etc/fu_xing.yaml b/py-client/etc/fu_xing.yaml new file mode 100644 index 0000000..154d9d7 --- /dev/null +++ b/py-client/etc/fu_xing.yaml @@ -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" diff --git a/py-client/etc/hu.yaml b/py-client/etc/hu.yaml new file mode 100644 index 0000000..32c83e0 --- /dev/null +++ b/py-client/etc/hu.yaml @@ -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" diff --git a/py-client/etc/liao.yaml b/py-client/etc/liao.yaml new file mode 100644 index 0000000..852a3e8 --- /dev/null +++ b/py-client/etc/liao.yaml @@ -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" diff --git a/py-client/etc/long.yaml b/py-client/etc/long.yaml new file mode 100644 index 0000000..2d3f539 --- /dev/null +++ b/py-client/etc/long.yaml @@ -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" diff --git a/py-client/etc/test.yaml b/py-client/etc/test.yaml new file mode 100644 index 0000000..2f76efd --- /dev/null +++ b/py-client/etc/test.yaml @@ -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" diff --git a/py-client/etc/tong_zhao.yaml b/py-client/etc/tong_zhao.yaml new file mode 100644 index 0000000..346ecc3 --- /dev/null +++ b/py-client/etc/tong_zhao.yaml @@ -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" diff --git a/py-client/etc/wen_ting.yaml b/py-client/etc/wen_ting.yaml new file mode 100644 index 0000000..756ec8e --- /dev/null +++ b/py-client/etc/wen_ting.yaml @@ -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" diff --git a/py-client/etc/xiao_dong.yaml b/py-client/etc/xiao_dong.yaml new file mode 100644 index 0000000..8a0854e --- /dev/null +++ b/py-client/etc/xiao_dong.yaml @@ -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" diff --git a/py-client/etc/yanweidong.yaml b/py-client/etc/yanweidong.yaml new file mode 100644 index 0000000..b289d10 --- /dev/null +++ b/py-client/etc/yanweidong.yaml @@ -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" diff --git a/py-client/etc/yin_fei.yaml b/py-client/etc/yin_fei.yaml new file mode 100644 index 0000000..9533e5c --- /dev/null +++ b/py-client/etc/yin_fei.yaml @@ -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" diff --git a/py-client/etc/zhang.yaml b/py-client/etc/zhang.yaml new file mode 100644 index 0000000..0526a0d --- /dev/null +++ b/py-client/etc/zhang.yaml @@ -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" diff --git a/py-client/libs/__pycache__/order.cpython-311.pyc b/py-client/libs/__pycache__/order.cpython-311.pyc index d7b3905..6af5403 100644 Binary files a/py-client/libs/__pycache__/order.cpython-311.pyc and b/py-client/libs/__pycache__/order.cpython-311.pyc differ diff --git a/py-client/libs/__pycache__/orderbook.cpython-311.pyc b/py-client/libs/__pycache__/orderbook.cpython-311.pyc index 8ee035d..ee891c4 100644 Binary files a/py-client/libs/__pycache__/orderbook.cpython-311.pyc and b/py-client/libs/__pycache__/orderbook.cpython-311.pyc differ diff --git a/py-client/libs/__pycache__/overview.cpython-311.pyc b/py-client/libs/__pycache__/overview.cpython-311.pyc index 8ae6cf7..2d13030 100644 Binary files a/py-client/libs/__pycache__/overview.cpython-311.pyc and b/py-client/libs/__pycache__/overview.cpython-311.pyc differ diff --git a/py-client/libs/orderbook.py b/py-client/libs/orderbook.py index 5adef1b..8f3b689 100644 --- a/py-client/libs/orderbook.py +++ b/py-client/libs/orderbook.py @@ -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. POSITION_FIELDS = tuple(field.name for field in fields(PositionItem)) DEAL_FIELDS = tuple(field.name for field in fields(DealItem)) + ('order_local_id',) +POSITION_DEFAULTS = asdict(PositionItem()) POSITION_UPSERT = ( f"INSERT INTO positions ({', '.join(POSITION_FIELDS)}) " f"VALUES ({', '.join(':' + key for key in POSITION_FIELDS)}) " @@ -98,20 +99,17 @@ class OrderBook: self.positions = positions self.deals = 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 def deal_record(deal: DealItem) -> dict: if not deal.order_sys_id or deal.volume <= 0: 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['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()): raise ValueError('Numeric values must be finite') amount = deal.trade_amount if deal.trade_amount > 0 else deal.price * deal.volume @@ -125,35 +123,38 @@ class OrderBook: return row def sync_deals(self, deals: list[DealItem]) -> None: - new_deals = [] - seen = self.deals_sys_ids.copy() + new_deals = {} for deal in deals: - if deal.order_sys_id in seen: - continue - new_deals.append(self.deal_record(deal)) - seen.add(deal.order_sys_id) + if deal.order_sys_id not in self.deals_sys_ids and deal.order_sys_id not in new_deals: + new_deals[deal.order_sys_id] = self.deal_record(deal) if not new_deals: return with closing(self._connect()) as db, db: - self._insert_deals(db, new_deals) + db.executemany(DEAL_INSERT, new_deals.values()) self.load() def sync_positions(self, positions: list[PositionItem]) -> None: """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: - if len(deals) < self._deal_count: - raise ValueError('Execution history is append-only') - new_deals = deals[self._deal_count:] - positions = [{**asdict(PositionItem()), **item} for item in items.values()] + def save(self, items: dict, deals: list[dict] | None = None) -> None: + """保存持仓快照,可同时追加成交;省略 deals 时仅更新持仓。""" + new_deals = [] + if deals is not None: + if len(deals) < len(self.deals): + raise ValueError('Execution history is append-only') + new_deals = deals[len(self.deals):] + positions = [{**POSITION_DEFAULTS, **item} for item in items.values()] for row in [*positions, *new_deals]: if any(isinstance(value, float) and not math.isfinite(value) for value in row.values()): raise ValueError('Numeric values must be finite') with closing(self._connect()) as db, db: - for row in db.execute('SELECT stock_code FROM positions').fetchall(): - if row['stock_code'] not in items: - db.execute('DELETE FROM positions WHERE stock_code = ?', (row['stock_code'],)) + removed = [ + (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) - self._insert_deals(db, new_deals) + db.executemany(DEAL_INSERT, new_deals) self.load() diff --git a/py-client/sdk/__pycache__/models.cpython-311.pyc b/py-client/sdk/__pycache__/models.cpython-311.pyc index 7e15380..dca7610 100644 Binary files a/py-client/sdk/__pycache__/models.cpython-311.pyc and b/py-client/sdk/__pycache__/models.cpython-311.pyc differ diff --git a/py-client/sdk/__pycache__/portfolio.cpython-311.pyc b/py-client/sdk/__pycache__/portfolio.cpython-311.pyc index a04fd18..126fe38 100644 Binary files a/py-client/sdk/__pycache__/portfolio.cpython-311.pyc and b/py-client/sdk/__pycache__/portfolio.cpython-311.pyc differ diff --git a/py-client/sdk/models.py b/py-client/sdk/models.py index 3180177..a260b70 100644 --- a/py-client/sdk/models.py +++ b/py-client/sdk/models.py @@ -37,7 +37,7 @@ class OrderItem: return _side(self.offset_flag) @property - def local_order_id(self) -> str: + def get_local_order_id(self) -> str: return self.remark.split("|", 1)[0] @property @@ -68,7 +68,7 @@ class DealItem: return _side(self.offset_flag) @property - def local_order_id(self) -> str: + def get_local_order_id(self) -> str: return self.remark.split("|", 1)[0] diff --git a/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc b/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc index 8225d93..a9fa5e9 100644 Binary files a/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc and b/py-client/strategy/trend/__pycache__/positions.cpython-311.pyc differ diff --git a/py-client/strategy/trend/positions.py b/py-client/strategy/trend/positions.py index 16dea12..3169d83 100644 --- a/py-client/strategy/trend/positions.py +++ b/py-client/strategy/trend/positions.py @@ -85,24 +85,21 @@ def manage_positions( elif runtime.account_cfg.enable_loss_add_position: loss_add_action = "大盘信号不允许" - if pnl_rate >= 0: - log.info( - "[Position ↑ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s", - code, - position.stock_name, - pnl_rate, - profit_action, - loss_add_action, - ) - else: - log.info( - "[Position ↓ ] 代码=%s,名称=%s,盈亏=%.2f%%,止盈=%s,补仓=%s", - code, - position.stock_name, - pnl_rate, - profit_action, - loss_add_action, - ) + strTag = "-" + if pnl_rate >= minimum_profit: + strTag = "↑" + elif pnl_rate< LOSS_TIERS[0]: + strTag = "↓" + + log.info( + "[Position %s ] %s %s,盈亏=%.2f%%,止盈=%s,补仓=%s", + strTag, + code, + position.stock_name, + pnl_rate, + profit_action, + loss_add_action, + ) except Exception: log.exception( "[Position] 持仓处理异常,代码=%s,继续处理后续持仓", diff --git a/py-client/strategy/zt/__pycache__/state.cpython-311.pyc b/py-client/strategy/zt/__pycache__/state.cpython-311.pyc index 996f1a3..9423dcf 100644 Binary files a/py-client/strategy/zt/__pycache__/state.cpython-311.pyc and b/py-client/strategy/zt/__pycache__/state.cpython-311.pyc differ diff --git a/py-client/tests/test_orderbook.py b/py-client/tests/test_orderbook.py index 9c644a4..af1b039 100644 --- a/py-client/tests/test_orderbook.py +++ b/py-client/tests/test_orderbook.py @@ -73,10 +73,7 @@ class OrderBookTests(unittest.TestCase): self.assertEqual((book.positions, book.deals, book.deals_sys_ids), ({}, {}, set())) first = self.deal('base', 'd1', 40, 10, '20260901') 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]) - insert.assert_called_once() - self.assertEqual(len(insert.call_args.args[1]), 2) + book.sync_deals([first, first, second]) self.assertEqual(book.deals_sys_ids, {'d1', 'd2'}) self.assertEqual(book.deals['d1']['trade_date'], '2026-09-01') self.assertEqual(book.deals['d2']['volume'], 60) @@ -84,10 +81,10 @@ class OrderBookTests(unittest.TestCase): book = OrderBook(self.path) self.assertEqual(book.deals_sys_ids, {'d1', 'd2'}) 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([]) - insert.assert_not_called() + connect.assert_not_called() self.assertEqual(len(book.deals), 2) 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): 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: columns = {row[1] for row in db.execute('PRAGMA table_info(positions)')} 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) store.sync_positions([]) self.assertEqual(store.positions, {}) + self.assertEqual(store.deals, saved_deals) store.sync_positions([PositionItem(stock_code='600001.SH')]) self.assertGreater(store.positions['600001.SH']['id'], first_id)