mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
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:
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user