Skip to content

fix: populate Tick.Exchange on streaming Alpaca ticks#57

Merged
Romazes merged 6 commits into
QuantConnect:masterfrom
kshep:fix/populate-tick-exchange-and-conditions
Apr 22, 2026
Merged

fix: populate Tick.Exchange on streaming Alpaca ticks#57
Romazes merged 6 commits into
QuantConnect:masterfrom
kshep:fix/populate-tick-exchange-and-conditions

Conversation

@kshep

@kshep kshep commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Description

Populate Tick.Exchange on streaming trade and quote ticks from the Alpaca SIP feed by mapping the single-letter SIP exchange code to a Lean Exchange name.

Related PR(s)

Depends on Lean PR #9422 (adds equity-tape mappings for S, T, V, H, U to Exchanges.GetPrimaryExchange). Without it, those Alpaca SIP codes fall through to Exchange.UNKNOWN here (and T is conflated with NASDAQ).

Related Issue

Sale-condition follow-up: #62.

Motivation and Context

Alpaca's SDK exposes the SIP exchange tag on every trade/quote, but HandleTradeReceived / HandleQuoteReceived were dropping it. Without the venue, microstructure strategies cannot distinguish lit exchange prints from off-exchange (FINRA/ADF) prints or compute venue-aware volume profiles.

Implementation

At the two tick construction sites in AlpacaBrokerage.Messaging.cs, set Tick.Exchange directly from the SIP code via Lean's GetPrimaryExchange(SecurityType).Name:

// HandleTradeReceived
Exchange = obj.Exchange.GetPrimaryExchange(subscriptionData.Symbol.SecurityType).Name,

// HandleQuoteReceived
Exchange = obj.AskExchange.GetPrimaryExchange(subscriptionData.Symbol.SecurityType).Name,

Codes with no Lean counterpart resolve to Exchange.UNKNOWN.Name (empty string), which matches Tick.Exchange's default.

The Alpaca Exchange codes: doc

{
  "A": "NYSE American (AMEX)",
  "B": "NASDAQ OMX BX",
  "C": "National Stock Exchange",
  "D": "FINRA ADF",
  "E": "Market Independent",
  "H": "MIAX",
  "I": "International Securities Exchange",
  "J": "Cboe EDGA",
  "K": "Cboe EDGX",
  "L": "Long Term Stock Exchange",
  "M": "Chicago Stock Exchange",
  "N": "New York Stock Exchange",
  "P": "NYSE Arca",
  "Q": "NASDAQ OMX",
  "S": "NASDAQ Small Cap",
  "T": "NASDAQ Int",
  "U": "Members Exchange",
  "V": "IEX",
  "W": "CBOE",
  "X": "NASDAQ OMX PSX",
  "Y": "Cboe BYX",
  "Z": "Cboe BZ"
}

Mappings below assume Lean PR #9422 has been merged.

Alpaca code Alpaca exchange name Lean accepted codes Lean Exchange enum Match status
A NYSE American (AMEX) A, AMEX Exchange.AMEX ✅ Exact
B NASDAQ OMX BX B, NASDAQ BX, NASDAQ_BX Exchange.NASDAQ_BX ✅ Exact
C National Stock Exchange C, NSX, NSE Exchange.NSX (US market) ✅ Exact (market-aware)
D FINRA ADF D, FINRA Exchange.FINRA ✅ Exact
E Market Independent Not mapped ❌ None
H MIAX H Exchange.MIAX_PEARL ✅ Exact
I International Securities Exchange I, ISE Exchange.ISE ✅ Exact
J Cboe EDGA J, EDGA Exchange.EDGA ✅ Exact
K Cboe EDGX K, EDGX Exchange.EDGX ✅ Exact
L Long Term Stock Exchange L, LTSE Exchange.LTSE ✅ Exact
M Chicago Stock Exchange M, CSE Exchange.CSE ✅ Exact (CHX → CSE)
N New York Stock Exchange N, NYSE Exchange.NYSE ✅ Exact
P NYSE Arca P, ARCA Exchange.ARCA ✅ Exact
Q NASDAQ OMX Q, T, NASDAQ, NASDAQ_OMX Exchange.NASDAQ ✅ Exact
S NASDAQ Small Cap S, NASDAQ_SC Exchange.NASDAQ_SC ✅ Exact
T NASDAQ Int T, NASDAQ_INT Exchange.NASDAQ_INT ✅ Exact
U Members Exchange U, MM, MEMX Exchange.MEMX ✅ Exact
V IEX IEX Exchange.IEX ✅ Exact
W CBOE W, CBOE Exchange.CBOE ✅ Exact
X NASDAQ OMX PSX X, NASDAQ PSX, NASDAQ_PSX Exchange.NASDAQ_PSX ✅ Exact
Y Cboe BYX Y, BYX, BATS Y, BATS_Y Exchange.BATS_Y ✅ Exact
Z Cboe BZ Z, BATS, BATS Z, BATS_Z Exchange.BATS ✅ Exact

Tick.SaleCondition is intentionally not populated. Alpaca's SIP conditions are tape-specific single-character codes ("@", "I", "T"), not a hex uint, so Tick.ParsedSaleCondition would throw FormatException. A tape-aware decoder that folds Alpaca codes into Lean's TradeConditionFlags / QuoteConditionFlags is tracked in #62.

Requires Documentation Change

N/A

How Has This Been Tested?

Live streaming test with Alpaca SIP (Algo Trader Plus) subscription via LEAN from source.

Before: exch= empty on all ticks
TRADE #1: AAPL px=254.45 sz=121.0 exch= sus=False

After:
TRADE #1: AAPL px=254.11 sz=4.0  exch=FINRA  sus=False
TRADE #3: AAPL px=254.02 sz=5.0  exch=EDGA   sus=False
QUOTE #1: AAPL bid=254.00x100.0 ask=254.15x100.0 exch=NASDAQ

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • My code follows the code style of this project.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.
  • My branch follows the naming convention bug-<issue#>-<description> or feature-<issue#>-<description>

kshep and others added 2 commits March 26, 2026 14:33
- add alpaca exchange code dictionaries in AlpacaBrokerageExtensions
- use TryGetExchange in trade/quote handlers to set Tick.Exchange
- map Alpaca SIP exchange code via Exchanges.GetPrimaryExchange with hard-coded fallbacks (H, S, U, V)
- drop SaleCondition assignment; Alpaca codes are tape-specific and not valid hex for ParsedSaleCondition (tracked in QuantConnect#62)
@Romazes Romazes changed the title Fix: populate Exchange and SaleCondition on streaming Tick objects fix: populate Tick.Exchange on streaming Alpaca ticks Apr 20, 2026
@Romazes Romazes requested a review from Martin-Molinero April 20, 2026 20:56
@Romazes Romazes self-assigned this Apr 20, 2026
- convert GetExchange into a string extension taking SecurityType
- delegate to Lean's GetPrimaryExchange(SecurityType), drop S/V hard-codes
- map "E" (Market Independent) explicitly to Exchange.UNKNOWN
Romazes added 2 commits April 21, 2026 14:52
- drop H/U mappings, delegate to GetPrimaryExchange for remaining codes
- call GetPrimaryExchange(SecurityType).Name directly at tick sites
- remove now-redundant GetExchange wrapper

@Martin-Molinero Martin-Molinero left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@Romazes Romazes merged commit 95e39b4 into QuantConnect:master Apr 22, 2026
@kshep

kshep commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for cranking this out, @Romazes !

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