Skip to content

Support orders placed outside of the algorithm - #53

Merged
Martin-Molinero merged 13 commits into
masterfrom
improve-unknown-order-error-message
Sep 28, 2026
Merged

Martin-Molinero merged 13 commits into
masterfrom
improve-unknown-order-error-message

Conversation

@AlexCatarino

@AlexCatarino AlexCatarino commented Jul 24, 2026 •

Copy link
Copy Markdown
Member

Description

Supports orders placed in the Tradier account outside of the algorithm, like TradeStation, Alpaca and IB do. The fill polling converts an unknown order and offers it through OnNewBrokerageOrderNotification; a brokerage message handler returning true from HandleOrder takes ownership and the order is tracked from then on: Submitted, the fills it already had, and every later update through the regular polling path.

  • Declined orders (the default handler) are only logged and left alone, like TradeStation and Alpaca; the algorithm keeps running.
  • Orders the plugin cannot offer (multileg/combo/oto/oco/otoco classes, credit/debit/even types, a stop whose price could not be fetched) raise a one-time UnprocessableOrderId Warning and are not tracked, as Alpaca does for its multi-leg orders; the algorithm keeps running. Adopting orders with legs on top of Lean's contingent orders (Add contingent orders: OCO, OTO, OUO and brackets Lean#9828) is left for a follow-up.
  • Order placement is no longer retried: a timed out POST can still reach Tradier, so a retry could place the order twice and have the first one offered back as an outside order. A failed placement invalidates the order with an OrderError warning instead of stopping the algorithm.
  • The algorithm is never offered its own order: the verification waits for in-flight placements (a slow reply left the order visible to the poll before its id was on the Lean order) and rechecks the cache and the order provider under the fill lock before offering. Each offered id is verified as it is handled, so a failure later in the batch doesn't re-offer the ones already declined.
  • GetOpenOrders skips the same unsupported orders with a warning instead of failing the launch.
  • A closed order found with a partial execution reports the fill before its final status, so the portfolio applies it.
  • TradierOrderClass gains oto, oco and otoco: one such order in the account failed the whole orders response to deserialize, breaking the fill polling.
  • The unknown-id verification no longer disables itself after a failure, cannot run twice for the same order, and tells a failed fetch apart from an account with no orders.

Related Issue

N/A

Motivation and Context

Every unknown Tradier order id terminated the live algorithm, with no way to reconcile manual or broker-initiated activity.

Requires Documentation Change

No

How Has This Been Tested?

  • Unit tests in TradierBrokerageAditionalTests cover the accepted, declined, unsupported (polling and launch), partial-fill, order-class deserialization and verification-retry paths, the single-attempt order placement, and the own-order and mid-batch failure guards (59 tests pass).
  • Live against the Tradier sandbox with the LEAN Launcher, orders placed through the REST API after Running: a declined resting limit leaves the algorithm running; an accepted one emits Submitted and then Canceled after an API cancel. These runs predate dropping the decline warning and the order POST retries, which are covered by unit tests only.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • Refactor (non-breaking change which improves implementation)
  • Performance (non-breaking change which improves performance)
  • Feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

🤖 Generated with Claude Code

