FillModel.LimitIfTouchedFill checks the wrong side of the quote (sell vs ask, buy vs bid), so LIT orders fill across the spread

オープン
#10,025 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
55/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
csharp
領域
backend, testing

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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:

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 uses FutureFillModel. This is the only LIT regression algorithm I found on a non-equity security.
  • LimitIfTouchedRegressionAlgorithm / LimitIfTouchedAsyncRegressionAlgorithm (C# and Python) and SplitEquityRegressionAlgorithm trade equities through EquityFillModel. 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 master branch
  • I have confirmed that this is not a duplicate issue by searching issues
  • I have provided detailed steps to reproduce the issue
主要言語
C#
スター
22.5k
フォーク
5.4k
平均マージ
22時間 6分
マージ済み PR(30日)
81

環境構築

Codespaces で開く

このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

QuantConnect/Lean のほかの issue

QuantConnect/Lean の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。