Update regime.py
Browse files
regime.py
CHANGED
|
@@ -1,14 +1,19 @@
|
|
| 1 |
-
import pandas as pd
|
| 2 |
-
import numpy as np
|
| 3 |
from typing import Dict, Any
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
|
| 7 |
def compute_atr(df: pd.DataFrame, period: int = ATR_PERIOD) -> pd.Series:
|
| 8 |
-
high = df["high"]
|
| 9 |
-
low = df["low"]
|
| 10 |
-
close = df["close"]
|
| 11 |
-
prev_close = close.shift(1)
|
| 12 |
tr = pd.concat(
|
| 13 |
[
|
| 14 |
high - low,
|
|
@@ -17,67 +22,79 @@ def compute_atr(df: pd.DataFrame, period: int = ATR_PERIOD) -> pd.Series:
|
|
| 17 |
],
|
| 18 |
axis=1,
|
| 19 |
).max(axis=1)
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
hh =
|
| 30 |
-
hl =
|
| 31 |
-
lh =
|
| 32 |
-
ll =
|
|
|
|
| 33 |
structure = pd.Series(0, index=df.index)
|
| 34 |
structure[hh & hl] = 1
|
| 35 |
structure[lh & ll] = -1
|
| 36 |
return structure
|
| 37 |
|
| 38 |
|
| 39 |
-
def
|
| 40 |
atr = compute_atr(df, period)
|
| 41 |
-
atr_ma = atr.rolling(period * 2).mean()
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
def detect_regime(df: pd.DataFrame) -> Dict[str, Any]:
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
last_atr =
|
| 52 |
-
last_structure =
|
| 53 |
-
last_vol_ratio =
|
| 54 |
-
|
| 55 |
-
recent_structure = structure.iloc[-5:]
|
| 56 |
-
bullish_count = (recent_structure == 1).sum()
|
| 57 |
-
bearish_count = (recent_structure == -1).sum()
|
| 58 |
-
|
| 59 |
-
if bullish_count >= 3:
|
| 60 |
-
trend = "bullish"
|
| 61 |
-
elif bearish_count >= 3:
|
| 62 |
-
trend = "bearish"
|
| 63 |
-
else:
|
| 64 |
-
trend = "ranging"
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
if trend == "bullish" and not
|
| 69 |
regime_score = 1.0
|
| 70 |
-
elif trend == "bullish" and
|
| 71 |
-
regime_score = 0.
|
|
|
|
|
|
|
| 72 |
elif trend == "ranging":
|
| 73 |
-
regime_score = 0.
|
| 74 |
-
elif trend == "bearish" and not
|
| 75 |
-
regime_score = 0.
|
| 76 |
else:
|
| 77 |
-
regime_score = 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
|
| 80 |
-
|
|
|
|
| 81 |
|
| 82 |
return {
|
| 83 |
"atr": last_atr,
|
|
@@ -85,9 +102,11 @@ def detect_regime(df: pd.DataFrame) -> Dict[str, Any]:
|
|
| 85 |
"structure": last_structure,
|
| 86 |
"trend": trend,
|
| 87 |
"vol_ratio": last_vol_ratio,
|
| 88 |
-
"
|
| 89 |
-
"
|
| 90 |
-
"
|
| 91 |
-
"
|
| 92 |
-
"
|
|
|
|
|
|
|
| 93 |
}
|
|
|
|
|
|
|
|
|
|
| 1 |
from typing import Dict, Any
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pandas as pd
|
| 5 |
+
|
| 6 |
+
from config import (
|
| 7 |
+
ATR_PERIOD,
|
| 8 |
+
STRUCTURE_LOOKBACK,
|
| 9 |
+
STRUCTURE_CONFIRM_BARS,
|
| 10 |
+
VOLATILITY_EXPANSION_MULT,
|
| 11 |
+
VOLATILITY_CONTRACTION_MULT,
|
| 12 |
+
)
|
| 13 |
|
| 14 |
|
| 15 |
def compute_atr(df: pd.DataFrame, period: int = ATR_PERIOD) -> pd.Series:
|
| 16 |
+
high, low, prev_close = df["high"], df["low"], df["close"].shift(1)
|
|
|
|
|
|
|
|
|
|
| 17 |
tr = pd.concat(
|
| 18 |
[
|
| 19 |
high - low,
|
|
|
|
| 22 |
],
|
| 23 |
axis=1,
|
| 24 |
).max(axis=1)
|
| 25 |
+
return tr.ewm(span=period, adjust=False).mean()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def compute_structure(df: pd.DataFrame, lookback: int = STRUCTURE_LOOKBACK) -> pd.Series:
|
| 29 |
+
roll_high = df["high"].rolling(lookback).max()
|
| 30 |
+
roll_low = df["low"].rolling(lookback).min()
|
| 31 |
+
prev_roll_high = roll_high.shift(lookback // 2)
|
| 32 |
+
prev_roll_low = roll_low.shift(lookback // 2)
|
| 33 |
+
|
| 34 |
+
hh = roll_high > prev_roll_high
|
| 35 |
+
hl = roll_low > prev_roll_low
|
| 36 |
+
lh = roll_high < prev_roll_high
|
| 37 |
+
ll = roll_low < prev_roll_low
|
| 38 |
+
|
| 39 |
structure = pd.Series(0, index=df.index)
|
| 40 |
structure[hh & hl] = 1
|
| 41 |
structure[lh & ll] = -1
|
| 42 |
return structure
|
| 43 |
|
| 44 |
|
| 45 |
+
def compute_vol_ratio(df: pd.DataFrame, period: int = ATR_PERIOD) -> pd.Series:
|
| 46 |
atr = compute_atr(df, period)
|
| 47 |
+
atr_ma = atr.rolling(period * 2).mean().replace(0, np.nan)
|
| 48 |
+
return atr / atr_ma
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def classify_trend(structure_series: pd.Series, lookback: int = STRUCTURE_CONFIRM_BARS) -> str:
|
| 52 |
+
recent = structure_series.iloc[-lookback:]
|
| 53 |
+
bullish = (recent == 1).sum()
|
| 54 |
+
bearish = (recent == -1).sum()
|
| 55 |
+
if bullish > bearish and bullish >= max(1, lookback // 2):
|
| 56 |
+
return "bullish"
|
| 57 |
+
if bearish > bullish and bearish >= max(1, lookback // 2):
|
| 58 |
+
return "bearish"
|
| 59 |
+
return "ranging"
|
| 60 |
|
| 61 |
|
| 62 |
def detect_regime(df: pd.DataFrame) -> Dict[str, Any]:
|
| 63 |
+
atr_series = compute_atr(df, ATR_PERIOD)
|
| 64 |
+
structure_series = compute_structure(df, STRUCTURE_LOOKBACK)
|
| 65 |
+
vol_ratio_series = compute_vol_ratio(df, ATR_PERIOD)
|
| 66 |
+
|
| 67 |
+
last_atr = float(atr_series.iloc[-1])
|
| 68 |
+
last_structure = int(structure_series.iloc[-1])
|
| 69 |
+
last_vol_ratio = float(vol_ratio_series.iloc[-1]) if not np.isnan(vol_ratio_series.iloc[-1]) else 1.0
|
| 70 |
+
last_close = float(df["close"].iloc[-1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
trend = classify_trend(structure_series, STRUCTURE_CONFIRM_BARS)
|
| 73 |
+
vol_expanding = last_vol_ratio > VOLATILITY_EXPANSION_MULT
|
| 74 |
+
vol_contracting = last_vol_ratio < VOLATILITY_CONTRACTION_MULT
|
| 75 |
+
atr_pct = last_atr / last_close if last_close > 0 else 0.0
|
| 76 |
|
| 77 |
+
if trend == "bullish" and not vol_expanding:
|
| 78 |
regime_score = 1.0
|
| 79 |
+
elif trend == "bullish" and vol_expanding:
|
| 80 |
+
regime_score = 0.55
|
| 81 |
+
elif trend == "ranging" and not vol_expanding and not vol_contracting:
|
| 82 |
+
regime_score = 0.35
|
| 83 |
elif trend == "ranging":
|
| 84 |
+
regime_score = 0.25
|
| 85 |
+
elif trend == "bearish" and not vol_expanding:
|
| 86 |
+
regime_score = 0.15
|
| 87 |
else:
|
| 88 |
+
regime_score = 0.05
|
| 89 |
+
|
| 90 |
+
if last_structure == 1:
|
| 91 |
+
regime_score = min(1.0, regime_score + 0.1)
|
| 92 |
+
elif last_structure == -1:
|
| 93 |
+
regime_score = max(0.0, regime_score - 0.1)
|
| 94 |
|
| 95 |
+
atr_ma_20 = atr_series.rolling(20).mean().iloc[-1]
|
| 96 |
+
atr_ma_50 = atr_series.rolling(50).mean().iloc[-1] if len(df) >= 50 else atr_ma_20
|
| 97 |
+
atr_trend = "rising" if atr_ma_20 > atr_ma_50 else "falling"
|
| 98 |
|
| 99 |
return {
|
| 100 |
"atr": last_atr,
|
|
|
|
| 102 |
"structure": last_structure,
|
| 103 |
"trend": trend,
|
| 104 |
"vol_ratio": last_vol_ratio,
|
| 105 |
+
"vol_expanding": vol_expanding,
|
| 106 |
+
"vol_contracting": vol_contracting,
|
| 107 |
+
"atr_trend": atr_trend,
|
| 108 |
+
"regime_score": round(float(np.clip(regime_score, 0.0, 1.0)), 4),
|
| 109 |
+
"atr_series": atr_series,
|
| 110 |
+
"structure_series": structure_series,
|
| 111 |
+
"vol_ratio_series": vol_ratio_series,
|
| 112 |
}
|