@AlexCatarino AlexCatarino changed the title Improve unknown Tradier order id error message Support orders placed outside of the algorithm Jul 30, 2026
@Romazes
Romazes self-requested a review August 7, 2026 09:55
AlexCatarino and others added 10 commits September 25, 2026 17:36
Explain that unknown order ids are usually caused by manual orders placed
directly on the brokerage account, why LEAN terminates the algorithm, and
how to trade manually safely (QuantConnect IDE/LEAN CLI or stop-and-redeploy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Orders placed directly in the Tradier account are now offered to the
algorithm through the NewBrokerageOrderNotification event, like the other
brokerages do. A custom IBrokerageMessageHandler whose HandleOrder returns
true takes ownership of them: LEAN assigns the order id and we start
tracking the order, emitting its submission, fills and final status. The
default handler ignores them, so unknown order ids keep terminating the
algorithm as before, with the message now pointing at the handler.

Rejected orders are skipped altogether instead of only within a one minute
window: they never reached the account, so there is nothing to track.

An empty response from the intraday order fetch no longer fails the
algorithm for ids it could not inspect. They are left unverified so the
next poll rechecks them, which is also why the error path no longer adds
the ids back to the pending set: doing so blocked the recheck task from
ever firing again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An order we couldn't fetch the details of, or couldn't convert into a Lean
order, was reported with the same message as an order the algorithm was
offered and declined. That message tells the user to set a brokerage
message handler whose HandleOrder returns true, which doesn't help when
the order never reached the handler in the first place.

Both outcomes are now distinguished and get their own message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The notAcceptedOrderIDs declaration was corrupted in the previous commit,
leaving the project uncompilable. Restore it, and use hash sets for both
collections so an order id can never be reported twice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restores the one minute window on the rejected order check: the fill
polling runs every couple of seconds, so a rejection older than that is not
one of ours whose brokerage id we failed to resolve, it was placed outside
of the algorithm and should be offered to it like any other order.

The orders are now kept in a dictionary rather than a set of ids, so the
same lookup serves both this check and the brokerage side order handling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restores the original recentOrders lines on top of a single fetch, instead
of testing the status and transaction date inside the loop, keeping the
diff closer to what the method looked like.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Move the BrokerageSideOrderResult enum to its own file under Models/Enums
- Clone the brokerage order and reset the execution fields instead of
  re-creating it field by field
- Report the order as submitted with the message the other brokerages use
- Cache the order only after it holds its real state: it was being cached
  with no execution progress before its fills were emitted, so a concurrent
  poll could see an already filled order as unfilled and emit them twice
- Cover the unsupported multileg only order types with a test
- Only trace the ids pending a recheck when they change, every poll retries
  them so an unreachable orders endpoint would flood the log
- Add a test for the ids being left unverified after a failed verification,
  which is what lets the next poll fire a new task for them

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ion races

- Declined orders are a one-time Warning and their ids are verified, so
  later polls don't re-offer them; unprocessable ones stay an Error
- Clear the pending id set once the verification task ends, so a second
  task can't offer the same order twice; handle orders under the fill lock
- Reject multileg/combo classes in ConvertOrder; GetOpenOrders skips
  orders it can't convert instead of failing the launch
- Emit the partial fill of closed brokerage-side orders before the final
  status; cache the order before emitting so a failure is recoverable
- TryGetIntradayAndPendingOrders tells a failed request from no orders
- Shorter messages, private result enum, no Clone on the order model

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…were

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…supported orders

An oto, oco or otoco order in the account failed the whole orders response
to deserialize. Unsupported orders placed outside the algorithm now warn
instead of stopping it, like IB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@AlexCatarino
AlexCatarino force-pushed the improve-unknown-order-error-message branch from 72ced1a to e9e72fa Compare September 25, 2026 17:25
An open multileg, combo, oto, oco or otoco order, or a stop whose price
could not be fetched, made GetOpenOrders throw and failed the launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AlexCatarino and others added 2 commits September 28, 2026 17:52
…e orders

A timed out order POST can still reach Tradier, so retrying it could place
the order twice and have the first one offered back as an outside order.
Declined or unsupported outside orders are logged like the other brokerages
do, a generic warning would belong in Lean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pported ones

A slow placement reply left the order visible to the poll before its id was
on the Lean order, so it was offered back as an outside order. The
verification now waits for in-flight placements and rechecks the cache and
order provider under the fill lock before offering. Each offered id is
verified as it's handled, so a failure later in the batch doesn't re-offer
the ones already declined. Unsupported outside orders warn again in both
paths, as Alpaca does; only declined ones are log-only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Martin-Molinero
Martin-Molinero merged commit 9f6eac7 into master Sep 28, 2026
1 check failed
@Martin-Molinero
Martin-Molinero deleted the improve-unknown-order-error-message branch September 28, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants