LLM sometimes copies the .reset_index(level=N, drop=True) suffix from
groupby().rolling().method() patterns and adds it after .transform(),
but transform() already preserves the original index. The extra
reset_index() drops an index level and causes ValueError: 'cannot reindex
on an axis with duplicate labels' or shape mismatch on assignment.
Detect: any line containing both .transform( and .reset_index(level=..., drop=True)
Fix: strip the .reset_index() suffix from those lines.
Adds 1 new test (test_transform_reset_index_stripped) — total 30 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. _fix_instrument_column_access: var['instrument'] = EXPR was incorrectly
converted to var.index.get_level_values(1) = EXPR, producing a SyntaxError
('cannot assign to function call'). Added (?!\s*=) negative lookahead to
skip assignment targets.
2. _fix_groupby_column_on_multiindex: groupby(['instrument','date']) on a
reset_index() variable was converted to groupby([var.index.get_level_values...])
but reset_index() produces a plain RangeIndex, not a MultiIndex, causing
AttributeError: 'RangeIndex' has no attribute 'normalize'. Added reset_vars
guard to skip variables produced by reset_index().
Adds 1 new test (test_assignment_target_not_touched) — total 29 tests, all passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. groupby(level=['instrument','date']) → get_level_values() — string level
names like 'date' don't exist in the (datetime, instrument) MultiIndex;
replaced with get_level_values(0).normalize() + get_level_values(1).
2. groupby(level=['date','instrument']) — symmetric fix for reversed order.
3. groupby(level=['instrument']) → groupby(level=1) — single string level.
4. groupby(level=N)['col'].apply(lambda) → transform(lambda) — apply() on a
grouped Series prepends an extra index level, causing index shape mismatch
when assigned back; transform() preserves the original index.
5. df.loc[instrument] DateParseError fix (instrument_loc_multiindex) — already
committed, adding supporting tests for groupby(level=['instrument','date']).
Adds 5 new tests (TestGroupbyLevelStringNames, TestGroupbyApplyToTransform)
— total 28 tests, all passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When LLM iterates over instruments via get_level_values('instrument').unique()
and then does df.loc[instrument], pandas tries to parse the instrument string
('EURUSD') as a datetime against level-0 of the (datetime, instrument) index,
raising DateParseError.
Fix: detect loop variables bound to get_level_values(1) or get_level_values('instrument')
and replace DF.loc[loop_var] (read) with DF.xs(loop_var, level=1). Assignment
write-backs are left untouched to avoid complex rewrites.
Adds 4 new tests (TestInstrumentLocMultiindex) — total 23 tests, all passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LLM-generated code often accesses df['instrument'] as a column, but
'instrument' is an index level (level 1) in the MultiIndex DataFrame.
Replace with df.index.get_level_values(1) except when the variable
was created via reset_index() (where the column actually exists).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LLM generates invalid Python by putting keyword args inside lists:
df.groupby([level=1, 'date']) ← SyntaxError
Also fixes the regex for the chained groupby Pattern A/B which had
an unescaped ')' causing re.error that silently reverted the fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The fixer was raising min_periods to match window size, which causes
all-NaN output for intraday factors with 96 bars/day — window=240 means
zero valid bars per day, window=60 means 61% NaN per day. Critics were
consistently flagging this as incorrect for intraday factors. The LLM
now controls its own min_periods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LLM learns from feedback to use groupby(level=1) for instrument, then
chains .groupby('date') to add the date dimension — but DataFrameGroupBy
has no .groupby() method, causing AttributeError at runtime.
Replace the invalid chain with a correct two-level groupby using
index.get_level_values(), consistent with the existing instrument+date fix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous fixer converted groupby(['instrument','date']) → groupby(level=1),
stripping the date level. This caused intraday calculations (VWAP, rolling-std,
cumsum) to accumulate across trading days instead of resetting daily, producing
all-NaN factor output — causing 100% failure rate on intraday factors.
New behaviour: capture the DataFrame variable name and emit:
var.groupby([var.index.get_level_values(1),
var.index.get_level_values(0).normalize()])
which groups by (instrument, day) as originally intended.
Adds test/qlib/test_auto_fixer.py covering all fixer cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The LLM generates x.rolling(window=N, ddof=1).std() where ddof is passed
to rolling() instead of std() — pandas raises TypeError on any ddof in rolling().
Fix both forms: rolling(..., ddof=N) and rolling(...).std(ddof=N).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _fix_reset_index_groupby: replace groupby(level=N) on reset_index'd variables
with groupby('instrument') — fixes ValueError: level > 0 only valid with MultiIndex
- _fix_groupby_mixed_levels: strip string level names from groupby(level=[int, 'str'])
to fix AssertionError: Level 'date' not in index
- _fix_groupby_column_on_multiindex: convert groupby(['instrument','date']) on
MultiIndex DataFrames to groupby(level=1) — fixes KeyError on column access
- _fix_rolling_ddof: remove unsupported ddof kwarg from rolling().std()/var()
- fix(proposal): apply history compression to factor_proposal.py (was causing
131k-token prompts from QlibFactorHypothesis2Experiment; pycache had stale .pyc)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>