fix(factors): extend look-ahead rules to session factors and add intraday-factor guidance

- Rule 7 extended: session-based aggregations (London/NY/Asian) must also
  be shifted by 1 trading day before use — same as daily aggregations
- Rule 8 added: prefer pure intraday rolling factors (RSI, Bollinger, VWAP
  deviation, rolling std) that have no look-ahead risk and vary every minute
- predix_full_eval.py: apply _shift_daily_constant_factor_if_needed before IC
- predix_gen_strategies_real_bt.py: improved swing prompt with daily-level
  signal logic guidance for daily-constant factors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
TPTBusiness
2026-04-24 20:19:07 +02:00
parent dfbbffd7ea
commit ef2a6c5ee0
2 changed files with 29 additions and 4 deletions
+14 -1
View File
@@ -134,7 +134,20 @@ qlib_factor_strategy: |-
# then map back to minute bars via ffill
```
This rule applies to ALL daily aggregations: returns, OHLC stats, volume, momentum, slopes, etc.
Intraday rolling factors (e.g. 30-min rolling std) do NOT need this shift — only daily aggregations do.
**Session-based aggregations (London, NY, Asian session returns) are also daily aggregations** — the London
session (08:00-16:00 UTC) ends at 16:00, so its return must be shifted by 1 day before use.
Intraday rolling factors (e.g. 30-min rolling std computed at bar t using only bars t-N..t-1) do NOT need this shift.
8. **PREFER pure intraday rolling factors**: Factors that use only a trailing window of recent bars (e.g.
rolling(30).mean() of returns, RSI(14), Bollinger Band z-score) have NO look-ahead risk and vary every
minute. These are the best candidates for short-horizon (96-bar) prediction. Examples:
- Rolling 15-min / 30-min / 60-min return momentum
- Rolling volatility (std of returns over 20-60 bars)
- Distance of close from N-bar moving average (z-score)
- RSI or similar oscillators computed on 1-min bars
- VWAP deviation (requires volume — use $volume column)
Always use `.shift(1)` on the lagged window (e.g. `rolling(N).mean().shift(1)`) to avoid using the
current bar's own price in its own feature value.
qlib_factor_output_format: |-
Your output should be a pandas dataframe similar to the following example information:
+15 -3
View File
@@ -250,7 +250,7 @@ Hard requirements:
- NO global mean/std — always use rolling(window).mean() with shift(1) to avoid look-ahead bias"""
else:
system_prompt = f"""You are a quantitative trading expert specializing in EUR/USD intraday strategies.
system_prompt = f"""You are a quantitative trading expert specializing in EUR/USD daily swing strategies.
CRITICAL RULES for {STYLE_DESC} (forward horizon: {FORWARD_BARS} bars = ~{FORWARD_BARS/60:.1f} hours):
1. ONLY use the factors listed below - no others!
@@ -258,15 +258,27 @@ CRITICAL RULES for {STYLE_DESC} (forward horizon: {FORWARD_BARS} bars = ~{FORWAR
3. Create a pandas Series called 'signal' with values: 1 (long), -1 (short), 0 (neutral)
4. signal.index MUST match close.index
5. signal.name must be 'signal'
6. IMPORTANT: factors are DAILY values broadcast to every 1-minute bar — they change once per day.
Use daily-level logic: compare today's factor value to a rolling daily mean (window 5-20 DAYS).
To get daily rolling mean: group by date, take first value per day, compute rolling, then reindex back.
Example: dates = factors[col].index.get_level_values('datetime').normalize()
daily_vals = factors[col].groupby(dates).first()
daily_mean = daily_vals.rolling(10).mean().shift(1)
daily_signal = (daily_vals > daily_mean).astype(int) * 2 - 1
signal = daily_signal.reindex(dates).values (broadcast back to minute bars)
7. The signal should change roughly once per day — this produces ~250-500 trades over 6 years.
8. Keep conditions SIMPLE: one factor above/below its N-day rolling average. Avoid combining 3+ conditions.
Output ONLY valid JSON with these fields:
{{"strategy_name": "short_name", "factor_names": ["f1", "f2"], "description": "one sentence", "code": "python code"}}"""
user_prompt = f"""Create a EUR/USD trading strategy using these factors:
user_prompt = f"""Create a EUR/USD SWING trading strategy (hold ~{FORWARD_BARS/60:.0f} hours) using these factors:
{factor_list}
{f'Previous feedback: {feedback}' if feedback else 'First attempt - be creative!'}"""
{f'Previous feedback: {feedback}' if feedback else 'First attempt - be creative!'}
Use daily-level signal logic (factor above/below rolling daily mean). Signal changes once per day."""
api = APIBackend()
response = api.build_messages_and_create_chat_completion(