working version, before optimalization
This commit is contained in:
196
clp_hedger.py
196
clp_hedger.py
@ -14,14 +14,9 @@ current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(current_dir)
|
||||
sys.path.append(project_root)
|
||||
|
||||
# Import local modules
|
||||
try:
|
||||
from logging_utils import setup_logging
|
||||
except ImportError:
|
||||
setup_logging = None
|
||||
# Ensure root logger is clean if we can't use setup_logging
|
||||
logging.getLogger().handlers.clear()
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
# Ensure root logger is clean
|
||||
logging.getLogger().handlers.clear()
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
from eth_account import Account
|
||||
from hyperliquid.exchange import Exchange
|
||||
@ -233,10 +228,12 @@ class HyperliquidStrategy:
|
||||
adj_pct = -norm_dist * max_boost
|
||||
adj_pct = max(-max_boost, min(max_boost, adj_pct))
|
||||
|
||||
raw_target_short = pool_delta
|
||||
|
||||
# --- BOTTOM STRATEGY LOGIC ---
|
||||
if strategy_type == "BOTTOM":
|
||||
# --- FIXED STRATEGY LOGIC ---
|
||||
if strategy_type == "FIXED":
|
||||
# Target is exactly the pool delta at entry price
|
||||
raw_target_short = self.get_pool_delta(self.entry_price)
|
||||
adj_pct = Decimal("0")
|
||||
elif strategy_type == "BOTTOM":
|
||||
if current_price > self.entry_price:
|
||||
# Disable hedging in upper half
|
||||
raw_target_short = Decimal("0")
|
||||
@ -287,13 +284,23 @@ class UnifiedHedger:
|
||||
|
||||
# Market Data Cache
|
||||
self.last_prices = {}
|
||||
self.price_history = {} # Symbol -> List[Decimal]
|
||||
self.price_history = {} # Symbol -> List[Decimal] (Fast: 1s samples)
|
||||
self.last_trade_times = {} # Symbol -> timestamp
|
||||
self.last_idle_log_times = {} # Symbol -> timestamp
|
||||
|
||||
# Shadow Orders (Global List)
|
||||
self.shadow_orders = []
|
||||
|
||||
# State: Emergency Close Hysteresis
|
||||
# Map: (file_path, token_id) -> bool
|
||||
self.emergency_close_active = {}
|
||||
|
||||
# Map: (file_path, token_id) -> Decimal (Locked hedge size)
|
||||
self.custom_fixed_targets = {}
|
||||
|
||||
# Map: (file_path, token_id) -> Decimal (Price when hedge leg opened)
|
||||
self.hedge_entry_prices = {}
|
||||
|
||||
self.startup_time = time.time()
|
||||
|
||||
logger.info(f"[CLP_HEDGER] Master Hedger initialized. Agent: {self.account.address}")
|
||||
@ -455,6 +462,7 @@ class UnifiedHedger:
|
||||
self.strategy_states[key]['pnl'] = to_decimal(entry.get('hedge_pnl_realized', 0))
|
||||
self.strategy_states[key]['fees'] = to_decimal(entry.get('hedge_fees_paid', 0))
|
||||
self.strategy_states[key]['status'] = entry.get('status', 'OPEN')
|
||||
self.strategy_states[key]['clp_fees'] = to_decimal(entry.get('clp_fees', 0))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading {filename}: {e}. Skipping updates.")
|
||||
@ -506,12 +514,23 @@ class UnifiedHedger:
|
||||
"start_time": start_time_ms,
|
||||
"pnl": to_decimal(position_data.get('hedge_pnl_realized', 0)),
|
||||
"fees": to_decimal(position_data.get('hedge_fees_paid', 0)),
|
||||
"clp_fees": to_decimal(position_data.get('clp_fees', 0)),
|
||||
"hedge_TotPnL": to_decimal(position_data.get('hedge_TotPnL', 0)), # NEW: Total Closed PnL
|
||||
"entry_price": entry_price, # Store for fishing logic
|
||||
"status": position_data.get('status', 'OPEN')
|
||||
}
|
||||
|
||||
# Initial hedge entry price is the CLP entry price
|
||||
self.hedge_entry_prices[key] = entry_price
|
||||
|
||||
logger.info(f"[STRAT] Init {key[1]} ({coin_symbol}) | Range: {lower}-{upper}")
|
||||
|
||||
# Ensure JSON has these fields initialized
|
||||
update_position_stats(key[0], key[1], {
|
||||
"hedge_TotPnL": float(self.strategy_states[key]['hedge_TotPnL']),
|
||||
"hedge_fees_paid": float(self.strategy_states[key]['fees'])
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to init strategy {key[1]}: {e}")
|
||||
|
||||
@ -680,7 +699,7 @@ class UnifiedHedger:
|
||||
price = to_decimal(mids[coin])
|
||||
self.last_prices[coin] = price
|
||||
|
||||
# Update Price History
|
||||
# Update Price History (Fast)
|
||||
if coin not in self.price_history: self.price_history[coin] = []
|
||||
self.price_history[coin].append(price)
|
||||
if len(self.price_history[coin]) > 300: self.price_history[coin].pop(0)
|
||||
@ -701,13 +720,50 @@ class UnifiedHedger:
|
||||
if coin not in aggregates:
|
||||
aggregates[coin] = {'target_short': Decimal("0"), 'contributors': 0, 'is_at_edge': False, 'is_at_bottom_edge': False, 'adj_pct': Decimal("0"), 'is_closing': False}
|
||||
|
||||
if status == 'CLOSING':
|
||||
# If Closing, we want target to be 0 for this strategy
|
||||
logger.info(f"[STRAT] {key[1]} is CLOSING -> Force Target 0")
|
||||
# --- EMERGENCY UPPER EDGE CLOSING (HYSTERESIS) ---
|
||||
# Logic: If price hits Top, close hedge. Do NOT re-open until price drops back to 75% of Range (FIXED) or Buffer (Others).
|
||||
|
||||
is_active_hysteresis = self.emergency_close_active.get(key, False)
|
||||
|
||||
if is_active_hysteresis:
|
||||
# CHECK RESET CONDITION
|
||||
if strategy_type == "FIXED":
|
||||
# Reset at 75% of range (from Bottom)
|
||||
range_width = strat.high_range - strat.low_range
|
||||
reset_threshold = strat.low_range + (range_width * Decimal("0.75"))
|
||||
else:
|
||||
reset_threshold = strat.high_range * Decimal("0.999")
|
||||
|
||||
if price < reset_threshold:
|
||||
logger.info(f"[STRAT] {key[1]} Price reset ({price:.2f} < {reset_threshold:.2f}). Resuming hedge.")
|
||||
self.emergency_close_active[key] = False
|
||||
is_active_hysteresis = False
|
||||
|
||||
# Capture NEW Dynamic Fixed Target and Entry Price
|
||||
if strategy_type == "FIXED":
|
||||
dynamic_delta = strat.get_pool_delta(price)
|
||||
self.custom_fixed_targets[key] = dynamic_delta
|
||||
self.hedge_entry_prices[key] = price
|
||||
logger.info(f"[STRAT] {key[1]} FIXED target reset to Dynamic Delta: {dynamic_delta:.4f} @ {price:.2f}")
|
||||
|
||||
if not is_active_hysteresis:
|
||||
# CHECK TRIGGER CONDITION
|
||||
if price >= strat.high_range:
|
||||
logger.warning(f"[STRAT] {key[1]} above High Range ({price:.2f} >= {strat.high_range:.2f}). Emergency closing hedge.")
|
||||
self.emergency_close_active[key] = True
|
||||
is_active_hysteresis = True
|
||||
# Reset entry price when closed
|
||||
self.hedge_entry_prices[key] = Decimal("0")
|
||||
|
||||
if status == 'CLOSING' or is_active_hysteresis:
|
||||
# If Closing OR Hysteresis Active, target is 0
|
||||
aggregates[coin]['is_closing'] = True
|
||||
# Do not add to target_short
|
||||
else:
|
||||
aggregates[coin]['target_short'] += calc['target_short']
|
||||
# Use custom fixed target if exists, else standard calc
|
||||
if strategy_type == "FIXED" and key in self.custom_fixed_targets:
|
||||
aggregates[coin]['target_short'] += self.custom_fixed_targets[key]
|
||||
else:
|
||||
aggregates[coin]['target_short'] += calc['target_short']
|
||||
|
||||
aggregates[coin]['contributors'] += 1
|
||||
aggregates[coin]['adj_pct'] = calc['adj_pct']
|
||||
@ -875,25 +931,69 @@ class UnifiedHedger:
|
||||
if existing_orders and not order_matched:
|
||||
for o in existing_orders: self.cancel_order(coin, o['oid'])
|
||||
|
||||
# --- THROTTLED STATUS LOGGING ---
|
||||
# --- REAL-TIME PnL CALCULATION & JSON UPDATE (1s) ---
|
||||
total_L_log = Decimal("0")
|
||||
for k_strat, strat_inst in self.strategies.items():
|
||||
if self.strategy_states[k_strat]['coin'] == coin:
|
||||
total_L_log += strat_inst.L
|
||||
|
||||
# Update all active strategies for this coin in JSON
|
||||
if total_L_log > 0 and price > 0:
|
||||
for k_strat, strat_inst in self.strategies.items():
|
||||
if self.strategy_states[k_strat]['coin'] != coin: continue
|
||||
|
||||
# CLP Value Calc
|
||||
def get_clp_value(p, s):
|
||||
if p <= s.low_range: return s.L * (p * (1/s.low_range.sqrt() - 1/s.high_range.sqrt()))
|
||||
if p >= s.high_range: return s.L * (s.high_range.sqrt() - s.low_range.sqrt())
|
||||
return s.L * (2*p.sqrt() - s.low_range.sqrt() - p/s.high_range.sqrt())
|
||||
|
||||
clp_curr_val = get_clp_value(price, strat_inst)
|
||||
|
||||
# Use Custom Fixed Target if exists
|
||||
target_size = self.custom_fixed_targets.get(k_strat, strat_inst.get_pool_delta(strat_inst.entry_price))
|
||||
|
||||
# USE TRACKED HEDGE ENTRY PRICE
|
||||
h_entry_px = self.hedge_entry_prices.get(k_strat, strat_inst.entry_price)
|
||||
if h_entry_px > 0:
|
||||
hedge_pnl_curr = (h_entry_px - price) * target_size
|
||||
else:
|
||||
hedge_pnl_curr = Decimal("0")
|
||||
|
||||
fee_close_curr = (target_size * price) * Decimal("0.000432")
|
||||
uni_fees = to_decimal(self.strategy_states[k_strat].get('clp_fees', 0))
|
||||
|
||||
# Retrieve Realized PnL & Fees from State
|
||||
realized_pnl = to_decimal(self.strategy_states[k_strat].get('hedge_TotPnL', 0))
|
||||
realized_fees = to_decimal(self.strategy_states[k_strat].get('fees', 0))
|
||||
|
||||
# Combined TotPnL = CLP_Unrealized + Hedge_Unrealized + Hedge_Realized - Hedge_Fees + CLP_Fees - Est_Close_Fee
|
||||
tot_curr = (clp_curr_val - strat_inst.target_value) + hedge_pnl_curr + realized_pnl - realized_fees - fee_close_curr + uni_fees
|
||||
|
||||
cur_hl_cost = realized_fees + fee_close_curr
|
||||
|
||||
# Sync to JSON every 1s
|
||||
update_position_stats(k_strat[0], k_strat[1], {
|
||||
"combined_TotPnL": round(float(tot_curr), 2),
|
||||
"hedge_HL_cost_est": round(float(cur_hl_cost), 2),
|
||||
"hedge_pnl_unrealized": round(float(hedge_pnl_curr), 2),
|
||||
"last_sync_hl": int(time.time())
|
||||
})
|
||||
|
||||
# --- THROTTLED STATUS LOGGING (300s) ---
|
||||
now = time.time()
|
||||
last_log = self.last_idle_log_times.get(coin, 0)
|
||||
monitor_interval = config.get("MONITOR_INTERVAL_SECONDS", 60)
|
||||
log_interval = config.get("LOG_INTERVAL_SECONDS", 300)
|
||||
|
||||
if now - last_log >= monitor_interval:
|
||||
if now - last_log >= log_interval:
|
||||
self.last_idle_log_times[coin] = now
|
||||
if is_asymmetric_blocked:
|
||||
logger.info(f"[ASYMMETRIC] Blocking BUY. Px ({price:.2f}) >= Eq ({p_mid_asym:.2f}) & Not Edge")
|
||||
|
||||
total_L_log = Decimal("0")
|
||||
for k_strat, strat_inst in self.strategies.items():
|
||||
if self.strategy_states[k_strat]['coin'] == coin:
|
||||
total_L_log += strat_inst.L
|
||||
|
||||
if total_L_log > 0 and price > 0:
|
||||
gamma_log = (Decimal("0.5") * total_L_log * (price ** Decimal("-1.5")))
|
||||
if gamma_log > 0:
|
||||
p_mid_log = price - (diff / gamma_log) # Corrected equilibrium formula
|
||||
p_mid_log = price - (diff / gamma_log)
|
||||
p_buy = price + (dynamic_thresh + diff) / gamma_log
|
||||
p_sell = price - (dynamic_thresh - diff) / gamma_log
|
||||
pad = " " if coin == "BNB" else ""
|
||||
@ -903,7 +1003,45 @@ class UnifiedHedger:
|
||||
fees = sum(s['fees'] for s in self.strategy_states.values() if s['coin'] == coin)
|
||||
total_pnl = (closed_pnl - fees) + unrealized
|
||||
|
||||
logger.info(f"[IDLE] {coin} | Px: {price:.2f}{pad} | M: {p_mid_log:.1f}{pad} | B: {p_buy:.1f}{pad} / S: {p_sell:.1f}{pad} | delta: {target_position:.4f}({diff:+.4f}) | Adj: {data.get('adj_pct',0)*100:+.2f}%, Vol: {vol_mult:.2f}, Thr: {dynamic_thresh:.4f} | PnL: {unrealized:.2f} | TotPnL: {total_pnl:.2f}")
|
||||
# Log individual strategy PnL
|
||||
if strategy_type == "FIXED":
|
||||
for k_strat, strat_inst in self.strategies.items():
|
||||
if self.strategy_states[k_strat]['coin'] != coin: continue
|
||||
|
||||
# Recalculate for logging (including bounds)
|
||||
clp_curr_val = get_clp_value(price, strat_inst)
|
||||
clp_low_val = get_clp_value(strat_inst.low_range, strat_inst)
|
||||
clp_high_val = get_clp_value(strat_inst.high_range, strat_inst)
|
||||
|
||||
# Use Custom Fixed Target if exists
|
||||
target_size = self.custom_fixed_targets.get(k_strat, strat_inst.get_pool_delta(strat_inst.entry_price))
|
||||
h_entry_px = self.hedge_entry_prices.get(k_strat, strat_inst.entry_price)
|
||||
|
||||
if h_entry_px > 0:
|
||||
hedge_pnl_curr = (h_entry_px - price) * target_size
|
||||
hedge_pnl_low = (h_entry_px - strat_inst.low_range) * target_size
|
||||
hedge_pnl_high = (h_entry_px - strat_inst.high_range) * target_size
|
||||
fee_open = (target_size * h_entry_px) * Decimal("0.000144")
|
||||
else:
|
||||
hedge_pnl_curr = hedge_pnl_low = hedge_pnl_high = Decimal("0")
|
||||
fee_open = Decimal("0")
|
||||
|
||||
fee_close_curr = (target_size * price) * Decimal("0.000432")
|
||||
fee_close_low = (target_size * strat_inst.low_range) * Decimal("0.000432")
|
||||
fee_close_high = (target_size * strat_inst.high_range) * Decimal("0.000432")
|
||||
|
||||
uni_fees = to_decimal(self.strategy_states[k_strat].get('clp_fees', 0))
|
||||
tot_curr = (clp_curr_val - strat_inst.target_value) + hedge_pnl_curr - (fee_open + fee_close_curr) + uni_fees
|
||||
tot_low = (clp_low_val - strat_inst.target_value) + hedge_pnl_low - (fee_open + fee_close_low) + uni_fees
|
||||
tot_high = (clp_high_val - strat_inst.target_value) + hedge_pnl_high - (fee_open + fee_close_high) + uni_fees
|
||||
|
||||
cur_hl_cost = fee_open + fee_close_curr
|
||||
|
||||
# ID or Range to distinguish
|
||||
strat_id = str(k_strat[1]) # Token ID
|
||||
logger.info(f"[FIXED] {coin} #{strat_id} | TotPnL: {tot_curr:+.2f} | Down: {tot_low:+.2f} | Up: {tot_high:+.2f} (Inc: Fees ${uni_fees:.2f}, HL Cost ${cur_hl_cost:.2f})")
|
||||
|
||||
logger.info(f"[IDLE] {coin} | Px: {price:.2f}{pad} | M: {p_mid_log:.1f}{pad} | B: {p_buy:.1f}{pad} / S: {p_sell:.1f}{pad} | delta: {target_position:.4f}({diff:+.4f}) | Adj: {data.get('adj_pct',0)*100:+.2f}%, Vol: {vol_mult:.2f}, Thr: {dynamic_thresh:.4f} | PnL: {unrealized:.2f} | HedgePnL: {total_pnl:.2f}")
|
||||
else:
|
||||
logger.info(f"[IDLE] {coin} | Px: {price:.2f} | delta: {target_position:.4f} | Diff: {diff:.4f} (Thresh: {dynamic_thresh:.4f})")
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user