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}")
|
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:
|
def get_dynamic_edge_proximity(self, price: Decimal) -> Decimal:
|
||||||
"""
|
"""
|
||||||
Calculate dynamic edge proximity based on position value.
|
Calculate dynamic edge proximity based on position value.
|
||||||
@ -685,14 +713,51 @@ class ScalperHedger:
|
|||||||
calc = self.strategy.calculate_rebalance(price, current_size)
|
calc = self.strategy.calculate_rebalance(price, current_size)
|
||||||
diff_abs = abs(calc['diff'])
|
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
|
# 4. Thresholds
|
||||||
sqrt_Pa = self.strategy.low_range.sqrt()
|
sqrt_Pa = self.strategy.low_range.sqrt()
|
||||||
sqrt_Pb = self.strategy.high_range.sqrt()
|
sqrt_Pb = self.strategy.high_range.sqrt()
|
||||||
max_potential_eth = self.strategy.L * ((Decimal("1")/sqrt_Pa) - (Decimal("1")/sqrt_Pb))
|
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:
|
if self.last_price:
|
||||||
pct_change = abs(price - self.last_price) / self.last_price
|
pct_change = abs(price - self.last_price) / self.last_price
|
||||||
if pct_change > Decimal("0.003"):
|
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")
|
exec_price = levels['ask'] * Decimal("1.001") if is_buy else levels['bid'] * Decimal("0.999")
|
||||||
|
|
||||||
urgency = "URGENT" if bypass_cooldown else "NORMAL"
|
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")
|
oid = self.place_limit_order(COIN_SYMBOL, is_buy, diff_abs, exec_price, "Ioc")
|
||||||
if oid:
|
if oid:
|
||||||
@ -777,7 +842,7 @@ class ScalperHedger:
|
|||||||
self.last_idle_log_time = time.time()
|
self.last_idle_log_time = time.time()
|
||||||
else:
|
else:
|
||||||
if time.time() - self.last_idle_log_time > 30:
|
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.last_idle_log_time = time.time()
|
||||||
|
|
||||||
self.track_fills_and_pnl()
|
self.track_fills_and_pnl()
|
||||||
|
|||||||
Reference in New Issue
Block a user