@@ -84,6 +84,58 @@ def test_portfolio_invested(self):
8484 assert ".invested" in result .code
8585 assert ".Invested" not in result .code
8686
87+ def test_chained_schedule_api_fixed (self ):
88+ code = (
89+ "self.Schedule.On(self.DateRules.EveryDay(), "
90+ "self.TimeRules.AfterMarketOpen('SPY', 30), self.rebalance)\n "
91+ )
92+ result = lint_qc_code (code )
93+ assert result .had_fixes
94+ assert "self.schedule.on(" in result .code
95+ assert "self.date_rules.every_day()" in result .code
96+ assert "self.time_rules.after_market_open(" in result .code
97+ assert "self.Schedule.On" not in result .code
98+ assert "self.DateRules" not in result .code
99+ assert "self.TimeRules" not in result .code
100+
101+ def test_chained_time_rules_variants (self ):
102+ code = (
103+ "self.Schedule.On(self.DateRules.MonthStart(), "
104+ "self.TimeRules.BeforeMarketClose('SPY', 5), self.close)\n "
105+ )
106+ result = lint_qc_code (code )
107+ assert "self.date_rules.month_start()" in result .code
108+ assert "self.time_rules.before_market_close(" in result .code
109+
110+ def test_chained_every_day_vs_every (self ):
111+ """EveryDay should not partially match Every."""
112+ code = (
113+ "a = self.DateRules.EveryDay()\n "
114+ "b = self.DateRules.Every(DayOfWeek.MONDAY)\n "
115+ )
116+ result = lint_qc_code (code )
117+ assert "self.date_rules.every_day()" in result .code
118+ assert "self.date_rules.every(DayOfWeek" in result .code
119+
120+ def test_rolling_window_add_fixed (self ):
121+ code = (
122+ "self.prices = RollingWindow[float](20)\n "
123+ "self.prices.Add(data['SPY'].close)\n "
124+ )
125+ result = lint_qc_code (code )
126+ assert result .had_fixes
127+ assert "self.prices.add(" in result .code
128+ assert "self.prices.Add(" not in result .code
129+
130+ def test_clean_schedule_api_unchanged (self ):
131+ code = (
132+ "self.schedule.on(self.date_rules.every_day(), "
133+ "self.time_rules.after_market_open('SPY', 30), self.rebalance)\n "
134+ )
135+ result = lint_qc_code (code )
136+ qc001_issues = _issues_by_rule (result , "QC001" )
137+ assert len (qc001_issues ) == 0
138+
87139
88140# ---------------------------------------------------------------------------
89141# QC002 — len() on RollingWindow
@@ -180,20 +232,27 @@ def test_no_action_no_change(self):
180232class TestQC005HistorySlice :
181233 """Warn about C# History iteration patterns."""
182234
183- def test_bars_access_warned (self ):
184- code = "history.Bars[symbol].Close \n "
235+ def test_foreach_warned (self ):
236+ code = "history.ForEach(lambda x: x) \n "
185237 result = lint_qc_code (code )
186238 qc005_issues = _issues_by_rule (result , "QC005" )
187239 assert len (qc005_issues ) == 1
188240 assert qc005_issues [0 ].severity == "warning"
189241 assert not qc005_issues [0 ].fixed
190242
191- def test_foreach_warned (self ):
192- code = "history.ForEach(lambda x: x )\n "
243+ def test_getvalue_warned (self ):
244+ code = "history.GetValue(symbol )\n "
193245 result = lint_qc_code (code )
194246 qc005_issues = _issues_by_rule (result , "QC005" )
195247 assert len (qc005_issues ) == 1
196248
249+ def test_bars_access_not_warned (self ):
250+ """data.Bars[symbol] is valid Slice access in on_data, not a History issue."""
251+ code = "if data.Bars.ContainsKey(self.symbol):\n price = data.Bars[self.symbol].close\n "
252+ result = lint_qc_code (code )
253+ qc005_issues = _issues_by_rule (result , "QC005" )
254+ assert len (qc005_issues ) == 0
255+
197256 def test_clean_history_no_warning (self ):
198257 code = "df = self.history(self.symbol, 20, Resolution.DAILY)\n "
199258 result = lint_qc_code (code )
@@ -319,14 +378,15 @@ def test_full_pascal_algo(self):
319378 " self.spy = spy.Symbol\n "
320379 " self.rsi = self.RSI(self.spy, 14, Resolution.Daily)\n "
321380 " self.prices = RollingWindow[float](20)\n "
322- " self.schedule.on (\n "
323- " self.date_rules.every_day (),\n "
324- " self.time_rules.at(10, 0 ),\n "
381+ " self.Schedule.On (\n "
382+ " self.DateRules.EveryDay (),\n "
383+ " self.TimeRules.AfterMarketOpen('SPY', 30 ),\n "
325384 " Action(self.trade)\n "
326385 " )\n "
327386 "\n "
328387 " def OnData(self, data):\n "
329388 " if self.rsi.IsReady:\n "
389+ " self.prices.Add(data['SPY'].close)\n "
330390 " if len(self.prices) >= 20:\n "
331391 " avg = sum(self.prices.Values) / 20\n "
332392 "\n "
@@ -345,6 +405,16 @@ def test_full_pascal_algo(self):
345405 assert ".invested" in result .code
346406 assert "self.liquidate" in result .code
347407
408+ # QC001 chained API fixes
409+ assert "self.schedule.on(" in result .code
410+ assert "self.date_rules.every_day()" in result .code
411+ assert "self.time_rules.after_market_open(" in result .code
412+ assert "self.Schedule.On" not in result .code
413+
414+ # QC001 .Add() fix
415+ assert "self.prices.add(" in result .code
416+ assert "self.prices.Add(" not in result .code
417+
348418 # QC007 fixes
349419 assert "Resolution.DAILY" in result .code
350420 assert "Resolution.Daily" not in result .code
0 commit comments