feat(agents): estendi input LLM con 4 statistiche regime-aware + directive v2.1
Aggiunge al USER_TEMPLATE dell'HypothesisAgent 4 metriche calcolate su rolling
window 500 barre (no backward bias del full sample):
- autocorr_lag1_recent vs autocorr_lag1_baseline (AR(1) delta vs storico)
- hurst_recent (R/S analysis, persistenza di scala)
- vol_percentile (0-100, posizione vol corrente nella distribuzione storica)
- seasonality_hour, seasonality_dow (0-1, varianza spiegata da feature temporali)
Razionale: skew/kurt da soli sono ambigui — un AR(1) discrimina momentum vs
mean-reversion meglio di tutta la guidance sui momenti.
NEW funzioni in metrics/basic.py:
- autocorr_lag1(returns)
- hurst_exponent(returns) via R/S a scale multiple
- vol_percentile_historical(returns, current_window=24, ref_window=2000)
- seasonality_strength(returns, by={"hour"|"dow"})
MarketSummary esteso con 6 nuovi campi (con default); build_market_summary calcola
rolling-500 per "recent", full sample per "baseline".
prompts.json v2.1: tutte le 7 directive estese con frase di interpretazione
style-specific dei 4 nuovi input (no style collapse). Es:
- physicist: "AR(1) sopra baseline = sistema con memoria fuori equilibrio"
- engineer: "AR(1) > 0.05 con std contenuta = SNR favorevole"
- psychologist: "AR(1) recente >> baseline = euforia coordinata"
Tests: +16 unit per le metriche, +1 smoke per MarketSummary populated.
Verifica: 207 test pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,12 @@ class MarketSummary:
|
||||
skew: float
|
||||
kurtosis: float
|
||||
volatility_regime: str
|
||||
autocorr_lag1_recent: float = 0.0 # AR(1) ultimi 500 bar
|
||||
autocorr_lag1_baseline: float = 0.0 # AR(1) full sample (riferimento)
|
||||
hurst_recent: float = 0.5 # Hurst ultimi 500 bar
|
||||
vol_percentile: float = 50.0 # 0-100 percentile della vol corrente
|
||||
seasonality_hour: float = 0.0 # 0-1 varianza spiegata da hour
|
||||
seasonality_dow: float = 0.0 # 0-1 varianza spiegata da dow
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -181,6 +187,12 @@ Statistiche return: mean={return_mean:.5f}, std={return_std:.5f}, \
|
||||
skew={skew:.3f}, kurt={kurtosis:.3f}.
|
||||
Regime volatilità: {volatility_regime}.
|
||||
|
||||
Regime recente (ultime 500 barre):
|
||||
autocorr_lag1: {autocorr_lag1_recent:.3f} (baseline storica: {autocorr_lag1_baseline:.3f})
|
||||
hurst: {hurst_recent:.3f} (0.5 = random walk, >0.55 trending, <0.45 mean rev)
|
||||
vol_pct: {vol_percentile:.0f}° percentile storico
|
||||
stagionalita: hour={seasonality_hour:.2f}, dow={seasonality_dow:.2f} (0-1, varianza spiegata)
|
||||
|
||||
Feature accessibili dal tuo genoma: {feature_access}.
|
||||
Lookback massimo che puoi usare nel ragionamento: {lookback_window} barre.
|
||||
|
||||
@@ -294,6 +306,12 @@ class HypothesisAgent:
|
||||
skew=market.skew,
|
||||
kurtosis=market.kurtosis,
|
||||
volatility_regime=market.volatility_regime,
|
||||
autocorr_lag1_recent=market.autocorr_lag1_recent,
|
||||
autocorr_lag1_baseline=market.autocorr_lag1_baseline,
|
||||
hurst_recent=market.hurst_recent,
|
||||
vol_percentile=market.vol_percentile,
|
||||
seasonality_hour=market.seasonality_hour,
|
||||
seasonality_dow=market.seasonality_dow,
|
||||
feature_access=", ".join(genome.feature_access),
|
||||
lookback_window=genome.lookback_window,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,12 @@ from __future__ import annotations
|
||||
import pandas as pd # type: ignore[import-untyped]
|
||||
from scipy import stats # type: ignore[import-untyped]
|
||||
|
||||
from ..metrics.basic import (
|
||||
autocorr_lag1,
|
||||
hurst_exponent,
|
||||
seasonality_strength,
|
||||
vol_percentile_historical,
|
||||
)
|
||||
from .hypothesis import MarketSummary
|
||||
|
||||
|
||||
@@ -24,6 +30,16 @@ def build_market_summary(
|
||||
else:
|
||||
regime = "high"
|
||||
|
||||
recent_window = min(500, len(returns))
|
||||
recent_returns = returns.iloc[-recent_window:]
|
||||
|
||||
autocorr_recent = autocorr_lag1(recent_returns)
|
||||
autocorr_baseline = autocorr_lag1(returns)
|
||||
hurst_r = hurst_exponent(recent_returns)
|
||||
vol_pct = vol_percentile_historical(returns, current_window=24, ref_window=2000)
|
||||
season_h = seasonality_strength(returns, by="hour")
|
||||
season_d = seasonality_strength(returns, by="dow")
|
||||
|
||||
return MarketSummary(
|
||||
symbol=symbol,
|
||||
timeframe=timeframe,
|
||||
@@ -33,4 +49,10 @@ def build_market_summary(
|
||||
skew=skew,
|
||||
kurtosis=kurt,
|
||||
volatility_regime=regime,
|
||||
autocorr_lag1_recent=autocorr_recent,
|
||||
autocorr_lag1_baseline=autocorr_baseline,
|
||||
hurst_recent=hurst_r,
|
||||
vol_percentile=vol_pct,
|
||||
seasonality_hour=season_h,
|
||||
seasonality_dow=season_d,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,122 @@ import numpy as np
|
||||
import pandas as pd # type: ignore[import-untyped]
|
||||
|
||||
|
||||
def autocorr_lag1(returns: pd.Series) -> float:
|
||||
"""Autocorrelazione dei ritorni con lag 1 (correlazione di Pearson)."""
|
||||
if len(returns) < 2:
|
||||
return 0.0
|
||||
val = returns.autocorr(lag=1)
|
||||
return float(val) if pd.notna(val) else 0.0
|
||||
|
||||
|
||||
def hurst_exponent(returns: pd.Series) -> float:
|
||||
"""Hurst exponent via R/S analysis (rescaled range).
|
||||
|
||||
Range 0-1: 0.5 random walk, >0.55 trending persistente, <0.45 mean reverting.
|
||||
Implementazione classica con scale multiple (2^k bins).
|
||||
"""
|
||||
n = len(returns)
|
||||
if n < 100:
|
||||
return 0.5 # insufficiente dati, ritorna random-walk default
|
||||
|
||||
series = returns.dropna().values
|
||||
n = len(series)
|
||||
if n < 100:
|
||||
return 0.5
|
||||
|
||||
# Scale: 2^k che dividono n in segmenti
|
||||
scales = [2**k for k in range(4, int(np.log2(n // 2)) + 1)]
|
||||
if not scales:
|
||||
return 0.5
|
||||
|
||||
rs_values: list[float] = []
|
||||
for scale in scales:
|
||||
n_chunks = n // scale
|
||||
if n_chunks < 1:
|
||||
continue
|
||||
rs_chunk: list[float] = []
|
||||
for i in range(n_chunks):
|
||||
chunk = series[i * scale : (i + 1) * scale]
|
||||
mean = chunk.mean()
|
||||
cumdev = (chunk - mean).cumsum()
|
||||
r = cumdev.max() - cumdev.min()
|
||||
s = chunk.std()
|
||||
if s > 0:
|
||||
rs_chunk.append(r / s)
|
||||
if rs_chunk:
|
||||
rs_values.append(float(np.mean(rs_chunk)))
|
||||
|
||||
if len(rs_values) < 2:
|
||||
return 0.5
|
||||
|
||||
log_scales = np.log(scales[: len(rs_values)])
|
||||
log_rs = np.log(rs_values)
|
||||
# Hurst = slope della regressione log-log
|
||||
h, _ = np.polyfit(log_scales, log_rs, 1)
|
||||
return float(np.clip(h, 0.0, 1.0))
|
||||
|
||||
|
||||
def vol_percentile_historical(
|
||||
returns: pd.Series,
|
||||
current_window: int = 24,
|
||||
ref_window: int = 2000,
|
||||
) -> float:
|
||||
"""Percentile (0-100) della vol corrente nella distribuzione storica.
|
||||
|
||||
Vol = std rolling su current_window barre. Confronta l'ultimo valore contro
|
||||
la distribuzione dei valori della stessa std rolling sugli ultimi ref_window.
|
||||
Output: 0 (vol attuale tra le piu basse), 100 (tra le piu alte).
|
||||
"""
|
||||
if len(returns) < max(current_window, 100):
|
||||
return 50.0
|
||||
|
||||
rolling_vol = returns.rolling(current_window, min_periods=current_window).std()
|
||||
rolling_vol = rolling_vol.dropna()
|
||||
if len(rolling_vol) < 10:
|
||||
return 50.0
|
||||
|
||||
# Limita ref_window all'effettiva disponibilita
|
||||
ref = rolling_vol.iloc[-ref_window:] if len(rolling_vol) > ref_window else rolling_vol
|
||||
current = float(rolling_vol.iloc[-1])
|
||||
pct = float((ref < current).sum()) / len(ref) * 100.0
|
||||
return pct
|
||||
|
||||
|
||||
def seasonality_strength(
|
||||
returns: pd.Series,
|
||||
by: str,
|
||||
) -> float:
|
||||
"""Frazione di varianza dei ritorni spiegata dalla feature temporale `by`.
|
||||
|
||||
`by` in {"hour", "dow"}. Output 0-1: 0 = no seasonality, 1 = tutta la varianza
|
||||
e dovuta al ciclo. Calcolato come 1 - (var residua / var totale) usando i
|
||||
gruppi indotti dalla feature.
|
||||
"""
|
||||
if not isinstance(returns.index, pd.DatetimeIndex):
|
||||
return 0.0
|
||||
if len(returns) < 50:
|
||||
return 0.0
|
||||
|
||||
if by == "hour":
|
||||
groups = returns.index.hour
|
||||
elif by == "dow":
|
||||
groups = returns.index.dayofweek
|
||||
else:
|
||||
raise ValueError(f"by deve essere 'hour' o 'dow', non {by!r}")
|
||||
|
||||
total_var = float(returns.var())
|
||||
if total_var <= 0:
|
||||
return 0.0
|
||||
|
||||
grouped = returns.groupby(groups)
|
||||
group_means = grouped.transform("mean")
|
||||
residuals = returns - group_means
|
||||
residual_var = float(residuals.var())
|
||||
|
||||
explained = 1.0 - (residual_var / total_var)
|
||||
return float(np.clip(explained, 0.0, 1.0))
|
||||
|
||||
|
||||
def sharpe_ratio(returns: pd.Series, periods_per_year: int = 8760, rf: float = 0.0) -> float:
|
||||
"""Sharpe annualizzato. periods_per_year=8760 per dati orari."""
|
||||
excess = returns - rf / periods_per_year
|
||||
|
||||
@@ -31,3 +31,35 @@ def test_volatility_regime_high_for_volatile() -> None:
|
||||
)
|
||||
s = build_market_summary(df, symbol="BTC/USDT", timeframe="1h")
|
||||
assert s.volatility_regime in {"medium", "high"}
|
||||
|
||||
|
||||
def test_build_summary_new_fields_populated() -> None:
|
||||
"""I 6 nuovi campi devono essere float nei range attesi."""
|
||||
idx = pd.date_range("2024-01-01", periods=600, freq="1h", tz="UTC")
|
||||
np.random.seed(42)
|
||||
close = 100 + np.cumsum(np.random.normal(0, 1, 600))
|
||||
df = pd.DataFrame(
|
||||
{"open": close, "high": close + 0.5, "low": close - 0.5, "close": close, "volume": 1.0},
|
||||
index=idx,
|
||||
)
|
||||
s = build_market_summary(df, symbol="ETH/USDT", timeframe="1h")
|
||||
|
||||
# autocorr fields: float in [-1, 1]
|
||||
assert isinstance(s.autocorr_lag1_recent, float)
|
||||
assert isinstance(s.autocorr_lag1_baseline, float)
|
||||
assert -1.0 <= s.autocorr_lag1_recent <= 1.0
|
||||
assert -1.0 <= s.autocorr_lag1_baseline <= 1.0
|
||||
|
||||
# hurst: float in [0, 1]
|
||||
assert isinstance(s.hurst_recent, float)
|
||||
assert 0.0 <= s.hurst_recent <= 1.0
|
||||
|
||||
# vol_percentile: float in [0, 100]
|
||||
assert isinstance(s.vol_percentile, float)
|
||||
assert 0.0 <= s.vol_percentile <= 100.0
|
||||
|
||||
# seasonality fields: float in [0, 1]
|
||||
assert isinstance(s.seasonality_hour, float)
|
||||
assert isinstance(s.seasonality_dow, float)
|
||||
assert 0.0 <= s.seasonality_hour <= 1.0
|
||||
assert 0.0 <= s.seasonality_dow <= 1.0
|
||||
|
||||
@@ -2,7 +2,15 @@ import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from multi_swarm_core.metrics.basic import max_drawdown, sharpe_ratio, total_return
|
||||
from multi_swarm_core.metrics.basic import (
|
||||
autocorr_lag1,
|
||||
hurst_exponent,
|
||||
max_drawdown,
|
||||
seasonality_strength,
|
||||
sharpe_ratio,
|
||||
total_return,
|
||||
vol_percentile_historical,
|
||||
)
|
||||
|
||||
|
||||
def test_sharpe_zero_returns():
|
||||
@@ -38,3 +46,123 @@ def test_max_drawdown_known_curve():
|
||||
def test_total_return():
|
||||
eq = pd.Series([100.0, 110.0, 105.0, 120.0])
|
||||
assert total_return(eq) == pytest.approx(0.20)
|
||||
|
||||
|
||||
# --- New metrics tests ---
|
||||
|
||||
|
||||
def test_autocorr_lag1_short_series():
|
||||
"""Serie corta ritorna 0.0 senza errori."""
|
||||
r = pd.Series([0.01])
|
||||
assert autocorr_lag1(r) == 0.0
|
||||
|
||||
|
||||
def test_autocorr_lag1_all_nan():
|
||||
"""Serie di NaN ritorna 0.0."""
|
||||
r = pd.Series([float("nan")] * 10)
|
||||
assert autocorr_lag1(r) == 0.0
|
||||
|
||||
|
||||
def test_autocorr_lag1_range():
|
||||
"""Autocorrelazione sempre in [-1, 1]."""
|
||||
np.random.seed(7)
|
||||
r = pd.Series(np.random.normal(0, 0.01, 500))
|
||||
val = autocorr_lag1(r)
|
||||
assert -1.0 <= val <= 1.0
|
||||
|
||||
|
||||
def test_autocorr_lag1_trending_positive():
|
||||
"""Serie con autocorrelazione positiva artificiale deve dare val > 0."""
|
||||
# costruiamo una serie con forte AR(1): x_t = 0.8*x_{t-1} + noise
|
||||
np.random.seed(1)
|
||||
n = 300
|
||||
x = np.zeros(n)
|
||||
for i in range(1, n):
|
||||
x[i] = 0.8 * x[i - 1] + np.random.normal(0, 0.1)
|
||||
r = pd.Series(x)
|
||||
assert autocorr_lag1(r) > 0.5
|
||||
|
||||
|
||||
def test_hurst_short_series():
|
||||
"""Serie < 100 elementi ritorna 0.5 (random-walk default)."""
|
||||
r = pd.Series(np.random.normal(0, 0.01, 50))
|
||||
assert hurst_exponent(r) == pytest.approx(0.5)
|
||||
|
||||
|
||||
def test_hurst_range():
|
||||
"""Hurst sempre in [0, 1]."""
|
||||
np.random.seed(3)
|
||||
r = pd.Series(np.random.normal(0, 0.01, 500))
|
||||
h = hurst_exponent(r)
|
||||
assert 0.0 <= h <= 1.0
|
||||
|
||||
|
||||
def test_hurst_random_walk_approx_half():
|
||||
"""Random walk iid deve avere Hurst vicino a 0.5."""
|
||||
np.random.seed(42)
|
||||
r = pd.Series(np.random.normal(0, 0.01, 2000))
|
||||
h = hurst_exponent(r)
|
||||
assert 0.3 <= h <= 0.7
|
||||
|
||||
|
||||
def test_vol_percentile_short_series():
|
||||
"""Serie troppo corta ritorna 50.0."""
|
||||
r = pd.Series(np.random.normal(0, 0.01, 10))
|
||||
assert vol_percentile_historical(r) == pytest.approx(50.0)
|
||||
|
||||
|
||||
def test_vol_percentile_range():
|
||||
"""Percentile sempre in [0, 100]."""
|
||||
np.random.seed(5)
|
||||
r = pd.Series(np.random.normal(0, 0.01, 500))
|
||||
p = vol_percentile_historical(r, current_window=24, ref_window=200)
|
||||
assert 0.0 <= p <= 100.0
|
||||
|
||||
|
||||
def test_vol_percentile_high_vol_at_end():
|
||||
"""Vol molto alta alla fine deve dare percentile elevato."""
|
||||
np.random.seed(9)
|
||||
low_vol = np.random.normal(0, 0.001, 400)
|
||||
high_vol = np.random.normal(0, 0.05, 100)
|
||||
r = pd.Series(np.concatenate([low_vol, high_vol]))
|
||||
p = vol_percentile_historical(r, current_window=24, ref_window=400)
|
||||
assert p > 70.0
|
||||
|
||||
|
||||
def test_seasonality_strength_no_datetimeindex():
|
||||
"""Senza DatetimeIndex ritorna 0.0."""
|
||||
r = pd.Series(np.random.normal(0, 0.01, 200))
|
||||
assert seasonality_strength(r, by="hour") == 0.0
|
||||
|
||||
|
||||
def test_seasonality_strength_short_series():
|
||||
"""Serie < 50 elementi ritorna 0.0."""
|
||||
idx = pd.date_range("2024-01-01", periods=30, freq="1h", tz="UTC")
|
||||
r = pd.Series(np.random.normal(0, 0.01, 30), index=idx)
|
||||
assert seasonality_strength(r, by="hour") == 0.0
|
||||
|
||||
|
||||
def test_seasonality_strength_range():
|
||||
"""Risultato sempre in [0, 1]."""
|
||||
np.random.seed(11)
|
||||
idx = pd.date_range("2024-01-01", periods=500, freq="1h", tz="UTC")
|
||||
r = pd.Series(np.random.normal(0, 0.01, 500), index=idx)
|
||||
val = seasonality_strength(r, by="hour")
|
||||
assert 0.0 <= val <= 1.0
|
||||
|
||||
|
||||
def test_seasonality_strength_dow_range():
|
||||
"""Stagionalita dow sempre in [0, 1]."""
|
||||
np.random.seed(13)
|
||||
idx = pd.date_range("2024-01-01", periods=500, freq="1h", tz="UTC")
|
||||
r = pd.Series(np.random.normal(0, 0.01, 500), index=idx)
|
||||
val = seasonality_strength(r, by="dow")
|
||||
assert 0.0 <= val <= 1.0
|
||||
|
||||
|
||||
def test_seasonality_strength_invalid_by():
|
||||
"""by non valido solleva ValueError."""
|
||||
idx = pd.date_range("2024-01-01", periods=100, freq="1h", tz="UTC")
|
||||
r = pd.Series(np.random.normal(0, 0.01, 100), index=idx)
|
||||
with pytest.raises(ValueError, match="'minute'"):
|
||||
seasonality_strength(r, by="minute")
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"_comment": "Stili cognitivi e direttive del system_prompt per il GA di strategy_crypto. Modifica liberamente: cambia 'directive' di uno stile esistente o aggiungi nuove voci a 'styles'. Il nome dello stile (key) viene usato come 'cognitive_style' del genoma; la 'directive' diventa il system_prompt iniziale.",
|
||||
"_schema": "2.1",
|
||||
"_changelog": "v2.1 - directive estese con interpretazione style-specific dei 4 nuovi input statistici (autocorr_lag1, hurst, vol_pct, seasonality). v2.0 - Riprogettato per blind-generator GA. Directive medio-compatte (~700 char) che orientano l'esplorazione cognitiva senza prescrivere indicatori specifici (lascia evolvere il GA). Mappate sulle 4 statistiche disponibili: mean, std, skew, kurtosis + volatility_regime. Rimosse ecologist (richiede multi-asset), game_theorist/epidemiologist (richiedono info esterne non visibili all'agente). Tenute 7 lenti che mappano bene sulle statistiche aggregate.",
|
||||
"_design_notes": "Le directive sono BIAS DI ESPLORAZIONE, non template. Suggeriscono cosa cercare nei 4 momenti e quali archetipi di strategia preferire, lasciando al GA la scoperta della combinazione esatta di indicatori e soglie. Sono pensate per essere riscritte dall'operatore mutate_prompt_llm mantenendo coerenza con la lente.",
|
||||
"styles": {
|
||||
"physicist": {
|
||||
"directive": "Pensa come un fisico: il mercato e un sistema con leggi di conservazione e regimi di scala. Leggi std come dispersione energetica e kurtosis come densita di eventi estremi (kurt alta = fat tails, sistema fuori equilibrio). Cerca simmetrie nei ritorni (skew ≈0 = sistema simmetrico) e rotture (skew marcato = forzante asimmetrica). AR(1) positivo significativamente sopra baseline = sistema con memoria fuori equilibrio, momentum legittimo; Hurst > 0.55 conferma persistenza di scala; vol percentile alto + kurt bassa = energia immagazzinata non ancora rilasciata. Preferisci ritorno all'equilibrio in regime simmetrico/basso vol, propagazione (momentum/breakout) in regime asimmetrico/alto vol. Pattern coerenti su piu lookback sono robusti, pattern singoli sono rumore."
|
||||
},
|
||||
"biologist": {
|
||||
"directive": "Pensa come un biologo evoluzionista: il mercato e un ecosistema di strategie in competizione. Skew negativo = predazione asimmetrica (vol-selling crowded che subisce shock). Skew positivo = predatori che cacciano breakout. Kurt alta = eventi di estinzione/fioritura. AR(1) positivo persistente = una specie sta colonizzando la nicchia (overcrowding imminente, fade); Hurst > 0.55 con vol percentile basso = nicchia stabile (occupa); seasonality forte = ritmo biologico ricorrente, sfruttabile. Preferisci contrarian in regime di skew estremo (fade la specie dominante) e coordinamento in regime simmetrico. Pattern asimmetrici: cattura la coda opposta al consensus."
|
||||
},
|
||||
"historian": {
|
||||
"directive": "Pensa come uno storico: i regimi si ripetono ma non identici. Usa mean come drift strutturale e std come ampiezza ciclo. Kurt alta + vol regime medium/high = fase tardiva (eventi estremi addensati, pre-transizione); kurt bassa + skew ≈0 = accumulazione/stabilita. AR(1) recente >> baseline storica = regime sta accelerando rispetto al normale; Hurst > 0.55 + vol percentile alto = fase markup matura, mean reversion attesa; seasonality forte = abitudini collettive consolidate, replicabili. Preferisci mean reversion strutturali: deviazioni significative tendono a ritornare su orizzonti multipli. Identifica analogie tra regime corrente e fasi tipiche (compressione vol, espansione, esaurimento trend)."
|
||||
},
|
||||
"meteorologist": {
|
||||
"directive": "Pensa come un meteorologo: la volatilita ha regimi persistenti con transizioni brusche. Vol regime e input primario. Std + kurt = microclima: std bassa + kurt bassa = calma stabile (vendi vol), std alta + kurt alta = tempesta (compra convexity), std bassa + kurt alta = calma ingannevole (riduci esposizione). AR(1) recente > baseline = fronte persistente in arrivo; Hurst > 0.55 = sistema su scala lunga (ciclone), Hurst < 0.45 = turbolenza locale (no trend persistente); vol percentile estremo = posizione nel ciclo seasonal; seasonality alta = pattern cyclonico ricorrente. Strategie regime-aware: gate espliciti su vol che attivano logiche diverse. Pattern multi-regime sono robusti."
|
||||
},
|
||||
"engineer": {
|
||||
"directive": "Pensa come un ingegnere di controllo: ogni segnale deve avere SNR favorevole e robustezza. Std e il rumore di fondo: il segnale deve essere significativamente piu grande della varianza intraday. Kurt alta riduce affidabilita dei segnali medi. AR(1) > 0.05 con std contenuta = SNR favorevole, segnale azionabile; AR(1) ≈0 = random walk, non costruirci sopra; Hurst < 0.45 = filtro mean-reversion causale efficace; vol percentile > 80 = saturazione (sensori instabili, riduci leverage); seasonality < 0.05 = feature temporali sono rumore, NON usarle. Pattern semplici e tarabili: poche condizioni in AND, soglie con margine, isteresi entry/exit. Robustezza > ottimalita su singolo regime."
|
||||
},
|
||||
"military_strategist": {
|
||||
"directive": "Pensa come uno stratega: distingui regime offensivo da difensivo. Vol regime medium/low + skew positivo + kurt moderata = terreno favorevole all'attacco (entry direzionali su breakout/momentum). Vol regime high + kurt elevata = terreno ostile (difesa: posizioni limitate, exit rapide). AR(1) > 0 = vento alle spalle, carica con momentum; AR(1) < 0 = imboscata possibile, contrarian; Hurst > 0.55 = posizione difendibile (hold trade); vol percentile alta = artiglieria nemica attiva (ritirata); seasonality forte = via predicibile da percorrere, sfruttala. Concentrazione: poche condizioni forti. Economia: flat quando il segnale non e dominante. Sorpresa: contrarian su skew estremo (consensus). Sicurezza: ogni entry con exit chiara."
|
||||
},
|
||||
"psychologist": {
|
||||
"directive": "Pensa come uno psicologo del comportamento collettivo: skew e kurt catturano emozioni. Skew neg + kurt alta = paura ricorrente (capitulation spikes, fade gli estremi al ribasso). Skew pos + kurt alta = euforia (FOMO spikes, fade gli estremi al rialzo). Skew ≈0 + kurt bassa = apatia/range (gioca i bordi). AR(1) recente >> baseline = euforia coordinata in corso, posizionati contro l'ultimo arrivato; Hurst > 0.55 = trance collettiva (trend trance, dura piu del razionale); vol percentile estremo + kurt alta = momentum emozionale puro; seasonality intra-day forte = bias circadiani sfruttabili (FOMO apertura US, panico chiusura asiatica). Estremi di oscillatori in regimi emotivi (kurt alta), crossover in regimi razionali (kurt ≈3). Contrarian sugli estremi, continuazione sulle medie."
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user