GoshawkVortexAI commited on
Commit
8d2a737
·
verified ·
1 Parent(s): 33a396a

Update volume_analysis.py

Browse files
Files changed (1) hide show
  1. volume_analysis.py +82 -66
volume_analysis.py CHANGED
@@ -1,10 +1,14 @@
1
- import pandas as pd
2
- import numpy as np
3
  from typing import Dict, Any
 
 
 
 
4
  from config import (
5
- VOLUME_SPIKE_MULTIPLIER,
6
  VOLUME_MA_PERIOD,
7
- CLIMAX_VOLUME_MULTIPLIER,
 
 
 
8
  )
9
 
10
 
@@ -12,93 +16,105 @@ def compute_volume_ma(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Se
12
  return df["volume"].rolling(period).mean()
13
 
14
 
15
- def detect_volume_spike(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
16
  vol_ma = compute_volume_ma(df, period)
17
- return df["volume"] > (vol_ma * VOLUME_SPIKE_MULTIPLIER)
18
 
19
 
20
- def detect_climax_volume(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
21
  vol_ma = compute_volume_ma(df, period)
22
- return df["volume"] > (vol_ma * CLIMAX_VOLUME_MULTIPLIER)
23
 
24
 
25
- def compute_breakout_confirmation(
26
- df: pd.DataFrame, lookback: int = VOLUME_MA_PERIOD
27
- ) -> pd.Series:
28
- price_high = df["close"].rolling(lookback).max().shift(1)
29
- price_low = df["close"].rolling(lookback).min().shift(1)
30
- breakout_up = df["close"] > price_high
31
- breakout_down = df["close"] < price_low
32
- vol_spike = detect_volume_spike(df)
33
- confirmed_up = breakout_up & vol_spike
34
- confirmed_down = breakout_down & vol_spike
35
- confirmation = pd.Series(0, index=df.index)
36
- confirmation[confirmed_up] = 1
37
- confirmation[confirmed_down] = -1
38
- return confirmation
39
 
40
 
41
- def compute_obv(df: pd.DataFrame) -> pd.Series:
42
- direction = np.sign(df["close"].diff())
43
- direction.iloc[0] = 0
44
- obv = (df["volume"] * direction).cumsum()
45
- return obv
 
 
46
 
47
 
48
- def compute_volume_delta_approx(df: pd.DataFrame) -> pd.Series:
49
  body = df["close"] - df["open"]
50
- wick_range = (df["high"] - df["low"]).replace(0, np.nan)
51
- buy_ratio = (body / wick_range).clip(0, 1).fillna(0.5)
52
- buy_volume = df["volume"] * buy_ratio
53
- sell_volume = df["volume"] * (1 - buy_ratio)
54
- delta = buy_volume - sell_volume
55
- return delta
 
 
 
 
 
 
 
 
 
56
 
57
 
58
  def analyze_volume(df: pd.DataFrame) -> Dict[str, Any]:
59
  vol_ma = compute_volume_ma(df, VOLUME_MA_PERIOD)
60
- spike = detect_volume_spike(df, VOLUME_MA_PERIOD)
61
- climax = detect_climax_volume(df, VOLUME_MA_PERIOD)
62
- breakout = compute_breakout_confirmation(df, VOLUME_MA_PERIOD)
63
  obv = compute_obv(df)
64
- delta = compute_volume_delta_approx(df)
 
65
 
66
- last_vol = df["volume"].iloc[-1]
67
- last_vol_ma = vol_ma.iloc[-1]
68
- last_spike = spike.iloc[-1]
69
- last_climax = climax.iloc[-1]
70
- last_breakout = breakout.iloc[-1]
 
71
 
72
  vol_ratio = last_vol / last_vol_ma if last_vol_ma > 0 else 1.0
73
 
74
- obv_slope = (obv.iloc[-1] - obv.iloc[-5]) / (obv.iloc[-5] + 1e-10)
75
- delta_sum = delta.iloc[-5:].sum()
 
 
 
 
 
 
76
 
77
  if last_climax:
78
- volume_score = 0.4
79
  elif last_spike and last_breakout != 0:
80
- volume_score = 1.0
81
- elif last_spike:
82
- volume_score = 0.7
83
- elif vol_ratio > 1.2:
84
- volume_score = 0.5
85
- elif vol_ratio > 0.8:
86
- volume_score = 0.3
87
  else:
88
- volume_score = 0.1
89
 
90
- obv_bonus = 0.1 if obv_slope > 0 else -0.1
91
- volume_score = float(np.clip(volume_score + obv_bonus, 0.0, 1.0))
 
92
 
93
  return {
94
- "vol_ratio": vol_ratio,
95
- "spike": bool(last_spike),
96
- "climax": bool(last_climax),
97
- "breakout": int(last_breakout),
98
- "obv_slope": obv_slope,
99
- "delta_sum": delta_sum,
100
- "volume_score": volume_score,
101
- "spike_series": spike,
102
- "climax_series": climax,
103
- "breakout_series": breakout,
 
 
 
104
  }
 
 
 
1
  from typing import Dict, Any
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
  from config import (
 
7
  VOLUME_MA_PERIOD,
8
+ VOLUME_SPIKE_MULT,
9
+ VOLUME_CLIMAX_MULT,
10
+ VOLUME_WEAK_THRESHOLD,
11
+ BREAKOUT_LOOKBACK,
12
  )
13
 
14
 
 
16
  return df["volume"].rolling(period).mean()
17
 
18
 
19
+ def detect_spikes(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
20
  vol_ma = compute_volume_ma(df, period)
21
+ return df["volume"] > vol_ma * VOLUME_SPIKE_MULT
22
 
23
 
24
+ def detect_climax(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
25
  vol_ma = compute_volume_ma(df, period)
26
+ return df["volume"] > vol_ma * VOLUME_CLIMAX_MULT
27
 
28
 
29
+ def compute_obv(df: pd.DataFrame) -> pd.Series:
30
+ direction = np.sign(df["close"].diff()).fillna(0)
31
+ return (df["volume"] * direction).cumsum()
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
+ def compute_vwap_deviation(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
35
+ typical = (df["high"] + df["low"] + df["close"]) / 3
36
+ vol = df["volume"]
37
+ cum_vp = (typical * vol).rolling(period).sum()
38
+ cum_vol = vol.rolling(period).sum().replace(0, np.nan)
39
+ vwap = cum_vp / cum_vol
40
+ return (df["close"] - vwap) / vwap
41
 
42
 
43
+ def compute_delta_approx(df: pd.DataFrame) -> pd.Series:
44
  body = df["close"] - df["open"]
45
+ wick = (df["high"] - df["low"]).replace(0, np.nan)
46
+ buy_ratio = ((body / wick) * 0.5 + 0.5).clip(0.0, 1.0).fillna(0.5)
47
+ buy_vol = df["volume"] * buy_ratio
48
+ sell_vol = df["volume"] * (1 - buy_ratio)
49
+ return buy_vol - sell_vol
50
+
51
+
52
+ def compute_breakout_signal(df: pd.DataFrame, lookback: int = BREAKOUT_LOOKBACK) -> pd.Series:
53
+ prior_high = df["close"].rolling(lookback).max().shift(1)
54
+ prior_low = df["close"].rolling(lookback).min().shift(1)
55
+ spikes = detect_spikes(df)
56
+ signal = pd.Series(0, index=df.index)
57
+ signal[(df["close"] > prior_high) & spikes] = 1
58
+ signal[(df["close"] < prior_low) & spikes] = -1
59
+ return signal
60
 
61
 
62
  def analyze_volume(df: pd.DataFrame) -> Dict[str, Any]:
63
  vol_ma = compute_volume_ma(df, VOLUME_MA_PERIOD)
64
+ spike_series = detect_spikes(df, VOLUME_MA_PERIOD)
65
+ climax_series = detect_climax(df, VOLUME_MA_PERIOD)
66
+ breakout_series = compute_breakout_signal(df, BREAKOUT_LOOKBACK)
67
  obv = compute_obv(df)
68
+ delta = compute_delta_approx(df)
69
+ vwap_dev = compute_vwap_deviation(df, VOLUME_MA_PERIOD)
70
 
71
+ last_vol = float(df["volume"].iloc[-1])
72
+ last_vol_ma = float(vol_ma.iloc[-1]) if not np.isnan(vol_ma.iloc[-1]) else 1.0
73
+ last_spike = bool(spike_series.iloc[-1])
74
+ last_climax = bool(climax_series.iloc[-1])
75
+ last_breakout = int(breakout_series.iloc[-1])
76
+ last_vwap_dev = float(vwap_dev.iloc[-1]) if not np.isnan(vwap_dev.iloc[-1]) else 0.0
77
 
78
  vol_ratio = last_vol / last_vol_ma if last_vol_ma > 0 else 1.0
79
 
80
+ obv_recent = obv.iloc[-10:]
81
+ obv_slope = float(np.polyfit(range(len(obv_recent)), obv_recent.values, 1)[0])
82
+ obv_normalized = obv_slope / (abs(obv_recent.mean()) + 1e-10)
83
+
84
+ delta_sum_5 = float(delta.iloc[-5:].sum())
85
+ delta_sign = 1 if delta_sum_5 > 0 else -1
86
+
87
+ weak_vol = vol_ratio < VOLUME_WEAK_THRESHOLD
88
 
89
  if last_climax:
90
+ base_score = 0.3
91
  elif last_spike and last_breakout != 0:
92
+ base_score = 1.0
93
+ elif last_spike and last_breakout == 0:
94
+ base_score = 0.65
95
+ elif vol_ratio >= 1.2:
96
+ base_score = 0.5
97
+ elif vol_ratio >= 0.8:
98
+ base_score = 0.35
99
  else:
100
+ base_score = 0.1
101
 
102
+ obv_bonus = float(np.clip(obv_normalized * 0.1, -0.1, 0.1))
103
+ vwap_bonus = 0.05 if last_vwap_dev > 0 and last_breakout == 1 else 0.0
104
+ volume_score = float(np.clip(base_score + obv_bonus + vwap_bonus, 0.0, 1.0))
105
 
106
  return {
107
+ "vol_ratio": round(vol_ratio, 3),
108
+ "spike": last_spike,
109
+ "climax": last_climax,
110
+ "weak": weak_vol,
111
+ "breakout": last_breakout,
112
+ "obv_slope_norm": round(obv_normalized, 4),
113
+ "delta_sum_5": round(delta_sum_5, 2),
114
+ "delta_sign": delta_sign,
115
+ "vwap_deviation": round(last_vwap_dev, 4),
116
+ "volume_score": round(volume_score, 4),
117
+ "spike_series": spike_series,
118
+ "climax_series": climax_series,
119
+ "breakout_series": breakout_series,
120
  }