fixed hedge_status.json
This commit is contained in:
@ -31,16 +31,16 @@ setup_logging("normal", "SCALPER_HEDGER")
|
|||||||
# --- CONFIGURATION ---
|
# --- CONFIGURATION ---
|
||||||
COIN_SYMBOL = "ETH"
|
COIN_SYMBOL = "ETH"
|
||||||
CHECK_INTERVAL = 1 # Faster check for scalper
|
CHECK_INTERVAL = 1 # Faster check for scalper
|
||||||
LEVERAGE = 5
|
LEVERAGE = 5 # 3x Leverage
|
||||||
STATUS_FILE = "hedge_status.json"
|
STATUS_FILE = "hedge_status.json"
|
||||||
|
|
||||||
# --- STRATEGY ZONES (Percent of Range Width) ---
|
# --- STRATEGY ZONES (Percent of Range Width) ---
|
||||||
# Bottom Hedge Zone: 0% to 15% -> Active Hedging
|
# 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)
|
# Close Zone: 15% to 20% -> Close All Hedges (Flatten)
|
||||||
ZONE_CLOSE_START = 0.18
|
ZONE_CLOSE_START = 0.51
|
||||||
ZONE_CLOSE_END = 0.20
|
ZONE_CLOSE_END = 0.52
|
||||||
|
|
||||||
# Middle Zone: 20% to 85% -> Idle (No new orders, keep existing)
|
# Middle Zone: 20% to 85% -> Idle (No new orders, keep existing)
|
||||||
# Implied by gaps between other zones.
|
# Implied by gaps between other zones.
|
||||||
@ -50,7 +50,7 @@ ZONE_TOP_HEDGE_START = 0.8
|
|||||||
|
|
||||||
# --- ORDER SETTINGS ---
|
# --- ORDER SETTINGS ---
|
||||||
PRICE_BUFFER_PCT = 0.0005 # 0.05% price move triggers order update
|
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
|
MIN_ORDER_VALUE_USD = 10.0 # Minimum order value for API safety
|
||||||
|
|
||||||
def get_active_automatic_position():
|
def get_active_automatic_position():
|
||||||
@ -67,25 +67,56 @@ def get_active_automatic_position():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def update_position_zones_in_json(token_id, zones_data):
|
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
|
if not os.path.exists(STATUS_FILE): return
|
||||||
try:
|
try:
|
||||||
with open(STATUS_FILE, 'r') as f:
|
with open(STATUS_FILE, 'r') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
updated = False
|
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:
|
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():
|
for k, v in zones_data.items():
|
||||||
entry[k] = v
|
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
|
updated = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if updated:
|
if updated:
|
||||||
with open(STATUS_FILE, 'w') as f:
|
with open(STATUS_FILE, 'w') as f:
|
||||||
json.dump(data, f, indent=2)
|
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:
|
except Exception as e:
|
||||||
logging.error(f"Error updating JSON zones: {e}")
|
logging.error(f"Error updating JSON zones: {e}")
|
||||||
|
|
||||||
@ -124,7 +155,10 @@ class HyperliquidStrategy:
|
|||||||
|
|
||||||
# Method 1: Use Amount0 (WETH)
|
# Method 1: Use Amount0 (WETH)
|
||||||
if entry_amount0 > 0:
|
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)
|
denom0 = (1/sqrt_P) - (1/sqrt_Pb)
|
||||||
if denom0 > 0.00000001:
|
if denom0 > 0.00000001:
|
||||||
self.L = amount0_eth / denom0
|
self.L = amount0_eth / denom0
|
||||||
@ -132,7 +166,9 @@ class HyperliquidStrategy:
|
|||||||
|
|
||||||
# Method 2: Use Amount1 (USDC)
|
# Method 2: Use Amount1 (USDC)
|
||||||
if self.L == 0.0 and entry_amount1 > 0:
|
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
|
denom1 = sqrt_P - sqrt_Pa
|
||||||
if denom1 > 0.00000001:
|
if denom1 > 0.00000001:
|
||||||
self.L = amount1_usdc / denom1
|
self.L = amount1_usdc / denom1
|
||||||
@ -458,26 +494,26 @@ class ScalperHedger:
|
|||||||
|
|
||||||
# Calculate Prices for Zones
|
# Calculate Prices for Zones
|
||||||
zone_bottom_limit_price = clp_low_range + (range_width * ZONE_BOTTOM_HEDGE_LIMIT)
|
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_bottom_price = clp_low_range + (range_width * ZONE_CLOSE_START)
|
||||||
zone_close_end_price = clp_low_range + (range_width * ZONE_CLOSE_END)
|
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)
|
zone_top_start_price = clp_low_range + (range_width * ZONE_TOP_HEDGE_START)
|
||||||
|
|
||||||
# Update JSON with zone prices if missing
|
# Update JSON with zone prices if missing
|
||||||
if 'zone_bottom_limit_price' not in active_pos:
|
if 'zone_bottom_limit_price' not in active_pos:
|
||||||
update_position_zones_in_json(active_pos['token_id'], {
|
update_position_zones_in_json(active_pos['token_id'], {
|
||||||
'zone_bottom_limit_price': zone_bottom_limit_price,
|
'zone_top_start_price': round(zone_top_start_price, 2),
|
||||||
'zone_close_start_price': zone_close_start_price,
|
'zone_close_top_price': round(zone_close_top_price, 2),
|
||||||
'zone_close_end_price': zone_close_end_price,
|
'zone_close_bottom_price': round(zone_close_bottom_price, 2),
|
||||||
'zone_top_start_price': zone_top_start_price
|
'zone_bottom_limit_price': round(zone_bottom_limit_price, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Check Zones
|
# 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)
|
in_hedge_zone = (price <= zone_bottom_limit_price) or (price >= zone_top_start_price)
|
||||||
|
|
||||||
# --- Execute Logic ---
|
# --- Execute Logic ---
|
||||||
if in_close_zone:
|
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()
|
self.close_all_positions()
|
||||||
time.sleep(CHECK_INTERVAL)
|
time.sleep(CHECK_INTERVAL)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -360,7 +360,7 @@
|
|||||||
{
|
{
|
||||||
"type": "AUTOMATIC",
|
"type": "AUTOMATIC",
|
||||||
"token_id": 5157395,
|
"token_id": 5157395,
|
||||||
"status": "OPEN",
|
"status": "CLOSED",
|
||||||
"entry_price": 3079.3931567773757,
|
"entry_price": 3079.3931567773757,
|
||||||
"range_lower": 3062.5442403757074,
|
"range_lower": 3062.5442403757074,
|
||||||
"range_upper": 3093.3217751359653,
|
"range_upper": 3093.3217751359653,
|
||||||
@ -369,10 +369,28 @@
|
|||||||
"amount1_initial": 188975073,
|
"amount1_initial": 188975073,
|
||||||
"static_long": 0.0,
|
"static_long": 0.0,
|
||||||
"timestamp_open": 1765733564,
|
"timestamp_open": 1765733564,
|
||||||
"timestamp_close": null,
|
"timestamp_close": 1765736225,
|
||||||
"zone_bottom_limit_price": 3065.6219938517334,
|
"zone_bottom_limit_price": 3065.6219938517334,
|
||||||
"zone_close_start_price": 3068.084196632554,
|
"zone_close_start_price": 3068.084196632554,
|
||||||
"zone_close_end_price": 3068.699747327759,
|
"zone_close_end_price": 3068.699747327759,
|
||||||
"zone_top_start_price": 3087.166268183914
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -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")
|
PRIVATE_KEY = os.environ.get("MAIN_WALLET_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY")
|
||||||
|
|
||||||
# Script behavior flags
|
# 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
|
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_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
|
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
|
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
|
# New Position Parameters
|
||||||
TARGET_INVESTMENT_VALUE_TOKEN1 = 350.0 # Target total investment value in Token1 terms (e.g. 350 USDC)
|
TARGET_INVESTMENT_VALUE_TOKEN1 = 2000.0 # Target total investment value in Token1 terms (e.g. 350 USDC)
|
||||||
RANGE_WIDTH_PCT = 0.005 # +/- 2% range for new positions
|
RANGE_WIDTH_PCT = 0.01 # +/- 2% range for new positions
|
||||||
|
|
||||||
# JSON File for tracking position state
|
# JSON File for tracking position state
|
||||||
STATUS_FILE = "hedge_status.json"
|
STATUS_FILE = "hedge_status.json"
|
||||||
@ -131,20 +132,46 @@ def update_hedge_status_file(action, position_data):
|
|||||||
current_data = []
|
current_data = []
|
||||||
|
|
||||||
if action == "OPEN":
|
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 = {
|
new_entry = {
|
||||||
"type": "AUTOMATIC",
|
"type": "AUTOMATIC",
|
||||||
"token_id": position_data['token_id'],
|
"token_id": position_data['token_id'],
|
||||||
|
"opened": opened_str,
|
||||||
"status": "OPEN",
|
"status": "OPEN",
|
||||||
"entry_price": position_data['entry_price'],
|
"entry_price": round(position_data['entry_price'], 2),
|
||||||
"range_lower": position_data['range_lower'],
|
"target_value": round(position_data.get('target_value', 0.0), 2),
|
||||||
"range_upper": position_data['range_upper'],
|
"amount0_initial": fmt_amt0,
|
||||||
"target_value": position_data.get('target_value', 0.0), # Save Actual Value as Target for hedging accuracy
|
"amount1_initial": fmt_amt1,
|
||||||
"amount0_initial": position_data.get('amount0_initial', 0),
|
|
||||||
"amount1_initial": position_data.get('amount1_initial', 0),
|
"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,
|
"static_long": 0.0,
|
||||||
"timestamp_open": int(time.time()),
|
"timestamp_open": open_ts,
|
||||||
"timestamp_close": None
|
"timestamp_close": None
|
||||||
}
|
}
|
||||||
|
# Remove None keys to keep it clean? Or keep structure?
|
||||||
|
# User wants specific structure.
|
||||||
|
|
||||||
current_data.append(new_entry)
|
current_data.append(new_entry)
|
||||||
print(f"Recorded new AUTOMATIC position {position_data['token_id']} in {STATUS_FILE}")
|
print(f"Recorded new AUTOMATIC position {position_data['token_id']} in {STATUS_FILE}")
|
||||||
|
|
||||||
@ -644,7 +671,7 @@ def main():
|
|||||||
print("Position Closed & Status Updated.")
|
print("Position Closed & Status Updated.")
|
||||||
|
|
||||||
# --- REBALANCE ON CLOSE (If Price Dropped) ---
|
# --- 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...")
|
print("📉 Position closed BELOW range (100% ETH). Selling 50% of WETH inventory to USDC...")
|
||||||
try:
|
try:
|
||||||
# Get WETH Balance
|
# Get WETH Balance
|
||||||
|
|||||||
Reference in New Issue
Block a user