Goshawk_Hedge_Pro / volume_analysis.py
GoshawkVortexAI's picture
Update volume_analysis.py
8d2a737 verified
Raw
History Blame
4.21 kB
from typing import Dict, Any
import numpy as np
import pandas as pd
from config import (
VOLUME_MA_PERIOD,
VOLUME_SPIKE_MULT,
VOLUME_CLIMAX_MULT,
VOLUME_WEAK_THRESHOLD,
BREAKOUT_LOOKBACK,
)
def compute_volume_ma(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
return df["volume"].rolling(period).mean()
def detect_spikes(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
vol_ma = compute_volume_ma(df, period)
return df["volume"] > vol_ma * VOLUME_SPIKE_MULT
def detect_climax(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
vol_ma = compute_volume_ma(df, period)
return df["volume"] > vol_ma * VOLUME_CLIMAX_MULT
def compute_obv(df: pd.DataFrame) -> pd.Series:
direction = np.sign(df["close"].diff()).fillna(0)
return (df["volume"] * direction).cumsum()
def compute_vwap_deviation(df: pd.DataFrame, period: int = VOLUME_MA_PERIOD) -> pd.Series:
typical = (df["high"] + df["low"] + df["close"]) / 3
vol = df["volume"]
cum_vp = (typical * vol).rolling(period).sum()
cum_vol = vol.rolling(period).sum().replace(0, np.nan)
vwap = cum_vp / cum_vol
return (df["close"] - vwap) / vwap
def compute_delta_approx(df: pd.DataFrame) -> pd.Series:
body = df["close"] - df["open"]
wick = (df["high"] - df["low"]).replace(0, np.nan)
buy_ratio = ((body / wick) * 0.5 + 0.5).clip(0.0, 1.0).fillna(0.5)
buy_vol = df["volume"] * buy_ratio
sell_vol = df["volume"] * (1 - buy_ratio)
return buy_vol - sell_vol
def compute_breakout_signal(df: pd.DataFrame, lookback: int = BREAKOUT_LOOKBACK) -> pd.Series:
prior_high = df["close"].rolling(lookback).max().shift(1)
prior_low = df["close"].rolling(lookback).min().shift(1)
spikes = detect_spikes(df)
signal = pd.Series(0, index=df.index)
signal[(df["close"] > prior_high) & spikes] = 1
signal[(df["close"] < prior_low) & spikes] = -1
return signal
def analyze_volume(df: pd.DataFrame) -> Dict[str, Any]:
vol_ma = compute_volume_ma(df, VOLUME_MA_PERIOD)
spike_series = detect_spikes(df, VOLUME_MA_PERIOD)
climax_series = detect_climax(df, VOLUME_MA_PERIOD)
breakout_series = compute_breakout_signal(df, BREAKOUT_LOOKBACK)
obv = compute_obv(df)
delta = compute_delta_approx(df)
vwap_dev = compute_vwap_deviation(df, VOLUME_MA_PERIOD)
last_vol = float(df["volume"].iloc[-1])
last_vol_ma = float(vol_ma.iloc[-1]) if not np.isnan(vol_ma.iloc[-1]) else 1.0
last_spike = bool(spike_series.iloc[-1])
last_climax = bool(climax_series.iloc[-1])
last_breakout = int(breakout_series.iloc[-1])
last_vwap_dev = float(vwap_dev.iloc[-1]) if not np.isnan(vwap_dev.iloc[-1]) else 0.0
vol_ratio = last_vol / last_vol_ma if last_vol_ma > 0 else 1.0
obv_recent = obv.iloc[-10:]
obv_slope = float(np.polyfit(range(len(obv_recent)), obv_recent.values, 1)[0])
obv_normalized = obv_slope / (abs(obv_recent.mean()) + 1e-10)
delta_sum_5 = float(delta.iloc[-5:].sum())
delta_sign = 1 if delta_sum_5 > 0 else -1
weak_vol = vol_ratio < VOLUME_WEAK_THRESHOLD
if last_climax:
base_score = 0.3
elif last_spike and last_breakout != 0:
base_score = 1.0
elif last_spike and last_breakout == 0:
base_score = 0.65
elif vol_ratio >= 1.2:
base_score = 0.5
elif vol_ratio >= 0.8:
base_score = 0.35
else:
base_score = 0.1
obv_bonus = float(np.clip(obv_normalized * 0.1, -0.1, 0.1))
vwap_bonus = 0.05 if last_vwap_dev > 0 and last_breakout == 1 else 0.0
volume_score = float(np.clip(base_score + obv_bonus + vwap_bonus, 0.0, 1.0))
return {
"vol_ratio": round(vol_ratio, 3),
"spike": last_spike,
"climax": last_climax,
"weak": weak_vol,
"breakout": last_breakout,
"obv_slope_norm": round(obv_normalized, 4),
"delta_sum_5": round(delta_sum_5, 2),
"delta_sign": delta_sign,
"vwap_deviation": round(last_vwap_dev, 4),
"volume_score": round(volume_score, 4),
"spike_series": spike_series,
"climax_series": climax_series,
"breakout_series": breakout_series,
}