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
|
skew: float
|
||||||
kurtosis: float
|
kurtosis: float
|
||||||
volatility_regime: str
|
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)
|
@dataclass(frozen=True)
|
||||||
@@ -181,6 +187,12 @@ Statistiche return: mean={return_mean:.5f}, std={return_std:.5f}, \
|
|||||||
skew={skew:.3f}, kurt={kurtosis:.3f}.
|
skew={skew:.3f}, kurt={kurtosis:.3f}.
|
||||||
Regime volatilità: {volatility_regime}.
|
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}.
|
Feature accessibili dal tuo genoma: {feature_access}.
|
||||||
Lookback massimo che puoi usare nel ragionamento: {lookback_window} barre.
|
Lookback massimo che puoi usare nel ragionamento: {lookback_window} barre.
|
||||||
|
|
||||||
@@ -294,6 +306,12 @@ class HypothesisAgent:
|
|||||||
skew=market.skew,
|
skew=market.skew,
|
||||||
kurtosis=market.kurtosis,
|
kurtosis=market.kurtosis,
|
||||||
volatility_regime=market.volatility_regime,
|
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),
|
feature_access=", ".join(genome.feature_access),
|
||||||
lookback_window=genome.lookback_window,
|
lookback_window=genome.lookback_window,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ from __future__ import annotations
|
|||||||
import pandas as pd # type: ignore[import-untyped]
|
import pandas as pd # type: ignore[import-untyped]
|
||||||
from scipy import stats # 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
|
from .hypothesis import MarketSummary
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +30,16 @@ def build_market_summary(
|
|||||||
else:
|
else:
|
||||||
regime = "high"
|
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(
|
return MarketSummary(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
timeframe=timeframe,
|
timeframe=timeframe,
|
||||||
@@ -33,4 +49,10 @@ def build_market_summary(
|
|||||||
skew=skew,
|
skew=skew,
|
||||||
kurtosis=kurt,
|
kurtosis=kurt,
|
||||||
volatility_regime=regime,
|
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]
|
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:
|
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."""
|
"""Sharpe annualizzato. periods_per_year=8760 per dati orari."""
|
||||||
excess = returns - rf / periods_per_year
|
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")
|
s = build_market_summary(df, symbol="BTC/USDT", timeframe="1h")
|
||||||
assert s.volatility_regime in {"medium", "high"}
|
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 pandas as pd
|
||||||
import pytest
|
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():
|
def test_sharpe_zero_returns():
|
||||||
@@ -38,3 +46,123 @@ def test_max_drawdown_known_curve():
|
|||||||
def test_total_return():
|
def test_total_return():
|
||||||
eq = pd.Series([100.0, 110.0, 105.0, 120.0])
|
eq = pd.Series([100.0, 110.0, 105.0, 120.0])
|
||||||
assert total_return(eq) == pytest.approx(0.20)
|
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