diff --git a/clp_hedger/clp_scalper_hedger.py b/clp_hedger/clp_scalper_hedger.py index 4f0de2c..467174f 100644 --- a/clp_hedger/clp_scalper_hedger.py +++ b/clp_hedger/clp_scalper_hedger.py @@ -31,16 +31,16 @@ setup_logging("normal", "SCALPER_HEDGER") # --- CONFIGURATION --- COIN_SYMBOL = "ETH" CHECK_INTERVAL = 1 # Faster check for scalper -LEVERAGE = 5 +LEVERAGE = 5 # 3x Leverage STATUS_FILE = "hedge_status.json" # --- STRATEGY ZONES (Percent of Range Width) --- # Bottom Hedge Zone: 0% to 15% -> Active Hedging -ZONE_BOTTOM_HEDGE_LIMIT = 0.1 +ZONE_BOTTOM_HEDGE_LIMIT = 0.5 # Close Zone: 15% to 20% -> Close All Hedges (Flatten) -ZONE_CLOSE_START = 0.18 -ZONE_CLOSE_END = 0.20 +ZONE_CLOSE_START = 0.51 +ZONE_CLOSE_END = 0.52 # Middle Zone: 20% to 85% -> Idle (No new orders, keep existing) # Implied by gaps between other zones. @@ -50,7 +50,7 @@ ZONE_TOP_HEDGE_START = 0.8 # --- ORDER SETTINGS --- PRICE_BUFFER_PCT = 0.0005 # 0.05% price move triggers order update -MIN_THRESHOLD_ETH = 0.005 # Minimum trade size in ETH +MIN_THRESHOLD_ETH = 0.01 # Minimum trade size in ETH MIN_ORDER_VALUE_USD = 10.0 # Minimum order value for API safety def get_active_automatic_position(): @@ -67,25 +67,56 @@ def get_active_automatic_position(): return None def update_position_zones_in_json(token_id, zones_data): - """Updates the active position in JSON with calculated zone prices.""" + """Updates the active position in JSON with calculated zone prices and formats the entry.""" if not os.path.exists(STATUS_FILE): return try: with open(STATUS_FILE, 'r') as f: data = json.load(f) updated = False - for entry in data: + for i, entry in enumerate(data): if entry.get('type') == 'AUTOMATIC' and entry.get('status') == 'OPEN' and entry.get('token_id') == token_id: - # Update keys + + # Merge Zones for k, v in zones_data.items(): entry[k] = v + + # Format & Reorder + open_ts = entry.get('timestamp_open', int(time.time())) + opened_str = time.strftime('%H:%M %d/%m/%y', time.localtime(open_ts)) + + # Reconstruct Dict in Order + new_entry = { + "type": entry.get('type'), + "token_id": entry.get('token_id'), + "opened": opened_str, + "status": entry.get('status'), + "entry_price": round(entry.get('entry_price', 0), 2), + "target_value": round(entry.get('target_value', 0), 2), + # Amounts might be string or float or int. Ensure float. + "amount0_initial": round(float(entry.get('amount0_initial', 0)), 4), + "amount1_initial": round(float(entry.get('amount1_initial', 0)), 2), + + "range_upper": round(entry.get('range_upper', 0), 2), + "zone_top_start_price": entry.get('zone_top_start_price'), + "zone_close_top_price": entry.get('zone_close_top_price'), + "zone_close_bottom_price": entry.get('zone_close_bottom_price'), + "zone_bottom_limit_price": entry.get('zone_bottom_limit_price'), + "range_lower": round(entry.get('range_lower', 0), 2), + + "static_long": entry.get('static_long', 0.0), + "timestamp_open": open_ts, + "timestamp_close": entry.get('timestamp_close') + } + + data[i] = new_entry updated = True break if updated: with open(STATUS_FILE, 'w') as f: json.dump(data, f, indent=2) - logging.info(f"Updated JSON with Zone Prices for Position {token_id}") + logging.info(f"Updated JSON with Formatted Zone Prices for Position {token_id}") except Exception as e: logging.error(f"Error updating JSON zones: {e}") @@ -124,7 +155,10 @@ class HyperliquidStrategy: # Method 1: Use Amount0 (WETH) if entry_amount0 > 0: - amount0_eth = entry_amount0 / 10**18 + # If amount is huge (Wei), scale it. If small (ETH), use as is. + if entry_amount0 > 1000: amount0_eth = entry_amount0 / 10**18 + else: amount0_eth = entry_amount0 + denom0 = (1/sqrt_P) - (1/sqrt_Pb) if denom0 > 0.00000001: self.L = amount0_eth / denom0 @@ -132,7 +166,9 @@ class HyperliquidStrategy: # Method 2: Use Amount1 (USDC) if self.L == 0.0 and entry_amount1 > 0: - amount1_usdc = entry_amount1 / 10**6 + if entry_amount1 > 100000: amount1_usdc = entry_amount1 / 10**6 + else: amount1_usdc = entry_amount1 + denom1 = sqrt_P - sqrt_Pa if denom1 > 0.00000001: self.L = amount1_usdc / denom1 @@ -458,26 +494,26 @@ class ScalperHedger: # Calculate Prices for Zones zone_bottom_limit_price = clp_low_range + (range_width * ZONE_BOTTOM_HEDGE_LIMIT) - zone_close_start_price = clp_low_range + (range_width * ZONE_CLOSE_START) - zone_close_end_price = clp_low_range + (range_width * ZONE_CLOSE_END) + zone_close_bottom_price = clp_low_range + (range_width * ZONE_CLOSE_START) + zone_close_top_price = clp_low_range + (range_width * ZONE_CLOSE_END) zone_top_start_price = clp_low_range + (range_width * ZONE_TOP_HEDGE_START) # Update JSON with zone prices if missing if 'zone_bottom_limit_price' not in active_pos: update_position_zones_in_json(active_pos['token_id'], { - 'zone_bottom_limit_price': zone_bottom_limit_price, - 'zone_close_start_price': zone_close_start_price, - 'zone_close_end_price': zone_close_end_price, - 'zone_top_start_price': zone_top_start_price + 'zone_top_start_price': round(zone_top_start_price, 2), + 'zone_close_top_price': round(zone_close_top_price, 2), + 'zone_close_bottom_price': round(zone_close_bottom_price, 2), + 'zone_bottom_limit_price': round(zone_bottom_limit_price, 2) }) # Check Zones - in_close_zone = (price >= zone_close_start_price and price <= zone_close_end_price) + in_close_zone = (price >= zone_close_bottom_price and price <= zone_close_top_price) in_hedge_zone = (price <= zone_bottom_limit_price) or (price >= zone_top_start_price) # --- Execute Logic --- if in_close_zone: - logging.info(f"ZONE: CLOSE ({price:.2f} in {zone_close_start_price:.2f}-{zone_close_end_price:.2f}). Closing all hedge positions.") + logging.info(f"ZONE: CLOSE ({price:.2f} in {zone_close_bottom_price:.2f}-{zone_close_top_price:.2f}). Closing all hedge positions.") self.close_all_positions() time.sleep(CHECK_INTERVAL) continue diff --git a/clp_hedger/hedge_status.json b/clp_hedger/hedge_status.json index ad32c92..7fb9df3 100644 --- a/clp_hedger/hedge_status.json +++ b/clp_hedger/hedge_status.json @@ -360,7 +360,7 @@ { "type": "AUTOMATIC", "token_id": 5157395, - "status": "OPEN", + "status": "CLOSED", "entry_price": 3079.3931567773757, "range_lower": 3062.5442403757074, "range_upper": 3093.3217751359653, @@ -369,10 +369,28 @@ "amount1_initial": 188975073, "static_long": 0.0, "timestamp_open": 1765733564, - "timestamp_close": null, + "timestamp_close": 1765736225, "zone_bottom_limit_price": 3065.6219938517334, "zone_close_start_price": 3068.084196632554, "zone_close_end_price": 3068.699747327759, "zone_top_start_price": 3087.166268183914 + }, + { + "type": "AUTOMATIC", + "token_id": 5157445, + "status": "CLOSED", + "entry_price": 3095.4053081664565, + "range_lower": 3077.8945378409912, + "range_upper": 3108.8263379038003, + "target_value": 332.600152414756, + "amount0_initial": 44140371554667029, + "amount1_initial": 195967812, + "static_long": 0.0, + "timestamp_open": 1765736272, + "timestamp_close": 1765743062, + "zone_bottom_limit_price": 3080.987717847272, + "zone_close_start_price": 3083.4622618522967, + "zone_close_end_price": 3084.080897853553, + "zone_top_start_price": 3102.6399778912387 } ] \ No newline at end of file diff --git a/clp_hedger/uniswap_manager.py b/clp_hedger/uniswap_manager.py index 2e6e330..9027d88 100644 --- a/clp_hedger/uniswap_manager.py +++ b/clp_hedger/uniswap_manager.py @@ -76,15 +76,16 @@ RPC_URL = os.environ.get("MAINNET_RPC_URL") PRIVATE_KEY = os.environ.get("MAIN_WALLET_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY") # Script behavior flags -MONITOR_INTERVAL_SECONDS = 30 +MONITOR_INTERVAL_SECONDS = 451 COLLECT_FEES_ENABLED = False # If True, will attempt to collect fees once and exit if no open auto position CLOSE_POSITION_ENABLED = True # If True, will attempt to close auto position when out of range CLOSE_IF_OUT_OF_RANGE_ONLY = True # If True, closes only if out of range; if False, closes immediately OPEN_POSITION_ENABLED = True # If True, will open a new position if no auto position exists +REBALANCE_ON_CLOSE_BELOW_RANGE = False # If True, will sell 50% of WETH to USDC when closing below range # New Position Parameters -TARGET_INVESTMENT_VALUE_TOKEN1 = 350.0 # Target total investment value in Token1 terms (e.g. 350 USDC) -RANGE_WIDTH_PCT = 0.005 # +/- 2% range for new positions +TARGET_INVESTMENT_VALUE_TOKEN1 = 2000.0 # Target total investment value in Token1 terms (e.g. 350 USDC) +RANGE_WIDTH_PCT = 0.01 # +/- 2% range for new positions # JSON File for tracking position state STATUS_FILE = "hedge_status.json" @@ -131,20 +132,46 @@ def update_hedge_status_file(action, position_data): current_data = [] if action == "OPEN": + # Format Timestamp + open_ts = int(time.time()) + opened_str = time.strftime('%H:%M %d/%m/%y', time.localtime(open_ts)) + + # Scale Amounts + raw_amt0 = position_data.get('amount0_initial', 0) + raw_amt1 = position_data.get('amount1_initial', 0) + + # Handle if they are already scaled (unlikely here, but safe) + if raw_amt0 > 1000: fmt_amt0 = round(raw_amt0 / 10**18, 4) + else: fmt_amt0 = round(raw_amt0, 4) + + if raw_amt1 > 1000: fmt_amt1 = round(raw_amt1 / 10**6, 2) + else: fmt_amt1 = round(raw_amt1, 2) + new_entry = { "type": "AUTOMATIC", "token_id": position_data['token_id'], + "opened": opened_str, "status": "OPEN", - "entry_price": position_data['entry_price'], - "range_lower": position_data['range_lower'], - "range_upper": position_data['range_upper'], - "target_value": position_data.get('target_value', 0.0), # Save Actual Value as Target for hedging accuracy - "amount0_initial": position_data.get('amount0_initial', 0), - "amount1_initial": position_data.get('amount1_initial', 0), + "entry_price": round(position_data['entry_price'], 2), + "target_value": round(position_data.get('target_value', 0.0), 2), + "amount0_initial": fmt_amt0, + "amount1_initial": fmt_amt1, + + "range_upper": round(position_data['range_upper'], 2), + # Zones (if present in position_data, otherwise None/Skip) + "zone_top_start_price": round(position_data['zone_top_start_price'], 2) if 'zone_top_start_price' in position_data else None, + "zone_close_top_price": round(position_data['zone_close_end_price'], 2) if 'zone_close_end_price' in position_data else None, + "zone_close_bottom_price": round(position_data['zone_close_start_price'], 2) if 'zone_close_start_price' in position_data else None, + "zone_bottom_limit_price": round(position_data['zone_bottom_limit_price'], 2) if 'zone_bottom_limit_price' in position_data else None, + "range_lower": round(position_data['range_lower'], 2), + "static_long": 0.0, - "timestamp_open": int(time.time()), + "timestamp_open": open_ts, "timestamp_close": None } + # Remove None keys to keep it clean? Or keep structure? + # User wants specific structure. + current_data.append(new_entry) print(f"Recorded new AUTOMATIC position {position_data['token_id']} in {STATUS_FILE}") @@ -644,7 +671,7 @@ def main(): print("Position Closed & Status Updated.") # --- REBALANCE ON CLOSE (If Price Dropped) --- - if status_str == "OUT OF RANGE (BELOW)": + if REBALANCE_ON_CLOSE_BELOW_RANGE and status_str == "OUT OF RANGE (BELOW)": print("📉 Position closed BELOW range (100% ETH). Selling 50% of WETH inventory to USDC...") try: # Get WETH Balance