optimalized parameters
This commit is contained in:
@ -30,7 +30,7 @@ setup_logging("normal", "SCALPER_HEDGER")
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
COIN_SYMBOL = "ETH"
|
||||
CHECK_INTERVAL = 1 # Faster check for scalper
|
||||
CHECK_INTERVAL = 5 # Optimized for cost/noise reduction (was 1)
|
||||
LEVERAGE = 5 # 3x Leverage
|
||||
STATUS_FILE = "hedge_status.json"
|
||||
|
||||
@ -39,8 +39,8 @@ STATUS_FILE = "hedge_status.json"
|
||||
ZONE_BOTTOM_HEDGE_LIMIT = 0.5
|
||||
|
||||
# Close Zone: 15% to 20% -> Close All Hedges (Flatten)
|
||||
ZONE_CLOSE_START = 0.51
|
||||
ZONE_CLOSE_END = 0.52
|
||||
ZONE_CLOSE_START = 0.52
|
||||
ZONE_CLOSE_END = 0.54
|
||||
|
||||
# Middle Zone: 20% to 85% -> Idle (No new orders, keep existing)
|
||||
# Implied by gaps between other zones.
|
||||
@ -49,8 +49,8 @@ ZONE_CLOSE_END = 0.52
|
||||
ZONE_TOP_HEDGE_START = 0.8
|
||||
|
||||
# --- ORDER SETTINGS ---
|
||||
PRICE_BUFFER_PCT = 0.0005 # 0.05% price move triggers order update
|
||||
MIN_THRESHOLD_ETH = 0.01 # Minimum trade size in ETH
|
||||
PRICE_BUFFER_PCT = 0.002 # 0.2% price move triggers order update (Relaxed for cost)
|
||||
MIN_THRESHOLD_ETH = 0.02 # Minimum trade size in ETH (~$60, Reduced frequency)
|
||||
MIN_ORDER_VALUE_USD = 10.0 # Minimum order value for API safety
|
||||
|
||||
def get_active_automatic_position():
|
||||
@ -286,6 +286,24 @@ class ScalperHedger:
|
||||
return 4
|
||||
except: return 4
|
||||
|
||||
def get_order_book_levels(self, coin):
|
||||
try:
|
||||
l2_snapshot = self.info.l2_snapshot(coin)
|
||||
if l2_snapshot and 'levels' in l2_snapshot:
|
||||
bids = l2_snapshot['levels'][0]
|
||||
asks = l2_snapshot['levels'][1]
|
||||
if bids and asks:
|
||||
best_bid = float(bids[0]['px'])
|
||||
best_ask = float(asks[0]['px'])
|
||||
mid = (best_bid + best_ask) / 2
|
||||
return {'bid': best_bid, 'ask': best_ask, 'mid': mid}
|
||||
# Fallback
|
||||
px = self.get_market_price(coin)
|
||||
return {'bid': px, 'ask': px, 'mid': px}
|
||||
except:
|
||||
px = self.get_market_price(coin)
|
||||
return {'bid': px, 'ask': px, 'mid': px}
|
||||
|
||||
def get_market_price(self, coin):
|
||||
try:
|
||||
mids = self.info.all_mids()
|
||||
@ -321,9 +339,12 @@ class ScalperHedger:
|
||||
user_state = self.info.user_state(self.vault_address or self.account.address)
|
||||
for pos in user_state["assetPositions"]:
|
||||
if pos["position"]["coin"] == coin:
|
||||
return float(pos["position"]["szi"])
|
||||
return 0.0
|
||||
except: return 0.0
|
||||
return {
|
||||
'size': float(pos["position"]["szi"]),
|
||||
'pnl': float(pos["position"]["unrealizedPnl"])
|
||||
}
|
||||
return {'size': 0.0, 'pnl': 0.0}
|
||||
except: return {'size': 0.0, 'pnl': 0.0}
|
||||
|
||||
def get_open_orders(self):
|
||||
try:
|
||||
@ -341,10 +362,12 @@ class ScalperHedger:
|
||||
logging.info(f"🕒 PLACING LIMIT: {coin} {'BUY' if is_buy else 'SELL'} {size} @ {price:.2f}")
|
||||
reduce_only = is_buy
|
||||
try:
|
||||
# Gtc order (Maker)
|
||||
# Gtc order (Maker) -> Changed to Alo to force Maker
|
||||
limit_px = round_to_sig_figs(price, 5)
|
||||
|
||||
order_result = self.exchange.order(coin, is_buy, size, limit_px, {"limit": {"tif": "Gtc"}}, reduce_only=reduce_only)
|
||||
# Use 'Alo' (Add Liquidity Only) to ensure Maker rebate.
|
||||
# If price crosses spread, order is rejected (safe cost-wise).
|
||||
order_result = self.exchange.order(coin, is_buy, size, limit_px, {"limit": {"tif": "Alo"}}, reduce_only=reduce_only)
|
||||
status = order_result["status"]
|
||||
if status == "ok":
|
||||
response_data = order_result["response"]["data"]
|
||||
@ -421,18 +444,69 @@ class ScalperHedger:
|
||||
self.cancel_order(COIN_SYMBOL, o['oid'])
|
||||
|
||||
price = self.get_market_price(COIN_SYMBOL)
|
||||
current_pos = self.get_current_position(COIN_SYMBOL)
|
||||
pos_data = self.get_current_position(COIN_SYMBOL)
|
||||
current_pos = pos_data['size']
|
||||
|
||||
if current_pos == 0: return
|
||||
|
||||
is_buy = current_pos < 0
|
||||
final_size = round_to_sz_decimals(abs(current_pos), self.sz_decimals)
|
||||
if final_size == 0: return
|
||||
|
||||
# Market order for closing
|
||||
self.exchange.order(COIN_SYMBOL, is_buy, final_size, round_to_sig_figs(price * (1.05 if is_buy else 0.95), 5), {"limit": {"tif": "Ioc"}}, reduce_only=True)
|
||||
price = self.get_market_price(COIN_SYMBOL) # Get mid price for safety fallback
|
||||
pos_data = self.get_current_position(COIN_SYMBOL)
|
||||
current_pos = pos_data['size']
|
||||
|
||||
if current_pos == 0: return
|
||||
|
||||
is_buy_to_close = current_pos < 0
|
||||
final_size = round_to_sz_decimals(abs(current_pos), self.sz_decimals)
|
||||
if final_size == 0: return
|
||||
|
||||
# --- ATTEMPT MAKER CLOSE (Alo) ---
|
||||
try:
|
||||
book_levels = self.get_order_book_levels(COIN_SYMBOL)
|
||||
TICK_SIZE = 0.1
|
||||
|
||||
if is_buy_to_close: # We are short, need to buy to close
|
||||
maker_price = book_levels['bid'] - TICK_SIZE
|
||||
else: # We are long, need to sell to close
|
||||
maker_price = book_levels['ask'] + TICK_SIZE
|
||||
|
||||
logging.info(f"Attempting MAKER CLOSE (Alo): {COIN_SYMBOL} {'BUY' if is_buy_to_close else 'SELL'} {final_size} @ {maker_price:.2f}")
|
||||
order_result = self.exchange.order(COIN_SYMBOL, is_buy_to_close, final_size, round_to_sig_figs(maker_price, 5), {"limit": {"tif": "Alo"}}, reduce_only=True)
|
||||
|
||||
status = order_result["status"]
|
||||
if status == "ok":
|
||||
response_data = order_result["response"]["data"]
|
||||
if "statuses" in response_data and "resting" in response_data["statuses"][0]:
|
||||
logging.info(f"✅ MAKER CLOSE Order Placed (Alo). OID: {response_data['statuses'][0]['resting']['oid']}")
|
||||
return
|
||||
elif "statuses" in response_data and "filled" in response_data["statuses"][0]:
|
||||
logging.info(f"✅ MAKER CLOSE Order Filled (Alo). OID: {response_data['statuses'][0]['filled']['oid']}")
|
||||
return
|
||||
else:
|
||||
# Fallback if Alo didn't rest or fill immediately in an expected way
|
||||
logging.warning(f"Alo order result unclear: {order_result}. Falling back to Market Close.")
|
||||
|
||||
elif status == "error":
|
||||
if "Post only order would have immediately matched" in order_result["response"]["data"]["statuses"][0].get("error", ""):
|
||||
logging.warning("Alo order would have immediately matched. Falling back to Market Close for guaranteed fill.")
|
||||
else:
|
||||
logging.error(f"Alo order failed with unknown error: {order_result}. Falling back to Market Close.")
|
||||
else:
|
||||
logging.warning(f"Alo order failed with status {status}. Falling back to Market Close.")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Exception during Alo close attempt: {e}. Falling back to Market Close.", exc_info=True)
|
||||
|
||||
# --- FALLBACK TO MARKET CLOSE (Ioc) for guaranteed fill ---
|
||||
logging.info(f"Falling back to MARKET CLOSE (Ioc): {COIN_SYMBOL} {'BUY' if is_buy_to_close else 'SELL'} {final_size} @ {price:.2f} (guaranteed)")
|
||||
self.exchange.order(COIN_SYMBOL, is_buy_to_close, final_size, round_to_sig_figs(price * (1.05 if is_buy_to_close else 0.95), 5), {"limit": {"tif": "Ioc"}}, reduce_only=True)
|
||||
self.active_position_id = None
|
||||
logging.info("✅ MARKET CLOSE Order Placed (Ioc).")
|
||||
except Exception as e:
|
||||
logging.error(f"Error closing: {e}")
|
||||
logging.error(f"Error closing positions: {e}", exc_info=True)
|
||||
|
||||
def run(self):
|
||||
logging.info(f"Starting Scalper Monitor Loop. Interval: {CHECK_INTERVAL}s")
|
||||
@ -467,13 +541,17 @@ class ScalperHedger:
|
||||
continue
|
||||
|
||||
# 2. Market Data
|
||||
price = self.get_order_book_mid(COIN_SYMBOL)
|
||||
book_levels = self.get_order_book_levels(COIN_SYMBOL)
|
||||
price = book_levels['mid']
|
||||
|
||||
if price is None:
|
||||
time.sleep(5)
|
||||
continue
|
||||
|
||||
funding_rate = self.get_funding_rate(COIN_SYMBOL)
|
||||
current_pos_size = self.get_current_position(COIN_SYMBOL)
|
||||
pos_data = self.get_current_position(COIN_SYMBOL)
|
||||
current_pos_size = pos_data['size']
|
||||
current_pnl = pos_data['pnl']
|
||||
|
||||
# 3. Calculate Logic
|
||||
calc = self.strategy.calculate_rebalance(price, current_pos_size)
|
||||
@ -498,8 +576,8 @@ class ScalperHedger:
|
||||
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 JSON with zone prices if they are None (initially set by uniswap_manager.py)
|
||||
if active_pos.get('zone_bottom_limit_price') is None:
|
||||
update_position_zones_in_json(active_pos['token_id'], {
|
||||
'zone_top_start_price': round(zone_top_start_price, 2),
|
||||
'zone_close_top_price': round(zone_close_top_price, 2),
|
||||
@ -513,7 +591,7 @@ class ScalperHedger:
|
||||
|
||||
# --- Execute Logic ---
|
||||
if in_close_zone:
|
||||
logging.info(f"ZONE: CLOSE ({price:.2f} in {zone_close_bottom_price:.2f}-{zone_close_top_price:.2f}). Closing all hedge positions.")
|
||||
logging.info(f"ZONE: CLOSE ({price:.2f} in {zone_close_bottom_price:.2f}-{zone_close_top_price:.2f}). PNL: ${current_pnl:.2f}. Closing all hedge positions.")
|
||||
self.close_all_positions()
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
continue
|
||||
@ -532,20 +610,31 @@ class ScalperHedger:
|
||||
min_trade_size = MIN_ORDER_VALUE_USD / price
|
||||
|
||||
if trade_size < min_trade_size:
|
||||
logging.info(f"Idle. Trade size {trade_size} < Min Order Size {min_trade_size:.4f} (${MIN_ORDER_VALUE_USD:.2f})")
|
||||
logging.info(f"Idle. Trade size {trade_size} < Min Order Size {min_trade_size:.4f} (${MIN_ORDER_VALUE_USD:.2f}). PNL: ${current_pnl:.2f}")
|
||||
elif trade_size > 0:
|
||||
logging.info(f"⚡ THRESHOLD TRIGGERED ({diff_abs:.4f} >= {rebalance_threshold:.4f}). In Hedge Zone.")
|
||||
logging.info(f"⚡ THRESHOLD TRIGGERED ({diff_abs:.4f} >= {rebalance_threshold:.4f}). In Hedge Zone. PNL: ${current_pnl:.2f}")
|
||||
# Execute Passively for Alo
|
||||
# Force 1 tick offset (0.1) away from BBO to ensure rounding doesn't cause cross
|
||||
# Sell at Ask + 0.1, Buy at Bid - 0.1
|
||||
TICK_SIZE = 0.1
|
||||
|
||||
is_buy = (calc['action'] == "BUY")
|
||||
self.place_limit_order(COIN_SYMBOL, is_buy, trade_size, price)
|
||||
|
||||
if is_buy:
|
||||
exec_price = book_levels['bid'] - TICK_SIZE
|
||||
else:
|
||||
exec_price = book_levels['ask'] + TICK_SIZE
|
||||
|
||||
self.place_limit_order(COIN_SYMBOL, is_buy, trade_size, exec_price)
|
||||
else:
|
||||
logging.info("Trade size rounds to 0. Skipping.")
|
||||
logging.info(f"Trade size rounds to 0. Skipping. PNL: ${current_pnl:.2f}")
|
||||
else:
|
||||
logging.info(f"Idle. Diff {diff_abs:.4f} < Threshold {rebalance_threshold:.4f}. In Hedge Zone.")
|
||||
logging.info(f"Idle. Diff {diff_abs:.4f} < Threshold {rebalance_threshold:.4f}. In Hedge Zone. PNL: ${current_pnl:.2f}")
|
||||
|
||||
else:
|
||||
# MIDDLE ZONE (IDLE)
|
||||
pct_position = (price - clp_low_range) / range_width
|
||||
logging.info(f"Idle. In Middle Zone ({pct_position*100:.1f}%). No Actions.")
|
||||
logging.info(f"Idle. In Middle Zone ({pct_position*100:.1f}%). PNL: ${current_pnl:.2f}. No Actions.")
|
||||
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
|
||||
|
||||
@ -392,5 +392,81 @@
|
||||
"zone_close_start_price": 3083.4622618522967,
|
||||
"zone_close_end_price": 3084.080897853553,
|
||||
"zone_top_start_price": 3102.6399778912387
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5157680,
|
||||
"opened": "22:21 14/12/25",
|
||||
"status": "CLOSED",
|
||||
"entry_price": 3090.84,
|
||||
"target_value": 1979.52,
|
||||
"amount0_initial": 0.3137,
|
||||
"amount1_initial": 1009.93,
|
||||
"range_upper": 3121.29,
|
||||
"zone_top_start_price": 3108.93,
|
||||
"zone_close_top_price": 3092.24,
|
||||
"zone_close_bottom_price": 3091.0,
|
||||
"zone_bottom_limit_price": 3090.39,
|
||||
"range_lower": 3059.48,
|
||||
"static_long": 0.0,
|
||||
"timestamp_open": 1765747295,
|
||||
"timestamp_close": 1765755472
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5157819,
|
||||
"opened": "00:45 15/12/25",
|
||||
"status": "CLOSED",
|
||||
"entry_price": 3058.26,
|
||||
"target_value": 1980.8,
|
||||
"amount0_initial": 0.3044,
|
||||
"amount1_initial": 1049.83,
|
||||
"range_upper": 3087.14,
|
||||
"zone_top_start_price": 3074.92,
|
||||
"zone_close_top_price": 3059.02,
|
||||
"zone_close_bottom_price": 3057.8,
|
||||
"zone_bottom_limit_price": 3056.58,
|
||||
"range_lower": 3026.02,
|
||||
"static_long": 0.0,
|
||||
"timestamp_open": 1765755940,
|
||||
"timestamp_close": 1765762761
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5157922,
|
||||
"opened": "02:47 15/12/25",
|
||||
"status": "CLOSED",
|
||||
"entry_price": 3104.56,
|
||||
"target_value": 1980.84,
|
||||
"amount0_initial": 0.2967,
|
||||
"amount1_initial": 1059.84,
|
||||
"range_upper": 3133.8,
|
||||
"zone_top_start_price": 3121.39,
|
||||
"zone_close_top_price": 3105.26,
|
||||
"zone_close_bottom_price": 3104.02,
|
||||
"zone_bottom_limit_price": 3102.78,
|
||||
"range_lower": 3071.75,
|
||||
"static_long": 0.0,
|
||||
"timestamp_open": 1765763228,
|
||||
"timestamp_close": 1765765504
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5158011,
|
||||
"opened": "03:32 15/12/25",
|
||||
"status": "OPEN",
|
||||
"entry_price": 3135.31,
|
||||
"target_value": 1983.24,
|
||||
"amount0_initial": 0.3009,
|
||||
"amount1_initial": 1039.86,
|
||||
"range_upper": 3165.29,
|
||||
"zone_top_start_price": 3152.76,
|
||||
"zone_close_top_price": 3136.46,
|
||||
"zone_close_bottom_price": 3135.21,
|
||||
"zone_bottom_limit_price": 3133.95,
|
||||
"range_lower": 3102.62,
|
||||
"static_long": 0.0,
|
||||
"timestamp_open": 1765765971,
|
||||
"timestamp_close": null
|
||||
}
|
||||
]
|
||||
@ -644,6 +644,11 @@ def main():
|
||||
unclaimed0 = from_wei(fees_sim[0], pos_details['token0_decimals'])
|
||||
unclaimed1 = from_wei(fees_sim[1], pos_details['token1_decimals'])
|
||||
except: pass
|
||||
|
||||
# Calculate Total Fee Value in Token1 (USDC)
|
||||
# Get Current Price from Pool Data
|
||||
current_price = price_from_sqrt_price_x96(pool_data['sqrtPriceX96'], pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||
total_fees_usd = (unclaimed0 * current_price) + unclaimed1
|
||||
|
||||
# Check Range
|
||||
is_out_of_range = False
|
||||
@ -657,7 +662,7 @@ def main():
|
||||
|
||||
print(f"\nID: {token_id} | Type: {pos_type} | Status: {status_str}")
|
||||
print(f" Range: {position['range_lower']:.2f} - {position['range_upper']:.2f}")
|
||||
print(f" Fees: {unclaimed0:.4f} {pos_details['token0_symbol']} / {unclaimed1:.4f} {pos_details['token1_symbol']}")
|
||||
print(f" Fees: {unclaimed0:.4f} {pos_details['token0_symbol']} / {unclaimed1:.4f} {pos_details['token1_symbol']} (~${total_fees_usd:.2f})")
|
||||
|
||||
# --- AUTO CLOSE LOGIC (AUTOMATIC ONLY) ---
|
||||
if pos_type == 'AUTOMATIC' and CLOSE_POSITION_ENABLED and is_out_of_range:
|
||||
|
||||
Reference in New Issue
Block a user