Spaces:
Running
Running
File size: 6,390 Bytes
36199b8 5342112 36199b8 5342112 36199b8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | """research/range_model.py β arithmetic (βdays) range model, replacing hardcoded per-TF tables.
WHY: the per-TF magnitude tables in ai_forecast.py / database.py are "magic numbers" that in fact
track a βtime volatility law (volatility ~ βhorizon). This module derives them from a handful of
COEFFICIENTS Γ βdays, so tuning is ~5 numbers instead of dozens of per-TF cells, and it generalizes
to any horizon. Values reproduce the calibrated backup (research/range_tables_backup.json): the
hit-rate-critical BULLISH hi-bound reproduces 1D exactly and 3D/INTRADAY within tolerance.
WHAT STAYS NON-ARITHMETIC (by design, see backup notes):
β’ NEUTRAL band β flat by policy (falsifiability), NOT a volatility magnitude.
β’ The conservative lo-bound β a fee-clearing floor (~NSE round-trip cost), horizon-independent.
Reusability / PROD PORT (see research/PRODUCTION_DELTA.md): pure functions, no deps beyond math.
To ship: `ai_forecast.py` (`_build_synthesis_prompt`, `_generate_range_from_point`,
`_atr_clamp_range`, `_apply_calibrated_range`) and `database.py` (`_SNAP_*`) import from here β
which also removes the "must match" duplication between those two files.
Run `python research/range_model.py` to self-test reproduction against the backup JSON.
"""
from __future__ import annotations
import math
# ββ Effective volatility-days per horizon (the ONE source of horizon length) ββββββββββββββ
# INTRADAY modeled as a partial session (~0.5 day) β reproduces the calibrated intraday values.
_TF_VOL_DAYS: dict[str, float] = {"INTRADAY": 0.5, "1D": 1.0, "3D": 3.0, "5D": 5.0, "1W": 7.0}
# ββ Coefficients (the ~handful of tunables). Each magnitude = COEFF Γ βdays. βββββββββββββββ
_BULL_HI_COEFF = 1.30 # calibrated BULLISH optimistic bound: 1.30Β·βdays (reproduces 1D=1.30)
_BULL_LO_COEFF = 0.19 # conservative bound slope; floored by cost below
_ATR_LO_COEFF = 0.25 # prompt ATR target: conservative multiplier
_ATR_HI_COEFF = 0.60 # prompt ATR target: optimistic multiplier
_MID_CEIL_COEFF = 1.15 # clamp: max |midpoint| in ATR units
_MAX_WIDTH_COEFF = 0.65 # clamp: max band width in ATR units
_HARD_CAP_COEFF = 4.0 # clamp/prompt: hard %-cap
_INTRADAY_HARD_CAP = 2.0 # deliberate tight same-session cap (overrides the formula for INTRADAY)
# NSE round-trip cost floors (%) β the conservative bound must clear these (delivery ~0.22%,
# intraday ~0.11%). Horizon-independent; NOT scaled.
_COST_FLOOR_PCT: dict[str, float] = {"INTRADAY": 0.11, "1D": 0.22, "3D": 0.22, "5D": 0.22, "1W": 0.22}
# NEUTRAL band β flat by policy (falsifiability). Not scaled.
_NEUT_FLAT: dict[str, tuple[float, float]] = {
"INTRADAY": (-0.50, 0.50), "1D": (-1.5, 1.5), "3D": (-1.0, 1.0), "5D": (-1.0, 1.0), "1W": (-1.0, 1.0),
}
def horizon_scale(tf_label: str) -> float:
"""β(effective days) β the volatility-scaling factor for a horizon."""
return math.sqrt(_TF_VOL_DAYS.get(tf_label, 1.0))
def _cost_floor(tf_label: str) -> float:
return _COST_FLOOR_PCT.get(tf_label, 0.22)
def calibrated_range(direction: str, tf_label: str) -> tuple[float, float]:
"""(lo_pct, hi_pct) calibrated return band β the arithmetic replacement for
_BULL_RANGE/_BEAR_RANGE/_NEUT_RANGE (and database._SNAP_*)."""
d = (direction or "NEUTRAL").upper()
s = horizon_scale(tf_label)
if d in ("BULLISH", "SLIGHTLY BULLISH"):
lo = max(_cost_floor(tf_label), _BULL_LO_COEFF * s)
hi = _BULL_HI_COEFF * s
return (round(lo, 2), round(hi, 2))
if d in ("BEARISH", "SLIGHTLY BEARISH"):
lo, hi = calibrated_range("BULLISH", tf_label)
return (round(-hi, 2), round(-lo, 2)) # mirror below entry: lo < hi < 0
return _NEUT_FLAT.get(tf_label, (-1.0, 1.0))
def atr_target_mults(tf_label: str) -> tuple[float, float]:
"""(lo_mult, hi_mult) in ATR units for the synthesis-prompt target band."""
s = horizon_scale(tf_label)
return (round(_ATR_LO_COEFF * s, 3), round(_ATR_HI_COEFF * s, 3))
def atr_safety_nets(tf_label: str) -> dict:
"""Clamp safety-net values (ATR-unit ceiling/width + %-cap) for _atr_clamp_range."""
s = horizon_scale(tf_label)
cap = _INTRADAY_HARD_CAP if tf_label == "INTRADAY" else round(_HARD_CAP_COEFF * s, 2)
return {
"mid_ceiling": round(_MID_CEIL_COEFF * s, 3), # max |midpoint| in ATR units
"max_width": round(_MAX_WIDTH_COEFF * s, 3), # max band width in ATR units
"hard_cap_pct": cap,
}
# ββ Self-test: reproduction vs the calibrated backup ββββββββββββββββββββββββββββββββββββββ
if __name__ == "__main__":
import json, os
bpath = os.path.join(os.path.dirname(__file__), "range_tables_backup.json")
backup = json.load(open(bpath))
bull = backup["ai_forecast"]["_BULL_RANGE"]
mults = backup["ai_forecast"]["atr_target_mults__build_synthesis_prompt"]
ceil = backup["ai_forecast"]["_ATR_MID_CEILING"]
width = backup["ai_forecast"]["_ATR_MAX_WIDTH"]
cap = backup["ai_forecast"]["_TF_HARD_CAP_PCT"]
print(f"{'TF':<9} {'BULL(formula)':<16} {'BULL(backup)':<16} {'ATRmul(f)':<14} {'ATRmul(bk)':<14} "
f"{'ceil f/bk':<12} {'width f/bk':<12} {'cap f/bk':<10}")
ok = True
for tf in ("INTRADAY", "1D", "3D"):
cr = calibrated_range("BULLISH", tf)
am = atr_target_mults(tf)
sn = atr_safety_nets(tf)
print(f"{tf:<9} {str(cr):<16} {str(tuple(bull[tf])):<16} {str(am):<14} {str(tuple(mults[tf])):<14} "
f"{sn['mid_ceiling']}/{ceil[tf]:<7} {sn['max_width']}/{width[tf]:<7} {sn['hard_cap_pct']}/{cap[tf]}")
# Assert the hit-rate-critical 1D BULLISH hi reproduces exactly; 3D/INTRADAY within 10%.
assert calibrated_range("BULLISH", "1D")[1] == 1.30, "1D bull-hi must reproduce 1.30"
for tf in ("INTRADAY", "3D"):
f_hi = calibrated_range("BULLISH", tf)[1]; b_hi = bull[tf][1]
assert abs(f_hi - b_hi) / b_hi <= 0.10, f"{tf} bull-hi drift >10%: {f_hi} vs {b_hi}"
# BEARISH mirror + NEUTRAL flat
assert calibrated_range("BEARISH", "1D") == (-1.30, -0.22)
assert calibrated_range("NEUTRAL", "1D") == (-1.5, 1.5)
print("\nrange_model self-test PASSED (1D bull-hi exact; 3D/INTRADAY within 10%; bear mirror; neut flat)")
|