feat(hedger): implement dynamic rebalance threshold based on volatility
This commit is contained in:
@ -349,6 +349,34 @@ class ScalperHedger:
|
||||
|
||||
logger.info(f"[DELTA] Delta-Zero Scalper Hedger initialized. Agent: {self.account.address}")
|
||||
|
||||
def calculate_volatility(self) -> Decimal:
|
||||
"""
|
||||
Calculate volatility (Standard Deviation %) of price history.
|
||||
Uses standard deviation of the last N prices relative to the mean.
|
||||
Returns: Decimal percentage (e.g., 0.001 = 0.1% volatility)
|
||||
"""
|
||||
if not self.price_history or len(self.price_history) < 30:
|
||||
return Decimal("0.0")
|
||||
|
||||
try:
|
||||
# 1. Mean
|
||||
n = len(self.price_history)
|
||||
mean = sum(self.price_history) / n
|
||||
|
||||
# 2. Variance (Sum of squared diffs)
|
||||
variance = sum([pow(p - mean, 2) for p in self.price_history]) / n
|
||||
|
||||
# 3. Std Dev
|
||||
std_dev = variance.sqrt()
|
||||
|
||||
# 4. Volatility %
|
||||
if mean == 0: return Decimal("0.0")
|
||||
return std_dev / mean
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating volatility: {e}")
|
||||
return Decimal("0.0")
|
||||
|
||||
def get_dynamic_edge_proximity(self, price: Decimal) -> Decimal:
|
||||
"""
|
||||
Calculate dynamic edge proximity based on position value.
|
||||
@ -685,14 +713,51 @@ class ScalperHedger:
|
||||
calc = self.strategy.calculate_rebalance(price, current_size)
|
||||
diff_abs = abs(calc['diff'])
|
||||
|
||||
# Update Price History (Max 300 items = 5 mins @ 1s)
|
||||
self.price_history.append(price)
|
||||
if len(self.price_history) > 300:
|
||||
self.price_history.pop(0)
|
||||
|
||||
# 4. Thresholds
|
||||
sqrt_Pa = self.strategy.low_range.sqrt()
|
||||
sqrt_Pb = self.strategy.high_range.sqrt()
|
||||
max_potential_eth = self.strategy.L * ((Decimal("1")/sqrt_Pa) - (Decimal("1")/sqrt_Pb))
|
||||
|
||||
rebalance_threshold = max(MIN_THRESHOLD_ETH, max_potential_eth * Decimal("0.05"))
|
||||
# --- Dynamic Threshold Optimization (ATR/Vol Based) ---
|
||||
|
||||
# Volatility Adjustment
|
||||
# 1. Calculate Volatility
|
||||
vol_pct = self.calculate_volatility()
|
||||
|
||||
# 2. Volatility Multiplier
|
||||
# Base Vol assumption: 0.05% (0.0005) per window.
|
||||
# If Vol is 0.15%, mult = 3x. Cap at 3.0x. Min 1.0x.
|
||||
base_vol_ref = Decimal("0.0005")
|
||||
vol_multiplier = Decimal("1.0")
|
||||
if vol_pct > 0:
|
||||
vol_multiplier = max(Decimal("1.0"), min(Decimal("3.0"), vol_pct / base_vol_ref))
|
||||
|
||||
# 3. Base Threshold Calculation (Range Dependent)
|
||||
range_width_pct = (self.strategy.high_range - self.strategy.low_range) / self.strategy.low_range
|
||||
|
||||
# Ensure we satisfy PRICE_BUFFER_PCT (0.15%) minimum
|
||||
base_threshold_pct = max(Decimal("0.05"), PRICE_BUFFER_PCT / range_width_pct if range_width_pct > 0 else Decimal("0.05"))
|
||||
|
||||
# 4. Apply Multiplier
|
||||
target_threshold_pct = base_threshold_pct * vol_multiplier
|
||||
|
||||
# 5. Safety Cap
|
||||
# Limit threshold to 20% of the total range width (relative) to prevent staying unhedged too long
|
||||
# e.g. if range is 1% wide, max threshold is 0.2% deviation.
|
||||
# If range is 10% wide, max threshold is 2% deviation.
|
||||
# Absolute hard cap at 15% delta deviation.
|
||||
safety_cap = min(Decimal("0.15"), Decimal("0.20"))
|
||||
|
||||
final_threshold_pct = min(target_threshold_pct, safety_cap)
|
||||
|
||||
rebalance_threshold = max(MIN_THRESHOLD_ETH, max_potential_eth * final_threshold_pct)
|
||||
|
||||
# Volatility Adjustment (Instantaneous Shock)
|
||||
# Keep this for sudden spikes that haven't affected the 5-min average yet
|
||||
if self.last_price:
|
||||
pct_change = abs(price - self.last_price) / self.last_price
|
||||
if pct_change > Decimal("0.003"):
|
||||
@ -765,7 +830,7 @@ class ScalperHedger:
|
||||
exec_price = levels['ask'] * Decimal("1.001") if is_buy else levels['bid'] * Decimal("0.999")
|
||||
|
||||
urgency = "URGENT" if bypass_cooldown else "NORMAL"
|
||||
logger.info(f"[TRIG] Rebalance ({urgency}): {calc['action']} {diff_abs:.4f} (Diff > {rebalance_threshold:.4f})")
|
||||
logger.info(f"[TRIG] Rebalance ({urgency}): {calc['action']} {diff_abs:.4f} > {rebalance_threshold:.4f} (Vol: {vol_pct*100:.3f}% x{vol_multiplier:.1f} | Thresh: {final_threshold_pct*100:.1f}%)")
|
||||
|
||||
oid = self.place_limit_order(COIN_SYMBOL, is_buy, diff_abs, exec_price, "Ioc")
|
||||
if oid:
|
||||
@ -777,7 +842,7 @@ class ScalperHedger:
|
||||
self.last_idle_log_time = time.time()
|
||||
else:
|
||||
if time.time() - self.last_idle_log_time > 30:
|
||||
logger.info(f"[IDLE] Px: {price:.2f} | Diff: {diff_abs:.4f} < {rebalance_threshold:.4f} | PnL: {current_pnl:.2f}")
|
||||
logger.info(f"[IDLE] Px: {price:.2f} | Diff: {diff_abs:.4f} < {rebalance_threshold:.4f} (Vol: {vol_pct*100:.3f}% x{vol_multiplier:.1f} | Thresh: {final_threshold_pct*100:.1f}%) | PnL: {current_pnl:.2f}")
|
||||
self.last_idle_log_time = time.time()
|
||||
|
||||
self.track_fills_and_pnl()
|
||||
|
||||
Reference in New Issue
Block a user