feat: add asymmetric compensation & exact liquidity support to hedger
This commit is contained in:
@ -222,7 +222,8 @@ def update_position_stats(token_id: int, stats_data: Dict):
|
|||||||
|
|
||||||
class HyperliquidStrategy:
|
class HyperliquidStrategy:
|
||||||
def __init__(self, entry_amount0: Decimal, entry_amount1: Decimal, target_value: Decimal,
|
def __init__(self, entry_amount0: Decimal, entry_amount1: Decimal, target_value: Decimal,
|
||||||
entry_price: Decimal, low_range: Decimal, high_range: Decimal, start_price: Decimal):
|
entry_price: Decimal, low_range: Decimal, high_range: Decimal, start_price: Decimal,
|
||||||
|
liquidity: int = 0):
|
||||||
self.entry_amount0 = entry_amount0
|
self.entry_amount0 = entry_amount0
|
||||||
self.entry_amount1 = entry_amount1
|
self.entry_amount1 = entry_amount1
|
||||||
self.target_value = target_value
|
self.target_value = target_value
|
||||||
@ -235,6 +236,19 @@ class HyperliquidStrategy:
|
|||||||
self.recovery_target = entry_price + (Decimal("2") * self.gap)
|
self.recovery_target = entry_price + (Decimal("2") * self.gap)
|
||||||
|
|
||||||
self.L = Decimal("0.0")
|
self.L = Decimal("0.0")
|
||||||
|
|
||||||
|
# Priority: Use exact Liquidity from Contract if available
|
||||||
|
if liquidity > 0:
|
||||||
|
# Scale raw liquidity (uint128) to human liquidity
|
||||||
|
# Formula: L_human = L_raw * 10^(-(d0+d1)/2)
|
||||||
|
# For ETH(18) / USDC(6) -> 10^(-12)
|
||||||
|
scale = Decimal("1e-12")
|
||||||
|
self.L = Decimal(liquidity) * scale
|
||||||
|
|
||||||
|
# Calculate implied delta at entry for verification
|
||||||
|
implied_delta = self.get_pool_delta(entry_price)
|
||||||
|
logger.info(f"Using Exact Liquidity: {self.L:.4f} (Raw: {liquidity}) -> Implied Delta: {implied_delta:.4f} ETH")
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
sqrt_P = entry_price.sqrt()
|
sqrt_P = entry_price.sqrt()
|
||||||
sqrt_Pa = low_range.sqrt()
|
sqrt_Pa = low_range.sqrt()
|
||||||
@ -286,21 +300,28 @@ class HyperliquidStrategy:
|
|||||||
def calculate_rebalance(self, current_price: Decimal, current_short_size: Decimal) -> Dict:
|
def calculate_rebalance(self, current_price: Decimal, current_short_size: Decimal) -> Dict:
|
||||||
pool_delta = self.get_pool_delta(current_price)
|
pool_delta = self.get_pool_delta(current_price)
|
||||||
|
|
||||||
# Over-Hedge Logic
|
# --- ASYMMETRIC COMPENSATION (0.35% Leakage Fix) ---
|
||||||
overhedge_pct = Decimal("0.0")
|
# Over-hedge on drops, Under-hedge on rises to offset execution friction.
|
||||||
|
# Max adjustment at edges: 7.5%
|
||||||
|
adj_pct = Decimal("0.0")
|
||||||
range_width = self.high_range - self.low_range
|
range_width = self.high_range - self.low_range
|
||||||
|
|
||||||
if range_width > 0:
|
if range_width > 0:
|
||||||
price_pct = (current_price - self.low_range) / range_width
|
# Distance from entry price
|
||||||
|
dist = current_price - self.entry_price
|
||||||
|
# Normalize to range half-width (approx)
|
||||||
|
half_width = range_width / Decimal("2")
|
||||||
|
norm_dist = dist / half_width
|
||||||
|
|
||||||
# If below 80% of range
|
# Adjustment: -7.5% at +1.0 (High), +7.5% at -1.0 (Low)
|
||||||
if price_pct < Decimal("0.8"):
|
max_boost = Decimal("0.075")
|
||||||
# Formula: 0.75% boost for every 0.1 drop below 0.8
|
adj_pct = -norm_dist * max_boost
|
||||||
diff_factor = (Decimal("0.8") - max(Decimal("0.0"), price_pct)) / Decimal("0.1")
|
|
||||||
overhedge_pct = diff_factor * Decimal("0.0075")
|
# Safety Cap
|
||||||
|
adj_pct = max(-max_boost, min(max_boost, adj_pct))
|
||||||
|
|
||||||
raw_target_short = pool_delta
|
raw_target_short = pool_delta
|
||||||
adjusted_target_short = raw_target_short * (Decimal("1.0") + overhedge_pct)
|
adjusted_target_short = raw_target_short * (Decimal("1.0") + adj_pct)
|
||||||
|
|
||||||
diff = adjusted_target_short - abs(current_short_size)
|
diff = adjusted_target_short - abs(current_short_size)
|
||||||
|
|
||||||
@ -311,7 +332,7 @@ class HyperliquidStrategy:
|
|||||||
"current_short": abs(current_short_size),
|
"current_short": abs(current_short_size),
|
||||||
"diff": diff,
|
"diff": diff,
|
||||||
"action": "SELL" if diff > 0 else "BUY",
|
"action": "SELL" if diff > 0 else "BUY",
|
||||||
"overhedge_pct": overhedge_pct
|
"adj_pct": adj_pct
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- MAIN HEDGER CLASS ---
|
# --- MAIN HEDGER CLASS ---
|
||||||
@ -434,6 +455,8 @@ class ScalperHedger:
|
|||||||
lower = to_decimal(position_data['range_lower'])
|
lower = to_decimal(position_data['range_lower'])
|
||||||
upper = to_decimal(position_data['range_upper'])
|
upper = to_decimal(position_data['range_upper'])
|
||||||
|
|
||||||
|
liquidity_val = int(position_data.get('liquidity', 0))
|
||||||
|
|
||||||
start_price = self.get_market_price(COIN_SYMBOL)
|
start_price = self.get_market_price(COIN_SYMBOL)
|
||||||
if start_price is None:
|
if start_price is None:
|
||||||
logger.warning("Waiting for initial price to start strategy...")
|
logger.warning("Waiting for initial price to start strategy...")
|
||||||
@ -446,7 +469,8 @@ class ScalperHedger:
|
|||||||
entry_price=entry_price,
|
entry_price=entry_price,
|
||||||
low_range=lower,
|
low_range=lower,
|
||||||
high_range=upper,
|
high_range=upper,
|
||||||
start_price=start_price
|
start_price=start_price,
|
||||||
|
liquidity=liquidity_val
|
||||||
)
|
)
|
||||||
|
|
||||||
# Reset State
|
# Reset State
|
||||||
@ -1017,7 +1041,7 @@ class ScalperHedger:
|
|||||||
create_shadow = False
|
create_shadow = False
|
||||||
|
|
||||||
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} > {rebalance_threshold:.4f} | Book: {levels['bid']}/{levels['ask']} | Vol: {vol_pct*100:.3f}% x{vol_multiplier:.1f} | Thresh: {final_threshold_pct*100:.1f}%")
|
logger.info(f"[TRIG] Rebalance ({urgency}): {calc['action']} {diff_abs:.4f} > {rebalance_threshold:.4f} | Adj: {calc['adj_pct']*100:+.1f}% | Book: {levels['bid']}/{levels['ask']} | 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, order_type)
|
oid = self.place_limit_order(COIN_SYMBOL, is_buy, diff_abs, exec_price, order_type)
|
||||||
if oid:
|
if oid:
|
||||||
@ -1074,7 +1098,7 @@ class ScalperHedger:
|
|||||||
p_sell = self.strategy.low_range + safety_margin
|
p_sell = self.strategy.low_range + safety_margin
|
||||||
|
|
||||||
net_pnl = self.accumulated_pnl - self.accumulated_fees
|
net_pnl = self.accumulated_pnl - self.accumulated_fees
|
||||||
logger.info(f"[IDLE] Px: {price:.2f} | M: {p_mid:.1f} | B: {p_buy:.1f} / S: {p_sell:.1f} (Vol: {vol_pct*100:.3f}% x{vol_multiplier:.1f} | Thresh: {final_threshold_pct*100:.1f}%) | TotPnL: {net_pnl:.2f}")
|
logger.info(f"[IDLE] Px: {price:.2f} | M: {p_mid:.1f} | B: {p_buy:.1f} / S: {p_sell:.1f} | Adj: {calc['adj_pct']*100:+.1f}% (Vol: {vol_pct*100:.3f}% x{vol_multiplier:.1f} | Thresh: {final_threshold_pct*100:.1f}%) | TotPnL: {net_pnl:.2f}")
|
||||||
self.last_idle_log_time = time.time()
|
self.last_idle_log_time = time.time()
|
||||||
|
|
||||||
# --- FISHING ORDER LOGIC (SAFE ZONE) ---
|
# --- FISHING ORDER LOGIC (SAFE ZONE) ---
|
||||||
|
|||||||
@ -128,8 +128,8 @@ CLOSE_POSITION_ENABLED = True
|
|||||||
OPEN_POSITION_ENABLED = True
|
OPEN_POSITION_ENABLED = True
|
||||||
REBALANCE_ON_CLOSE_BELOW_RANGE = True
|
REBALANCE_ON_CLOSE_BELOW_RANGE = True
|
||||||
TARGET_INVESTMENT_VALUE_USDC = 2000
|
TARGET_INVESTMENT_VALUE_USDC = 2000
|
||||||
INITIAL_HEDGE_CAPITAL_USDC = 2000 # Your starting Hyperliquid balance for Benchmark calc
|
INITIAL_HEDGE_CAPITAL_USDC = 1000 # Your starting Hyperliquid balance for Benchmark calc
|
||||||
RANGE_WIDTH_PCT = Decimal("0.01") # +/- 1% (2% total width)
|
RANGE_WIDTH_PCT = Decimal("0.05") # +/- 5% (10% total width)
|
||||||
SLIPPAGE_TOLERANCE = Decimal("0.02") # do not change, or at least remember it ( 0.02 = 2.0% slippage tolerance)
|
SLIPPAGE_TOLERANCE = Decimal("0.02") # do not change, or at least remember it ( 0.02 = 2.0% slippage tolerance)
|
||||||
TRANSACTION_TIMEOUT_SECONDS = 30
|
TRANSACTION_TIMEOUT_SECONDS = 30
|
||||||
|
|
||||||
@ -179,9 +179,10 @@ def send_transaction_robust(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 1. Prepare Params
|
# 1. Prepare Params
|
||||||
|
# Use 'pending' to ensure we get the correct nonce if a tx was just sent/mined
|
||||||
tx_params = {
|
tx_params = {
|
||||||
'from': account.address,
|
'from': account.address,
|
||||||
'nonce': w3.eth.get_transaction_count(account.address),
|
'nonce': w3.eth.get_transaction_count(account.address, 'pending'),
|
||||||
'value': value,
|
'value': value,
|
||||||
'chainId': w3.eth.chain_id,
|
'chainId': w3.eth.chain_id,
|
||||||
}
|
}
|
||||||
@ -541,7 +542,7 @@ def mint_new_position(w3: Web3, npm_contract, account: LocalAccount, token0: str
|
|||||||
# IncreaseLiquidity Event (Topic0)
|
# IncreaseLiquidity Event (Topic0)
|
||||||
increase_liq_topic = Web3.keccak(text="IncreaseLiquidity(uint256,uint128,uint256,uint256)").hex()
|
increase_liq_topic = Web3.keccak(text="IncreaseLiquidity(uint256,uint128,uint256,uint256)").hex()
|
||||||
|
|
||||||
minted_data = {'token_id': None, 'amount0': 0, 'amount1': 0}
|
minted_data = {'token_id': None, 'liquidity': 0, 'amount0': 0, 'amount1': 0}
|
||||||
|
|
||||||
for log in receipt.logs:
|
for log in receipt.logs:
|
||||||
topics = [t.hex() for t in log['topics']]
|
topics = [t.hex() for t in log['topics']]
|
||||||
@ -560,6 +561,7 @@ def mint_new_position(w3: Web3, npm_contract, account: LocalAccount, token0: str
|
|||||||
data = data[2:]
|
data = data[2:]
|
||||||
|
|
||||||
# liquidity is first 32 bytes (padded), amt0 next 32, amt1 next 32
|
# liquidity is first 32 bytes (padded), amt0 next 32, amt1 next 32
|
||||||
|
minted_data['liquidity'] = int(data[0:64], 16)
|
||||||
minted_data['amount0'] = int(data[64:128], 16)
|
minted_data['amount0'] = int(data[64:128], 16)
|
||||||
minted_data['amount1'] = int(data[128:192], 16)
|
minted_data['amount1'] = int(data[128:192], 16)
|
||||||
|
|
||||||
@ -872,6 +874,7 @@ def main():
|
|||||||
"entry_price": round(entry_price, 2),
|
"entry_price": round(entry_price, 2),
|
||||||
"amount0_initial": round(fmt_amt0, 4),
|
"amount0_initial": round(fmt_amt0, 4),
|
||||||
"amount1_initial": round(fmt_amt1, 2),
|
"amount1_initial": round(fmt_amt1, 2),
|
||||||
|
"liquidity": str(minted['liquidity']),
|
||||||
"range_upper": round(float(price_from_tick(tick_upper, d0, d1)), 2),
|
"range_upper": round(float(price_from_tick(tick_upper, d0, d1)), 2),
|
||||||
"range_lower": round(float(price_from_tick(tick_lower, d0, d1)), 2),
|
"range_lower": round(float(price_from_tick(tick_lower, d0, d1)), 2),
|
||||||
"timestamp_open": int(time.time())
|
"timestamp_open": int(time.time())
|
||||||
|
|||||||
Reference in New Issue
Block a user