Add S/R v2 research and validation harness
This commit is contained in:
@@ -256,6 +256,12 @@ def compute_volume_profile(
|
||||
) -> dict[str, Any]:
|
||||
"""Compute Volume Profile: POC, Value Area, HVN, LVN.
|
||||
|
||||
Volume is assigned to the bin containing each bar's **close** (no
|
||||
double-counting across the high–low span).
|
||||
|
||||
HVN = local peaks in the volume histogram (not every bin above mean).
|
||||
LVN = local valleys in the histogram.
|
||||
|
||||
Score: proximity of latest close to POC (closer = higher).
|
||||
"""
|
||||
n = len(closes)
|
||||
@@ -275,14 +281,18 @@ def compute_volume_profile(
|
||||
price_min + (i + 0.5) * bin_width for i in range(num_bins)
|
||||
]
|
||||
|
||||
# Assign each bar's full volume to the close's bin only.
|
||||
for i in range(n):
|
||||
# Distribute volume across bins the bar spans
|
||||
bar_low, bar_high = lows[i], highs[i]
|
||||
for b in range(num_bins):
|
||||
bl = price_min + b * bin_width
|
||||
bh = bl + bin_width
|
||||
if bar_high >= bl and bar_low <= bh:
|
||||
bins[b] += volumes[i]
|
||||
c = closes[i]
|
||||
if c <= price_min:
|
||||
b = 0
|
||||
elif c >= price_max:
|
||||
b = num_bins - 1
|
||||
else:
|
||||
b = int((c - price_min) / bin_width)
|
||||
if b >= num_bins:
|
||||
b = num_bins - 1
|
||||
bins[b] += volumes[i]
|
||||
|
||||
total_vol = sum(bins)
|
||||
if total_vol == 0:
|
||||
@@ -304,10 +314,17 @@ def compute_volume_profile(
|
||||
va_low = round(price_min + min(va_indices) * bin_width, 4)
|
||||
va_high = round(price_min + (max(va_indices) + 1) * bin_width, 4)
|
||||
|
||||
# HVN / LVN: bins above/below average volume
|
||||
# HVN / LVN: local peaks / valleys (require above/below mean to skip noise)
|
||||
avg_vol = total_vol / num_bins
|
||||
hvn = [round(bin_prices[i], 4) for i in range(num_bins) if bins[i] > avg_vol]
|
||||
lvn = [round(bin_prices[i], 4) for i in range(num_bins) if bins[i] < avg_vol]
|
||||
hvn: list[float] = []
|
||||
lvn: list[float] = []
|
||||
for i in range(num_bins):
|
||||
left = bins[i - 1] if i > 0 else bins[i]
|
||||
right = bins[i + 1] if i < num_bins - 1 else bins[i]
|
||||
if bins[i] > left and bins[i] > right and bins[i] > avg_vol:
|
||||
hvn.append(round(bin_prices[i], 4))
|
||||
elif bins[i] < left and bins[i] < right and bins[i] < avg_vol:
|
||||
lvn.append(round(bin_prices[i], 4))
|
||||
|
||||
# Score: proximity of latest close to POC
|
||||
latest = closes[-1]
|
||||
@@ -333,10 +350,14 @@ def compute_pivot_points(
|
||||
lows: list[float],
|
||||
closes: list[float],
|
||||
window: int = 2,
|
||||
min_prominence: float | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Detect swing highs/lows as pivot points.
|
||||
|
||||
A swing high at index *i* means highs[i] >= all highs in [i-window, i+window].
|
||||
When *min_prominence* is set, only keep swings whose window range
|
||||
(max high − min low) is at least that amount — filters tiny noise fractals.
|
||||
|
||||
Score: based on number of pivots near current price.
|
||||
"""
|
||||
n = len(closes)
|
||||
@@ -349,12 +370,24 @@ def compute_pivot_points(
|
||||
swing_lows: list[float] = []
|
||||
|
||||
for i in range(window, n - window):
|
||||
lo = i - window
|
||||
hi = i + window + 1
|
||||
# Swing high
|
||||
if all(highs[i] >= highs[j] for j in range(i - window, i + window + 1)):
|
||||
swing_highs.append(round(highs[i], 4))
|
||||
if all(highs[i] >= highs[j] for j in range(lo, hi)):
|
||||
if min_prominence is None or min_prominence <= 0:
|
||||
swing_highs.append(round(highs[i], 4))
|
||||
else:
|
||||
depth = highs[i] - min(lows[j] for j in range(lo, hi))
|
||||
if depth >= min_prominence:
|
||||
swing_highs.append(round(highs[i], 4))
|
||||
# Swing low
|
||||
if all(lows[i] <= lows[j] for j in range(i - window, i + window + 1)):
|
||||
swing_lows.append(round(lows[i], 4))
|
||||
if all(lows[i] <= lows[j] for j in range(lo, hi)):
|
||||
if min_prominence is None or min_prominence <= 0:
|
||||
swing_lows.append(round(lows[i], 4))
|
||||
else:
|
||||
depth = max(highs[j] for j in range(lo, hi)) - lows[i]
|
||||
if depth >= min_prominence:
|
||||
swing_lows.append(round(lows[i], 4))
|
||||
|
||||
all_pivots = swing_highs + swing_lows
|
||||
latest = closes[-1]
|
||||
|
||||
Reference in New Issue
Block a user