FillModel.LimitIfTouchedFill checks the wrong side of the quote (sell vs ask, buy vs bid), so LIT orders fill across the spread
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 55/100
Línea de trabajo
Start with Common/Orders/Fills/FillModel.cs and compare LimitIfTouchedFill with the equity model behavior described in the issue; the intended rule needs confirmation because the linked documentation disagrees. Read Tests/Common/Orders/Fills/LimitIfTouchedSpreadTests.cs and the existing ImmediateFillModelTests.PerformsLimitIfTouchedFillBuy/Sell cases. Done means the intended quote side is established and tests cover a spread where bid and ask differ.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Expected Behavior
After the trigger is touched, a LIT order should behave like a limit order: a buy fills only when the ask is <= limit, and a sell fills only when the bid is >= limit. That is what EquityFillModel.LimitIfTouchedFill does (ask for buys, bid for sells). The base FillModel already prices this way in LimitFill, StopLimitFill and GetPrices.
Actual Behavior
Common/Orders/Fills/FillModel.cs, LimitIfTouchedFill uses the opposite side of the quote:
// line 659, Sell
if (GetAskPrice(asset, out pricesEndTime) >= order.LimitPrice)
// line 675, Buy
if (GetBidPrice(asset, out pricesEndTime) <= order.LimitPrice)
The order then fills at order.LimitPrice. A buy LIT fills as soon as the bid reaches the limit, even when no seller is offering at that price. A sell LIT fills as soon as the ask reaches the limit. Either way the order fills at its limit price on the far side of the spread.
Example: an option quotes bid 1.00 / ask 1.20. A buy LIT with limit 1.05 fills at 1.05 once triggered, but nobody is offering at 1.05.
This affects every security that uses the base implementation: ImmediateFillModel, FutureFillModel and FutureOptionFillModel, which covers options, futures, future options, forex, crypto, CFDs and indices. Equities are not affected because EquityFillModel overrides the method.
The code has been this way since LIT orders were added in 643e8754 (#5164). The existing tests ImmediateFillModelTests.PerformsLimitIfTouchedFillBuy/Sell use quote bars where bid == ask (101/101/100.5/101 and 100/100/99/99), so they cannot catch which side is used.
Docs: which behaviour is intended?
The documentation does not agree with itself, so I'd like to confirm the intended rule before opening a PR:
- Immediate model page (https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/trade-fills/supported-models/immediate-model) describes the current code: "Buy orders fill when the bid price <= limit price and sell orders fill when the ask price >= limit price." and "The order fills at the limit price."
- Equity model page (https://www.quantconnect.com/docs/v2/writing-algorithms/reality-modeling/trade-fills/supported-models/equity-model), LIT fill table: Buy "Best effort ask price <= limit price", fill price "min(best effort ask price, limit price)"; Sell "Best effort bid price >= limit price", fill price "max(best effort bid price, limit price)".
- LIT order type page (https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/order-types/limit-if-touched-orders) uses both: "fills the order when the bid close price is less than or equal to the limit price, and sets the fill price to the minimum of the ask price and the limit price" (buy), and "fills the order when the ask close price is greater than or equal to the limit price, and sets the fill price to the maximum of the bid price and the limit price" (sell). The base model always fills at the limit price, so this sentence does not match any current implementation.
If the Immediate model page describes the intended behaviour, this is a docs and consistency question rather than a bug. If the Equity model rule is the intended one, I'd propose the fix below.
Potential Solution
Swap the two price getters in FillModel.LimitIfTouchedFill:
- if (GetAskPrice(asset, out pricesEndTime) >= order.LimitPrice)
+ if (GetBidPrice(asset, out pricesEndTime) >= order.LimitPrice)
...
- if (GetBidPrice(asset, out pricesEndTime) <= order.LimitPrice)
+ if (GetAskPrice(asset, out pricesEndTime) <= order.LimitPrice)
I tested this locally. The two new tests below pass, and all 35 LIT-related unit tests (including those two) pass. This issue does not cover whether the fill price should also follow the Equity model (min(ask, limit) / max(bid, limit)) rather than always using the limit price. I can include that change if you want it.
Regression algorithms that may need their statistics updated:
ContinuousFutureLimitIfTouchedOrderRegressionAlgorithm(C#), which usesFutureFillModel. This is the only LIT regression algorithm I found on a non-equity security.LimitIfTouchedRegressionAlgorithm/LimitIfTouchedAsyncRegressionAlgorithm(C# and Python) andSplitEquityRegressionAlgorithmtrade equities throughEquityFillModel. They should not change, but I would re-run them to check.
If you confirm the intended behaviour, I can open a PR with the code change, the tests and any regression algorithm updates. I can also send a matching fix to QuantConnect/Documentation for whichever pages are wrong.
Reproducing the Problem
Unit test Tests/Common/Orders/Fills/LimitIfTouchedSpreadTests.cs uses ImmediateFillModel with SPY minute TradeBar and QuoteBar subscriptions. The spread is set wider than the limit, so neither order should fill:
- Buy LIT, quantity 100, trigger 101.5, limit 100. TradeBar low 99.8 touches the trigger. Quote: bid 99.90, ask 100.30. The ask is above the limit, so the order should not fill. Current code compares the bid (99.90 <= 100) and fills 100 @ 100.
- Sell LIT, quantity -100, trigger 101.5, limit 103. TradeBar high 103.1 touches the trigger. Quote: bid 102.70, ask 103.10. The bid is below the limit, so the order should not fill. Current code compares the ask (103.10 >= 103) and fills -100 @ 103.
Both cases assert OrderStatus.None and fail on master. Both pass with the swap above.
System Information
Linux (kernel 7.0), .NET SDK 10.0.401, Lean master at 9ecfed9.
Checklist
- I have completely filled out this template
- I have confirmed that this issue exists on the current
masterbranch - I have confirmed that this is not a duplicate issue by searching issues
- I have provided detailed steps to reproduce the issue
- Lenguaje dominante
- C#
- Estrellas
- 22.5k
- Forks
- 5.4k
- Merge medio
- 22 h 6 min
- PR fusionados (30 d)
- 81
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Incluye un Dockerfile o un archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de QuantConnect/Lean
-
PDF report generation can overwrite HTML when the destination extension is not lowercase .htmlAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
QuantConnect/Lean#10082 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
QuantConnect/Lean#10059 ·
Los mantenedores suelen responder en 1 día
-
Library Request: highspyAbiertolibrary-request
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
QuantConnect/Lean#10034 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
QuantConnect/Lean#9965 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
QuantConnect/Lean#9955 ·
Los mantenedores suelen responder en 1 día
Todos los issues de QuantConnect/Lean
Issues similares
-
bug emitter:client:csharp
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
microsoft/typespec#12572 · 1 comentario · 1 reacción ·
Los mantenedores suelen responder en 1 día
-
needs-area-label
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 2 días
-
AWS Lambda: KeyNotFoundException drops HTTP-triggered transactions when allowAllHeaders is disabledAbiertobug
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
newrelic/newrelic-dotnet-agent#3949 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
area-networking
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
dotnet/aspnetcore#70545 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 87/100
Los mantenedores suelen responder en 1 día