Repository navigation
Add OrderEvent synonyms, OrderType and GroupId, OrderFee.Amount and combo ticket helpers - #9896
jhonabreul wants to merge 8 commits into
Conversation
|
Read at head d0a1432 (suite not run). Things that check out:
What I could not find in the test diff is a round trip for the two new fields through Minor: events built with the long |
Python users reach for the Order property names on order events and fees, failing with attribute errors. OrderEvent gets Tag and Time as synonyms of Message and UtcTime, an OrderType property populated from the order on every path and serialized as orderType, and OrderFee gets Amount as a synonym of Value.Amount. OrderType is nullable so events serialized before this change read back as null instead of Market.
…cket helpers Combo order legs expose their group id directly on the order and on the order event, which the transaction handler stamps from the order and which serializes as groupId. GroupOrderExtensions gains GetGroupId and IsFilled on the leg ticket collections returned by the combo order methods, and OrderFee gets a Currency synonym next to Amount.
There was a problem hiding this comment.
Requesting changes: OrderEvent.Tag gives the wrong value, and the branch conflicts with master.
Blocking
OrderEvent.Tagis not the order's tag. It is a synonym ofMessage, the brokerage/exchange message, whileOrder.Tagis the user's tag. The PR's motivation is that Python code written againstOrdershould also work on events. Code that readsorder_event.tagexpecting the tag it set gets something else, with no error. I tested it end to end in a Python backtest on this branch merged with master, callingself.market_order("GOOG", 1, tag="equity-tag"). Inon_order_event,order_event.tagwas''for both the submitted and filled events, whileself.transactions.get_order_by_id(e.order_id).tagwas'equity-tag'. The setter also overwritesMessage, soorder_event.tag = ...replaces the brokerage message in logs and results.TagAndTimeAreAccessibleFromPythondoesn't catch this because it passes"This is a message"as the message and never sets an order tag. Please either dropTagor have it return the order's tag, copying it from the order in the constructor and stamping it inHandleOrderEventsthe wayOrderTypeandGroupIdare, and add a test where the order tag and the message differ.- Merge conflict with master in
Tests/Common/Orders/Fees/OrderFeesTests.cs. Master's pythonnet 2.0.67 update (#9964) addedOrderFeeIsNumericFromPythonto the same place, so this needs a rebase. The current CI ran on the old base with pythonnet 2.0.66. On that base theEditorBrowsablemarkings do nothing: Python.NET binds extension methods to instances only from 2.0.67. So CI hasn't tested that part. Resolving the conflict by keeping both sides is straightforward; I did it locally (results below).
Non-blocking
GetGroupId/IsFilledfrom Python.combo_market_orderreturns a Pythonlistto algorithms (I loggedtype(tickets):<class 'list'>). Sotickets.is_filled()andtickets.get_group_id()fail with'list' object has no attribute 'is_filled'. Only the static form works:GroupOrderExtensions.is_filled(tickets)returnedTrueandGroupOrderExtensions.get_group_id(tickets)returned the group id. Since this is a Python-motivated change, please add a Python test for the form that works, and make sure the docs show that form.- Scope. Marking about 25 engine helpers
EditorBrowsable(Never)undoes something master's pythonnet update introduced: those helpers being bound to instances. That has nothing to do with order-event synonyms. It is backwards compatible (the static forms still work, and I verifiedExtensions.requires_mapping(symbol)), but it would be easier to review, and to revert, as its own PR together with a Python test that the helpers are not bound. The same goes for theStubsIgnoreonOrderTicket'simplicit operator int. - Doc comment on
OrderType(follow-up to the earlier comment): events built with the long constructor, as brokerages do, are also null untilHandleOrderEventsstamps them. The comment only mentions old payloads.
What checks out
- Round-trip serialization of
orderType/groupIdis now covered (RoundTripSerialization), which addresses the earlier review comment. I also checked a protobuf round trip locally:OrderType?andint?withProtoMember(20/21)come back asComboLimit/7, and a market order's event comes back asMarket/null. - On this branch merged with master: the 361 tests in
OrderEventTests,OrderFeesTests,GroupOrderExtensionsTests,OrderTestsandBrokerageTransactionHandlerTestspass. - From Python,
order_event.order_type,group_id,time,order_fee.amount/currencyandorder.group_idreturn the right values.hasattr(symbol, 'requires_mapping')andhasattr(order, 'try_get_group_orders')areFalse. - In the backtest, every event, for both the equity and the combo legs, had
order_type/group_idequal to the order's (MARKET/None,COMBO_MARKET/1). - The added work per order event is two property assignments, which doesn't matter for performance. The Benchmarks failure is not from this change: master fails the same way (run 40723665017,
StatelessCoarseUniverseSelectionBenchmarkand others), and none of the benchmark algorithms that failed place orders.
Description: it lists unit and regression tests but no end-to-end run. Please add one after the fix, with a Python algorithm that sets an order tag and checks order_event.tag against it, and give its outcome.
Other repositories: the Documentation repository needs the new members (listed under Requires Documentation Change). QuantConnect/quantconnect-stubs-generator#104 depends on this PR's EditorBrowsable/StubsIgnore markings, so it should go together with whatever PR ends up carrying them.
Risk: risk:high. This adds to Lean's user-facing algorithm API (OrderEvent, Order, OrderFee and new extension methods) and to the serialized order-event payload.
58f16ff to
a0de170
Compare
|
The PR branch has been rebased from master. The review can be re-done since it has a newer version of Pythonnet |
|
Re-read at head a0de170 (suite not run). My earlier gap is closed: One thing still untested: the protobuf path. |
There was a problem hiding this comment.
Re-reviewed at head a0de170. The only change since my last review (at 58f16ff) is the rebase onto master. The range-diff shows the four feature commits otherwise unchanged, and the pythonnet 2.0.68 commits and their reverts are gone. Still requesting changes: the OrderEvent.Tag issue has not been addressed.
Resolved since last review
- The merge conflict in
OrderFeesTests.csis resolved by keeping both sides.OrderFeeIsNumericFromPythonis still there next to the new tests. GitHub now reports the PR as mergeable. - CI is green on the new base, which uses pythonnet 2.0.67. That means the
EditorBrowsablemarkings now run against the Python.NET version where they matter.
Still blocking
OrderEvent.Tagis not the order's tag (Common/Orders/OrderEvent.cs:150, unchanged). It still gets and setsMessage, the brokerage/exchange message. A Python algorithm that placesmarket_order(..., tag="x")and readsorder_event.taggets''back, with no error. Setting it overwrites the brokerage message. This contradicts the PR's own motivation, which is that names written againstOrdershould work on events. Please either dropTag, or make it the order's tag: copy it in theOrderEvent(Order, ...)constructor and stamp it inHandleOrderEventsthe same wayOrderType/GroupIdare. Add a test where the order tag and the message differ.- No end-to-end run in the description. After the fix, please add a run with a Python algorithm that sets an order tag and checks
order_event.tagagainst it, and give the outcome.
Still open, non-blocking (details in my previous review)
GetGroupId/IsFilled: from Python,combo_market_orderreturns alist, so onlyGroupOrderExtensions.is_filled(tickets)works. Please add a Python test for that form.- Scope: the
EditorBrowsable(Never)markings on about 25 engine helpers and theStubsIgnoreonOrderTicket'simplicit operator intare unrelated to the order-event synonyms. They would be easier to review and revert as a separate PR, with a Python test that the helpers are not bound to instances. No such test exists today, so CI being green doesn't show the markings work. - The
OrderTypedoc comment should mention that events built with the long constructor are null untilHandleOrderEventsstamps them.
Other repositories: the Documentation repository still needs the new members. QuantConnect/quantconnect-stubs-generator#104 depends on the EditorBrowsable/StubsIgnore markings and should ship together with whichever PR carries them.
Risk: I kept risk:high. The PR adds to Lean's user-facing algorithm API (OrderEvent, Order, OrderFee, new extension methods) and to the serialized order-event payload.
|
Pushed ec58c00.
The description is updated accordingly. |
There was a problem hiding this comment.
Re-reviewed at head ec58c00. Since my last review (at a0de170) the EditorBrowsable/StubsIgnore commits were reverted and moved to #9988, and OrderEvent.Tag is now the order's tag. Approving. Both blocking items from my last review are resolved.
Resolved since last review
OrderEvent.Tagis now the order's tag (Common/Orders/OrderEvent.cs:152). TheOrderEvent(Order, ...)constructor copiesorder.Tag, andHandleOrderEventsstamps it next toOrderType/GroupId. The setter is internal, it isJsonIgnore, andMessageis left alone. I checked every engine path that raises an event: all of them either use theOrderconstructor or go throughHandleOrderEvents(BrokerageTransactionHandler.cslines 393, 556, 1097, 1147, 1236, 1253, 1723, 2167, plus brokerage events). Option exercise events built from the order id go throughHandleOrderEventstoo. So every event the algorithm receives carries the tag.HandleOrderEventsreadsorder.Tagat handling time, so an event that arrives after anUpdateOrderFields.Tagupdate shows the new tag. That matches the "order's tag" meaning, andBrokerageOrderEventsCarryTheOrderTypeAndTagtests it. Both writes happen under_lockHandleOrderEvent, and the value is a reference assignment, so a tag update from the algorithm thread at the same moment is safe.- End-to-end run: the author's comment has a Python backtest that tags market, limit and bracket orders, retags and cancels one, and checks
order_event.tagagainstget_order_by_id(...).tagand against the message on every event. Its outcome: every check passed. This run would have failed with the oldMessagesynonym, so it covers the concern. - Scope: the
EditorBrowsable(Never)/StubsIgnoremarkings are gone from this PR. The diff against master now holds only the order-event, order, fee and combo-ticket changes. OrderTypedoc comment now says the value is null for events built from the order id alone until the handler stamps them.
Verified locally at ec58c00
- The 362 tests in
OrderEventTests,OrderFeesTests,GroupOrderExtensionsTests,OrderTestsandBrokerageTransactionHandlerTestspass.
CI: everything passes except Benchmarks, and that failure doesn't come from this change:
- The failing benchmark is
EmptySingleSecuritySecondEquityBenchmark(Python), at 250k dps against a 274.5k threshold. That algorithm places no orders, and the code this PR touches runs only on order events. - Benchmarks passed at 38c45f3. The only commit since then adds the
Tagassignment on the order-event path. - I ran that benchmark locally on master and on this branch, alternating runs. Master gave 190k, 201k and 181k. This branch gave 197k, 189k and 183k. Both fall in the same range.
Non-blocking
Common/Orders/OrderEvent.csnow starts with a UTF-8 BOM that master doesn't have, and no other file inCommon/Ordershas one. It's harmless, but it adds a one-character diff on line 1. Consider dropping it.GetGroupId/IsFilledfrom Python work only in the static form (GroupOrderExtensions.is_filled(tickets)), becausecombo_market_orderreturns a Python list. The author accepted that. The docs should show the static form.
Other repositories: the Documentation repository needs the new members: OrderEvent.Tag, Time, OrderType, GroupId, Order.GroupId, OrderFee.Amount/Currency, and the combo ticket helpers. QuantConnect/quantconnect-stubs-generator#104 now goes with #9988, not this PR.
Risk: risk:high. The PR adds to Lean's user-facing algorithm API (OrderEvent, Order, OrderFee, new extension methods) and to the serialized order-event payload (orderType, groupId). Those are high-risk traits however good the evidence is. The additions are backwards compatible: no existing member changes, and older payloads deserialize with null.
Description
Python users reach for the
Orderproperty names on order events and fees and fail with attribute errors:The change:
OrderEvent.Tagis the order's tag, distinct fromMessage, the brokerage message. TheOrderEvent(Order, ...)constructor copies it andBrokerageTransactionHandler.HandleOrderEventsstamps it, so events after a tag update carry the new tag. It isJsonIgnore: the tag lives on the order, and deserialized events read it back as null.OrderEvent.Timeis a read/write synonym ofUtcTime, markedJsonIgnoreso serialized events are unchanged.OrderEvent.OrderTypeexposes the order's type. TheOrderEvent(Order, ...)constructor copies it from the order andDefaultExerciseModelsets it on exercise events.Order.GroupIdandOrderEvent.GroupIdexpose the combo group id, null for non-combo orders.BrokerageTransactionHandler.HandleOrderEventsstamps the type and group id from the order when attaching the ticket, covering brokerages that build events from the order id alone.SerializedOrderEventcarries both asorderTypeandgroupId, omitted when null, andOrderEvent.FromSerializedrestores them.OrderTypeis nullable, unlikeOrder.Type: payloads written before this change have no type and read back asnullinstead of silently asMarket.GroupOrderExtensions.GetGroupIdandIsFilledonIEnumerable<OrderTicket>track a combo order from the leg tickets the combo order methods return.OrderFee.AmountandOrderFee.Currencyare read-only synonyms ofValue.AmountandValue.Currency, markedJsonIgnore.The
StubsIgnoreandEditorBrowsableattributes for the stubs generator moved to #9988, which ships with QuantConnect/quantconnect-stubs-generator#104.Related Issue
N/A
Motivation and Context
Order events, fees and combo legs should expose the same names as orders so Python code written against
Orderworks on them too.Requires Documentation Change
Yes: document
OrderEvent.Tag,Time,OrderTypeandGroupId,Order.GroupId,OrderFee.AmountandCurrency, and the combo ticket helpers.How Has This Been Tested?
OrderEventTests.TagIsTheOrderTagAndNotTheMessage: an order taggedentrywith messageFilled at stale pricereads back as both; an event built from the order id alone has a null tag.OrderEventTests.TimeIsASynonymOfUtcTime: reads and writes through the synonym.OrderEventTests.TagAndTimeAreAccessibleFromPython:order_event.tag,order_event.messageandorder_event.timefrom a Python module.OrderEventTests.OrderTypeIsTakenFromTheOrderandGroupIdIsTakenFromTheOrder: copied from a stop limit order and a combo order, null otherwise.OrderEventTests.RoundTripSerialization:orderTypeandgroupIdare serialized and restored.OrderEventTests.BackwardsCompatibleDeserialization: a payload without them reads back as null.OrderEventTests.NullOrderTypeAndGroupIdAreNotSerialized: null values emit no key through the converter or plain serialization.OrderEventTests.JsonIgnoresandRoundTripSerialization: the tag and theTimesynonym never appear in serialized output, a deserialized event's tag is null.BrokerageTransactionHandlerTests.BrokerageOrderEventsCarryTheOrderTypeAndTagand...TheGroupId: brokerage events built from the order id reach the algorithm with the order's type, tag and group id, keeping their own message; afterUpdateOrderFields.Tagthe fill event carries the new tag.OrderTests.GroupIdComesFromTheGroupOrderManager: null for a market order, the manager id for a combo order.GroupOrderExtensionsTests.GetGroupIdOfTheLegTicketsandIsFilledOnlyWhenEveryLegTicketIsFilled.OrderFeesTests.AmountAndCurrencyAreSynonymsOfTheValueAmountAndCurrencyandAmountIsAccessibleFromPython: values and serialization unchanged,order_fee.amountfrom Python.OptionExerciseAssignRegressionAlgorithmandComboMarketOrderAlgorithm: unchanged statistics.order_event.tagagainstget_order_by_id(...).tagon every event. Log in the comments.Types of changes
Checklist:
bug-<issue#>-<description>orfeature-<issue#>-<description>