From a946c5b53da761b10d299188a86ccba9a21f1053 Mon Sep 17 00:00:00 2001 From: yanweidong Date: Sun, 30 Aug 2026 15:29:21 +0800 Subject: [PATCH] feat dev --- .../trend/__pycache__/boot.cpython-311.pyc | Bin 9682 -> 9548 bytes .../trend/__pycache__/order.cpython-311.pyc | Bin 7459 -> 6481 bytes .../trend/__pycache__/state.cpython-311.pyc | Bin 16385 -> 13054 bytes py-client/strategy/trend/boot.py | 5 +- py-client/strategy/trend/order.py | 2 +- py-client/strategy/trend/state.py | 127 ++++-------------- .../__pycache__/test_trend.cpython-311.pyc | Bin 20035 -> 20725 bytes py-client/tests/test_trend.py | 13 +- 8 files changed, 38 insertions(+), 109 deletions(-) diff --git a/py-client/strategy/trend/__pycache__/boot.cpython-311.pyc b/py-client/strategy/trend/__pycache__/boot.cpython-311.pyc index 7d38c505362adeb3057839e89a6bc37e695f9814..400ac1aca0a6c3a9e3b12c6db56838bd5fc826d6 100644 GIT binary patch delta 1401 zcmah}&2Jl35TBRbwb#4e565v5+ws=MF;W_}hBiq@Yj02Cxr2`5BKYYA?kiUZ<;R5+pMWJ#I}Ppf(J z@tYa#%x`yl=E1afSJM;#fFHN9oW7_1tr83U!AE#MVn`wr8v(qhg;T&_l>n>}4&{e{ z8`)O@psb=9JccQ)Th%;oSw}M%Ib<7{G`f9tiAfD}zo@{(a>z?q?EqNo@GJsu2K8VP zFY8+7plH~OKaH;Y+yOkPXSM&3OkyJ5*5kOMpCTGI^;Cwe$^dJ!SE25ApZ9bCMwsAs z0N|*W#1Ug4EC;|nb13d-KBUH3{3{a1k0S~`FfxAmh5}Fz;1eS)iA9M8FllCp4=2nV zE}J`A5CB?!YR^%-wt)H2NG3q-S*JQ%s@N^DSa1te;*emiG+!-L@`Xypd57xwQ)qrr zp~C<`Pw+U&)06yG99pz#{+i=lZz(m`q4q0o!L@O=vtQ&}#O2Nm*~D)b6??bE-doYe^jDX*#GxH=s3{I%DSSqF zXhiXD_&3usC$g}SwI)sBdxK1xxYCvK&G`U6FxS89delL>@J^4VFdvim7J~oue9{wO zfpNGxQdbz1d%Y1hqWeo%jxvcsIrcP40H)yM-mKr8hr0TaiB>d9;`m2bcmEAPKt0IF zI9wg8YfLNmd8sV8L}-?&jRbEL2BsZtgw<$N(8_(hkdMIfi*62^(YT~Dy^-WeDtu_p zk)r&b%v984S(4)3IJh~l34l9(pf}Pyj_*f9cqgXgotQxk{3dqstV$zHq+uq|kUMpN zXTS`mt_lEag6AdR&UiYs5jYxu>~Bf!U1O&2ck(rwi8lwIrG8EbkqbUAI?Ba4F4|1t zRp`rHT*NOEr>~80#>j=mQhvdyxqAc>YPL%`t6S2nQ?zTXkV^|iJ72WjLaCB(Gm^g_ zeSu?qa}J%iT{^}M^X)3NYffd!F6P_0^mQDx#z_bltMyf=6-P^eV)?3Uz~X0O+19sp|mZ;(84&QboI(rHh)9JnF?Jh=(Ksy!?6?cnoIqd0#q@b!d#V@ketJsf!=ow}S1{Oro=mS_>VK7- Qm?Y!^`4j&4qNf4iUp^8==>Px# delta 1417 zcmah|O>7%g5T55CVp>jtJ$1kT}2rp%;@T5eHVQ_ukBW z^S#}fZ|*%&ol-wgRSy8bAKzkb@pJW0FBw9Ya^I12qoK4C4;o4%@#14@EAirAT1+a5 z3@RGF)EF=!LskjE8sP-|DLENbbRNSm6Aw+oLMBd(gWKr^j^!*F{zIuRA_w2+A?W*ge1cO-z$qn9SVuX5(At8>WYOMq%b;$;%)YdVmgdbQ>`5 zQ56wjkc%8e3BbI#6zp^P7NDf8`0($h5NXDZU?ie4G72+8C5>shmi;WIUL~{-`$HiN zg>{JlQ!B0fIwx=#Z{0%#%BSr!`xyYtdGtF;!v8LLP~Ii5?8@*kr^JS5E*=z?X_)7&twT&7mBkiTkW zQuUN)@ovi~@uMEiMwRDj;gUsDb@8jzdZBO_xEyV6nc*z%bUt5CuQ~1P+@&IY4giXN z{3aSOV~W!}f;3jOkeHE<XKS z_fZ!qx)RP44827IRmqqfAP-!ZJeOgfpMrV8U{tvHGfA|hPu_ad5DT1r0Y#6!0{8^< z*9_JW3xdsk<^p0xym+KrQMw~bEdy^?eleinwL`5=@&;sOn=u3fvOar< z8Tu_V04t2mA;1jz!=aF;k`_Os9|eFef5TVQb2y*H`C@MBN*3peIdaw+@->9nVy-wh z+mI*D4GxWFR8n>K9Frz<*Cz8YKg&RU{W+s0PH{2)Ak6*}+PYh0+AJ2^I z#{oc(Q=p>Kk5K3%Pu<7S5IN%M6(~FVC7Jg0cei(xxq2d5<~D<|CWM`S3(D%IH&FTf zuZ~{ROaHnYX$qv%+dZ}^habzybvap8@4d4or#IxZDW@$vxP9M_el@zttx3v;q?nRo zCAoXm^md!x?s`w;T1qy<8x??d=rldqqM+jvJyXtW@>-Nedw(Z5DGS7 Pe|%!`&t75GQvmQE&p2N$ delta 2438 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 None: assets = client.assets() _, positions = client.positions() order_book = OrderBook() - order_book.refresh() + order_book.refresh(client) storeState = State.for_strategy( config.global_config.qmt_data_dir, @@ -149,8 +149,7 @@ def RunOnce(run: Runtime, signals) -> None: previous_state_codes = set(run.state.codes) try: broker_orders = run.client.trade_detail_data("order") - broker_deals = run.client.deals() - run.state.reconcile(positions, broker_orders, broker_deals) + run.state.reconcile(positions, broker_orders) except Exception: logging.exception("订单状态对账失败,本轮禁止自动交易") return diff --git a/py-client/strategy/trend/order.py b/py-client/strategy/trend/order.py index c666cf5..203713b 100644 --- a/py-client/strategy/trend/order.py +++ b/py-client/strategy/trend/order.py @@ -115,6 +115,6 @@ class OrderBook: ) with self.mutex: key = f"{side}-{request.code}" - self.data[key] = pending + self.data.append(pending) self.lock[key] = pending.created_at.timestamp() return True diff --git a/py-client/strategy/trend/state.py b/py-client/strategy/trend/state.py index 8c092fb..f8d3804 100644 --- a/py-client/strategy/trend/state.py +++ b/py-client/strategy/trend/state.py @@ -70,16 +70,6 @@ class State: with self.lock: return list(self.items) - @property - def unresolved_codes(self) -> list[str]: - """返回存在处理中或未知订单状态的证券代码快照。""" - with self.lock: - return [ - code - for code, item in self.items.items() - if _has_unresolved_order(item) - ] - def get(self, code: str) -> StateItem: """获取指定证券的状态;不存在时抛出 KeyError。""" with self.lock: @@ -93,16 +83,8 @@ class State: def delete(self, code: str) -> bool: """删除已终结的证券状态,并返回是否实际删除。""" with self.lock: - item = self.items.get(code) - if item is not None and _has_unresolved_order(item): - return False return self.items.pop(code, None) is not None - def has_unresolved_order(self, code: str) -> bool: - """判断证券是否存在必须阻止自动下单的未决订单。""" - with self.lock: - item = self.items.get(code) - return item is not None and _has_unresolved_order(item) def sync_positions(self, positions: Iterable[PositionItem]) -> None: """把尚未接管的真实持仓初始化为已完成底仓。 @@ -137,20 +119,39 @@ class State: positions: Iterable[PositionItem], orders: list[OrderItem], ) -> None: - """用真实持仓、委托和成交恢复本地状态,不增加持久化字段。""" + """用真实持仓和委托恢复本地状态;拆分订单全部完成才算完成。""" position_list = list(positions) self.sync_positions(position_list) position_codes = { item.stock_code for item in position_list if item.volume > 0 } + orders_by_local_id: dict[str, list[OrderItem]] = {} + for order in orders: + if order.local_order_id: + orders_by_local_id.setdefault(order.local_order_id, []).append(order) + for code in list(self.codes): item = self.get(code) - item.base_status = _reconcile_leg( - item.base_order_id, item.base_status, item.base_qty, orders, deals - ) - item.added_status = _reconcile_leg( - item.added_order_id, item.added_status, item.added_qty, orders, deals - ) + for order_id_attr, status_attr in ( + ("base_order_id", "base_status"), + ("added_order_id", "added_status"), + ): + local_order_id = getattr(item, order_id_attr) + current_status = getattr(item, status_attr) + if ( + current_status not in {STATUS_ING, STATUS_UNKNOWN} + or not local_order_id + ): + continue + + matching_orders = orders_by_local_id.get(local_order_id) + if matching_orders: + status = ( + STATUS_OK + if all(order.status == "56" for order in matching_orders) + else STATUS_ING + ) + setattr(item, status_attr, status) self.set(item) # Opening orders normally have no position until their first fill. Order @@ -194,79 +195,3 @@ class State: } except (TypeError, ValueError) as exc: raise ValueError(f"[状态] 状态字段无效: {exc}") from exc - - -def _reconcile_leg( - local_order_id: str, - current_status: str, - expected_qty: int, - orders: list[OrderItem], - deals: list[dict[str, str]], -) -> str: - if current_status not in {STATUS_ING, STATUS_UNKNOWN} or not local_order_id: - return current_status - matching_orders = [ - order for order in orders if order.local_order_id == local_order_id - ] - order = matching_orders[-1] if matching_orders else None - if order is None: - matching_deals = [ - row for row in deals if _matches_local_order(row, local_order_id) - ] - dealt = sum(_deal_volume(row) for row in matching_deals) - if expected_qty > 0 and dealt >= expected_qty: - return STATUS_OK - if expected_qty <= 0 and matching_deals: - return STATUS_OK - return STATUS_UNKNOWN - - system_order_id = order.id.strip() - matching_deals = [ - row - for row in deals - if ( - system_order_id - and str(row.get("m_strOrderSysID") or "").strip() == system_order_id - ) - or (not system_order_id and _matches_local_order(row, local_order_id)) - ] - dealt = sum(_deal_volume(row) for row in matching_deals) - traded = max(order.traded_volume, dealt) - ordered = order.volume or expected_qty - status = order.status - if ordered > 0 and traded >= ordered: - return STATUS_OK - if status in {"48", "49", "50", "51", "52", "55"}: - return STATUS_ING - if status in {"54", "56"}: - return STATUS_UNKNOWN if traded > 0 else STATUS_CANCELED - if status in {"57", "58"}: - return STATUS_UNKNOWN if traded > 0 else STATUS_FAILED - return STATUS_UNKNOWN - - -def _has_unresolved_order(item: StateItem) -> bool: - return item.base_status in {STATUS_ING, STATUS_UNKNOWN} or item.added_status in { - STATUS_ING, - STATUS_UNKNOWN, - } - - -def _matches_local_order(row: dict[str, str], local_order_id: str) -> bool: - remark = str(row.get("m_strRemark") or "") - return remark.split("|", 1)[0] == local_order_id - - -def _deal_volume(deal: dict[str, str]) -> int: - for key in ("m_nVolume", "m_nTradeVolume", "m_nVolumeTraded"): - volume = _as_int(deal.get(key)) - if volume > 0: - return volume - return 0 - - -def _as_int(value: object) -> int: - try: - return int(value or 0) - except (TypeError, ValueError): - return 0 diff --git a/py-client/tests/__pycache__/test_trend.cpython-311.pyc b/py-client/tests/__pycache__/test_trend.cpython-311.pyc index d3df8efd7d645d6a712729f01cb2a38399dbf6c5..ea67ad1f04f2057bcca1a4d59d2a4e4bc3b854be 100644 GIT binary patch delta 2481 zcma)-eN0nV6u{4YZJ~v>lrL=&q#zRzLF7voWqb%xQ6UOAzeZPhPbe!N-uK)@QFNOb zG4qS-2RLNtro*^om}XhC&5cZ*?vEKtI>(x5beUVsk_k~0=O1G^uN9e;WH&FroOjQ; z=ic+qyXRc)A&*ZJ{U0DEA_lzJXT-b>?jF^q60D$`{lo)F@C(Jp9q7HE!06dC1LGoy190_fW~g~|03 z!pPjEQTtGZ8KHfM(|GJalo-oSOo}@L0Jah!K@F>Sx7MHODbHdoAd7C#%__ch4*YF%4 z#0T?Q9(Wxea83@D}!3vX>+Y0ingXY5q}M{Jmga0DUbwFvP}54h%I)TWND_nKt&6d5W64= zmgXjVtL3eFp~+&k+bvcqSVYMxZ4)ioIXhNXQlW{j6hui(o5oqe|Ms>QZ|^CBRq2*4 z)}@Pc>*7X1&yrHh+HV=-`sVbB{Urk#1Cq0HFk`UQWnAMnuIX5D%M{<2*{|rY8Yprq zo%~?Yb&bok)@@qbQF=!kdZ>JVdDr@`(tguG=}=O>OIzU977S?%Mzn^Xwb8@c=&q^{ zH=NwiCw5o&RJ&sqIU`-#e781#NSp6lJrZgGhXg z(N*r~sv*O=F_l86e*_AhepH5k-kI`$2$tXPMm@Ge0oTs>wrz|0Xs;f#;LL8-Ov#}(XXOxkn%(-x9^yhLD@s>Hc=E#Kwn+Nrp9b>&2 z4ZbDE+4{`YKKTTDoM|PDy_#kA&2+JAS=W4WFT0&RBcK`GZ1NSisL-hu8P9Qsb>uA2 zUBYvyvENWL}HurV+~*6U#zaNih^aU)JmOfe||~AcesgM zMlYlEJz`2E4+<;Dmuy)n#eH#c+=w5jXsL_=bfqO^6&qgP#yL5hI9S*k4Q2{B*(xg%t+*YA)}%Ns%~y$b-9^jEa|4RH_gI~wa> zEI()2KgCJp50+RmH|BX)z&`2i3ISfX^ge4S*+)KejFy;{u@u!O5$6#O#J7k)5r4A< wrANX-RCpIAG2Ss;dW;aYqkdHz$!_FG-YEFzVWT@VeoR(Anr!kqGnLKz7r|bJr~m)} delta 1895 zcmai!aZFof5Ww%FEwqJ}zET)4pe;6|m36~0ST|vt1qO*Q7O5ti%bL1Ax&kfZ>)Q++ zks0S7&W*xOj5AEZMd#d9md-gfnhdjPV$_oG2P4i&OicD4M3!LOqPa&WIN5xE{BrN^ z-hFrX?z`I~?CT+>KcmxW0Ra9S!?umK3;HGl`z^lP(8UarK>CHJN7<&qggz(LpY6}t zmx8lGDbr%1(aKif7GqS1s@zZSbUqPHZK-Q)bh;hscsk3eR76Axf6ThV=5WCDW&WwG zH#|c-PZkXoyV&#F9?tGlux`AYJ19I401*ZN7H$CmPTE~~H_yy^ z;)VIEQ>*rq_E|zBAwcK?fU`=@NiHQwgad>lWJ=+$kbD%+yDX2e7I36&_waS3>b?Is z0ElkGk4o}TU%V3USRAWfq6jMPCigzVF+vfc1pscTNQ&W;R-0vjL>=J-08A+YqBN!P z`Te5b8$J-??bvKD#Pe2j4w8=?@ilT(1|>1X2jjn3TUE9P706H0M^q1glL|4j7T_++T0*^-+47s*~s7$TgagwsfhVXEEE-omx^?DQpRW3#<%!`tLAOlT);1ptSJn@Rq6 z;(U;@SG5LtMB=C8m+YYwtr`Fn0POdpIIT0|*3D(O$7aO7(%pD%bFQgJ5nuuEYy8?E z3$XVT0Ou5VPAFFPqztQZT4TapPQj$PH+hojo&+dZvd~acZcE?Jm+8@2#nS(ex143Y z=^<}K;!lYoA8(Ilc69FbbvPgE33c*tFdT5C^Hi#61!1uRa>cn+mWGRaJHvZ}9irR; zE_dZ3d1q5Yl*=_FqNK^SSmfdDg{fjmj7Z*p0(pbsfVY$TMec3ooguGZ^mRm?9xjIc z9x)8?ssjcsA>s`#CZE*jqOLB+iZx_z^I?|MMQ6x)Xgtq zmAlUHIn9cEU%w#ft7OU}wJq!u{KLJ6b<6T4JYVA$uH3(f$d>UbX=zLk!zctoMF{U$FW*0C@NmDSjf{ApA^i z*WZ6d+i|qs&TiuM`c;LCtBBU{!YWFQ1Os7T2cO1*hL_m~@pl?5N^_XZuM^%Sd`zIm z@?QzFINdm4)T(IMmoPVeq-l^bi03!=u!?;tta=8PXMgU5P&_Le=a#xWgWv6V^l!;r B>ns2O diff --git a/py-client/tests/test_trend.py b/py-client/tests/test_trend.py index 02d37cc..91e54f6 100644 --- a/py-client/tests/test_trend.py +++ b/py-client/tests/test_trend.py @@ -65,7 +65,7 @@ class TrendTests(unittest.TestCase): book.refresh(client) - self.assertEqual(set(book.data), {"BUY-A", "SELL-B"}) + self.assertEqual({item.id for item in book.data}, {"completed"}) self.assertEqual(client.canceled, ["active"]) def test_position_dataclasses_execute_without_type_error(self): @@ -126,15 +126,20 @@ class TrendTests(unittest.TestCase): second = handle_loss(runtime, position, Tick(last_price=5), -50, 5000) self.assertIn("等待", second.message) - def test_reconcile_ing_order_from_deal(self): + def test_reconcile_split_orders_complete_only_when_all_are_status_56(self): with TemporaryDirectory() as directory: state = State.for_strategy(directory, "trend", "A") position = PositionItem(stock_code="A", volume=100, open_price=10) state.set(StateItem("A", base_order_id="local-1", base_status="ING")) + completed = OrderItem("1", "A", "BUY", "", "56", None, 50, "local-1") + processing = OrderItem("2", "A", "BUY", "", "50", None, 50, "local-1") + + state.reconcile([position], [completed, processing]) + self.assertEqual(state.get("A").base_status, "ING") + state.reconcile( [position], - [], - [{"m_strRemark": "local-1|morning"}], + [completed, OrderItem("2", "A", "BUY", "", "56", None, 50, "local-1")], ) self.assertEqual(state.get("A").base_status, STATUS_OK)