Update scripts with optimizations from florida directory
This commit is contained in:
@ -45,7 +45,7 @@ DEFAULT_STRATEGY = {
|
||||
"POSITION_CLOSED_EDGE_PROXIMITY_PCT": Decimal("0.025"), # Safety margin for closing positions
|
||||
"LARGE_HEDGE_MULTIPLIER": Decimal("5.0"), # Multiplier to bypass trade cooldown for big moves
|
||||
"ENABLE_EDGE_CLEANUP": True, # Force rebalances when price is at range boundaries
|
||||
"EDGE_CLEANUP_MARGIN_PCT": Decimal("0.02"), # % of range width used for edge detection
|
||||
"EDGE_CLEANUP_MARGIN_PCT": Decimal("0.03"), # % of range width used for edge detection
|
||||
"MAKER_ORDER_TIMEOUT": 600, # Timeout for resting Maker orders (seconds)
|
||||
"SHADOW_ORDER_TIMEOUT": 600, # Timeout for theoretical shadow order tracking
|
||||
"ENABLE_FISHING": False, # Use passive maker orders for rebalancing (advanced)
|
||||
@ -98,6 +98,7 @@ CLP_PROFILES = {
|
||||
"TOKEN_B_ADDRESS": "0x55d398326f99059fF775485246999027B3197955", # USDT
|
||||
"WRAPPED_NATIVE_ADDRESS": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
||||
"POOL_FEE": 100,
|
||||
"EDGE_CLEANUP_MARGIN_PCT": Decimal("0.1875"), # 0.1875 only for asymmetric shedge % of range width used for edge detection
|
||||
"RANGE_WIDTH_PCT": Decimal("0.004"),
|
||||
"TARGET_INVESTMENT_AMOUNT": 1000,
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.015"),
|
||||
|
||||
312
clp_hedger.py
312
clp_hedger.py
@ -275,6 +275,7 @@ class UnifiedHedger:
|
||||
self.last_prices = {}
|
||||
self.price_history = {} # Symbol -> List[Decimal]
|
||||
self.last_trade_times = {} # Symbol -> timestamp
|
||||
self.last_idle_log_times = {} # Symbol -> timestamp
|
||||
|
||||
# Shadow Orders (Global List)
|
||||
self.shadow_orders = []
|
||||
@ -667,7 +668,7 @@ class UnifiedHedger:
|
||||
calc = strat.calculate_rebalance(price, Decimal("0"))
|
||||
|
||||
if coin not in aggregates:
|
||||
aggregates[coin] = {'target_short': Decimal("0"), 'contributors': 0, 'is_at_edge': False, 'adj_pct': Decimal("0"), 'is_closing': False}
|
||||
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
|
||||
@ -693,6 +694,8 @@ class UnifiedHedger:
|
||||
|
||||
if dist_bottom_pct < safety_margin_pct or dist_top_pct < safety_margin_pct:
|
||||
aggregates[coin]['is_at_edge'] = True
|
||||
if dist_bottom_pct < safety_margin_pct:
|
||||
aggregates[coin]['is_at_bottom_edge'] = True
|
||||
|
||||
# Check Shadow Orders (Pre-Execution)
|
||||
self.check_shadow_orders(l2_snapshots)
|
||||
@ -706,255 +709,164 @@ class UnifiedHedger:
|
||||
for coin in coins_to_process:
|
||||
data = aggregates.get(coin, {'target_short': Decimal("0"), 'contributors': 0, 'is_at_edge': False, 'adj_pct': Decimal("0"), 'is_closing': False})
|
||||
|
||||
price = self.last_prices.get(coin, Decimal("0")) # FIX: Explicitly get price for this coin
|
||||
price = self.last_prices.get(coin, Decimal("0"))
|
||||
if price == 0: continue
|
||||
|
||||
target_short_abs = data['target_short'] # Always positive (it's a magnitude of short)
|
||||
target_position = -target_short_abs # We want to be Short, so negative size
|
||||
|
||||
target_short_abs = data['target_short']
|
||||
target_position = -target_short_abs
|
||||
current_pos = current_positions.get(coin, Decimal("0"))
|
||||
|
||||
diff = target_position - current_pos # e.g. -1.0 - (-0.8) = -0.2 (Sell 0.2)
|
||||
diff = target_position - current_pos
|
||||
diff_abs = abs(diff)
|
||||
|
||||
# Thresholds
|
||||
config = self.coin_configs.get(coin, {})
|
||||
min_thresh = config.get("min_threshold", Decimal("0.008"))
|
||||
|
||||
# Volatility Multiplier
|
||||
min_thresh = config.get("MIN_HEDGE_THRESHOLD", Decimal("0.008"))
|
||||
vol_pct = self.calculate_volatility(coin)
|
||||
base_vol = Decimal("0.0005")
|
||||
vol_mult = max(Decimal("1.0"), min(Decimal("3.0"), vol_pct / base_vol)) if vol_pct > 0 else Decimal("1.0")
|
||||
|
||||
base_rebalance_pct = config.get("BASE_REBALANCE_THRESHOLD_PCT", Decimal("0.20"))
|
||||
thresh_pct = min(Decimal("0.15"), base_rebalance_pct * vol_mult)
|
||||
dynamic_thresh = max(min_thresh, abs(target_position) * thresh_pct)
|
||||
|
||||
# FORCE EDGE CLEANUP
|
||||
enable_edge_cleanup = config.get("ENABLE_EDGE_CLEANUP", True)
|
||||
if data['is_at_edge'] and enable_edge_cleanup:
|
||||
if dynamic_thresh > min_thresh:
|
||||
# logger.info(f"[EDGE] {coin} forced to min threshold.")
|
||||
dynamic_thresh = min_thresh
|
||||
if data['is_at_edge'] and config.get("ENABLE_EDGE_CLEANUP", True):
|
||||
if dynamic_thresh > min_thresh: dynamic_thresh = min_thresh
|
||||
|
||||
# Check Trigger
|
||||
action_needed = diff_abs > dynamic_thresh
|
||||
|
||||
# Determine Intent (Moved UP for Order Logic)
|
||||
is_buy_bool = diff > 0
|
||||
side_str = "BUY" if is_buy_bool else "SELL"
|
||||
|
||||
# Manage Existing Orders
|
||||
existing_orders = orders_map.get(coin, [])
|
||||
force_taker_retry = False
|
||||
|
||||
# Fishing Config
|
||||
enable_fishing = config.get("ENABLE_FISHING", False)
|
||||
fishing_timeout = config.get("FISHING_TIMEOUT_FALLBACK", 30)
|
||||
|
||||
# Check Existing Orders for compatibility
|
||||
order_matched = False
|
||||
price_buffer_pct = config.get("PRICE_BUFFER_PCT", Decimal("0.0015"))
|
||||
|
||||
for o in existing_orders:
|
||||
o_oid = o['oid']
|
||||
o_price = to_decimal(o['limitPx'])
|
||||
o_side = o['side'] # 'B' or 'A'
|
||||
o_side = o['side']
|
||||
o_timestamp = o.get('timestamp', int(time.time()*1000))
|
||||
|
||||
is_same_side = (o_side == 'B' and is_buy_bool) or (o_side == 'A' and not is_buy_bool)
|
||||
|
||||
# Price Check (within buffer)
|
||||
dist_pct = abs(price - o_price) / price
|
||||
|
||||
# Maker Timeout Check (General)
|
||||
maker_timeout = config.get("MAKER_ORDER_TIMEOUT", 300)
|
||||
order_age_sec = (int(time.time()*1000) - o_timestamp) / 1000.0
|
||||
|
||||
if is_same_side and order_age_sec > maker_timeout:
|
||||
logger.info(f"[TIMEOUT] {coin} Order {o_oid} expired ({order_age_sec:.1f}s > {maker_timeout}s). Cancelling to refresh.")
|
||||
if is_same_side and order_age_sec > config.get("MAKER_ORDER_TIMEOUT", 300):
|
||||
logger.info(f"[TIMEOUT] {coin} Order {o_oid} expired. Cancelling.")
|
||||
self.cancel_order(coin, o_oid)
|
||||
continue
|
||||
|
||||
# Fishing Timeout Check
|
||||
if enable_fishing and is_same_side and order_age_sec > fishing_timeout:
|
||||
logger.info(f"[FISHING] {coin} Order {o_oid} timed out ({order_age_sec:.1f}s > {fishing_timeout}s). Cancelling for Taker retry.")
|
||||
if config.get("ENABLE_FISHING", False) and is_same_side and order_age_sec > config.get("FISHING_TIMEOUT_FALLBACK", 30):
|
||||
logger.info(f"[FISHING] {coin} Order {o_oid} timed out. Retrying as Taker.")
|
||||
self.cancel_order(coin, o_oid)
|
||||
force_taker_retry = True
|
||||
continue # Do not mark matched, let it flow to execution
|
||||
continue
|
||||
|
||||
if is_same_side and dist_pct < price_buffer_pct:
|
||||
if is_same_side and (abs(price - o_price) / price) < price_buffer_pct:
|
||||
order_matched = True
|
||||
if int(time.time()) % 10 == 0:
|
||||
logger.info(f"[WAIT] {coin} Pending {side_str} Order {o_oid} @ {o_price} (Dist: {dist_pct*100:.3f}%) | Age: {order_age_sec:.1f}s")
|
||||
if int(time.time()) % 15 == 0:
|
||||
logger.info(f"[WAIT] {coin} Pending {side_str} @ {o_price} | Age: {order_age_sec:.1f}s")
|
||||
break
|
||||
else:
|
||||
logger.info(f"Cancelling stale order {o_oid} ({o_side} @ {o_price})")
|
||||
self.cancel_order(coin, o_oid)
|
||||
|
||||
# --- EXECUTION LOGIC ---
|
||||
if not order_matched:
|
||||
if action_needed or force_taker_retry:
|
||||
bypass_cooldown = False
|
||||
force_maker = False
|
||||
|
||||
# 0. Forced Taker Retry (Fishing Timeout)
|
||||
if force_taker_retry:
|
||||
bypass_cooldown = True
|
||||
logger.info(f"[RETRY] {coin} Fishing Failed -> Force Taker")
|
||||
|
||||
# 1. Urgent Closing -> Taker
|
||||
elif data.get('is_closing', False):
|
||||
bypass_cooldown = True
|
||||
logger.info(f"[URGENT] {coin} Closing Strategy -> Force Taker Exit")
|
||||
|
||||
# 2. Ghost/Cleanup -> Maker
|
||||
elif data.get('contributors', 0) == 0:
|
||||
if time.time() - self.startup_time > 5:
|
||||
force_maker = True
|
||||
logger.info(f"[CLEANUP] {coin} Ghost Position -> Force Maker Reduce")
|
||||
else:
|
||||
logger.info(f"[STARTUP] Skipping Ghost Cleanup for {coin} (Grace Period)")
|
||||
continue # Skip execution for this coin
|
||||
|
||||
# Large Hedge Check (Only Force Taker if AT EDGE)
|
||||
large_hedge_mult = config.get("LARGE_HEDGE_MULTIPLIER", Decimal("5.0"))
|
||||
if diff_abs > (dynamic_thresh * large_hedge_mult) and not force_maker and data.get('is_at_edge', False):
|
||||
bypass_cooldown = True
|
||||
logger.info(f"[WARN] LARGE HEDGE (Edge Protection): {diff_abs:.4f} > {dynamic_thresh:.4f} (x{large_hedge_mult})")
|
||||
elif diff_abs > (dynamic_thresh * large_hedge_mult) and not force_maker:
|
||||
# Large hedge but safe zone -> Maker is fine, but maybe log it
|
||||
logger.info(f"[INFO] Large Hedge (Safe Zone): {diff_abs:.4f}. Using Standard Execution.")
|
||||
|
||||
last_trade = self.last_trade_times.get(coin, 0)
|
||||
|
||||
min_time_trade = config.get("MIN_TIME_BETWEEN_TRADES", 60)
|
||||
can_trade = False
|
||||
if bypass_cooldown:
|
||||
can_trade = True
|
||||
elif time.time() - last_trade > min_time_trade:
|
||||
can_trade = True
|
||||
|
||||
if can_trade:
|
||||
# Get Orderbook for Price
|
||||
if coin not in l2_snapshots:
|
||||
l2_snapshots[coin] = self.info.l2_snapshot(coin)
|
||||
|
||||
levels = l2_snapshots[coin]['levels']
|
||||
if not levels[0] or not levels[1]: continue
|
||||
|
||||
bid = to_decimal(levels[0][0]['px'])
|
||||
ask = to_decimal(levels[1][0]['px'])
|
||||
|
||||
# Price logic
|
||||
create_shadow = False
|
||||
|
||||
# Decide Order Type: Taker (Ioc) or Maker (Alo)
|
||||
# Taker if: Urgent (bypass_cooldown) OR Fishing Disabled OR Force Maker is False (wait, Force Maker means Alo)
|
||||
|
||||
# Logic:
|
||||
# If Force Maker -> Alo
|
||||
# Else if Urgent -> Ioc
|
||||
# Else if Enable Fishing -> Alo
|
||||
# Else -> Alo (Default non-urgent behavior was Alo anyway?)
|
||||
|
||||
# Let's clarify:
|
||||
# Previous logic: if bypass_cooldown -> Ioc. Else -> Alo.
|
||||
# New logic:
|
||||
# If bypass_cooldown -> Ioc
|
||||
# Else -> Alo (Fishing)
|
||||
|
||||
if bypass_cooldown and not force_maker:
|
||||
exec_price = ask * Decimal("1.001") if is_buy_bool else bid * Decimal("0.999")
|
||||
order_type = "Ioc"
|
||||
create_shadow = True
|
||||
else:
|
||||
# Fishing / Standard Maker
|
||||
exec_price = bid if is_buy_bool else ask
|
||||
order_type = "Alo"
|
||||
|
||||
logger.info(f"[TRIG] Net {coin}: {side_str} {diff_abs:.4f} | Tgt: {target_position:.4f} / Cur: {current_pos:.4f} | Thresh: {dynamic_thresh:.4f} | Type: {order_type}")
|
||||
|
||||
oid = self.place_limit_order(coin, is_buy_bool, diff_abs, exec_price, order_type)
|
||||
if oid:
|
||||
self.last_trade_times[coin] = time.time()
|
||||
|
||||
# Shadow Order
|
||||
if create_shadow:
|
||||
shadow_price = bid if is_buy_bool else ask
|
||||
shadow_timeout = config.get("SHADOW_ORDER_TIMEOUT", 600)
|
||||
self.shadow_orders.append({
|
||||
'coin': coin,
|
||||
'side': side_str,
|
||||
'price': shadow_price,
|
||||
'expires_at': time.time() + shadow_timeout
|
||||
})
|
||||
logger.info(f"[SHADOW] Created Maker {side_str} @ {shadow_price:.2f}")
|
||||
|
||||
# UPDATED: Sleep for API Lag (Phase 5.1)
|
||||
logger.info("Sleeping 10s to allow position update...")
|
||||
time.sleep(10)
|
||||
|
||||
# --- UPDATE CLOSED PnL FROM API ---
|
||||
self._update_closed_pnl(coin)
|
||||
else:
|
||||
# Cooldown log
|
||||
pass
|
||||
# Determine Urgency / Bypass Cooldown
|
||||
bypass_cooldown = False
|
||||
force_maker = False
|
||||
if not order_matched and (action_needed or force_taker_retry):
|
||||
if force_taker_retry: bypass_cooldown = True
|
||||
elif data.get('is_closing', False): bypass_cooldown = True
|
||||
elif data.get('contributors', 0) == 0:
|
||||
if time.time() - self.startup_time > 5: force_maker = True
|
||||
else: continue # Skip startup ghost positions
|
||||
|
||||
else:
|
||||
# Action NOT needed
|
||||
# Cleanup any dangling orders
|
||||
if existing_orders:
|
||||
for o in existing_orders:
|
||||
logger.info(f"Cancelling idle order {o['oid']} ({o['side']} @ {o['limitPx']})")
|
||||
self.cancel_order(coin, o['oid'])
|
||||
large_hedge_mult = config.get("LARGE_HEDGE_MULTIPLIER", Decimal("5.0"))
|
||||
if diff_abs > (dynamic_thresh * large_hedge_mult) and not force_maker and data.get('is_at_edge', False):
|
||||
# Prevent IOC for BUYs at bottom edge
|
||||
if not (is_buy_bool and data.get('is_at_bottom_edge', False)):
|
||||
bypass_cooldown = True
|
||||
|
||||
# --- IDLE LOGGING (Restored Format) ---
|
||||
# Calculate aggregate Gamma to estimate triggers
|
||||
# Gamma = 0.5 * Sum(L) * P^-1.5
|
||||
# We need Sum(L) for this coin.
|
||||
total_L = Decimal("0")
|
||||
# We need to re-iterate or cache L.
|
||||
# Simpler: Just re-sum L from active strats for this coin.
|
||||
for key, strat in self.strategies.items():
|
||||
if self.strategy_states[key]['coin'] == coin:
|
||||
total_L += strat.L
|
||||
|
||||
if total_L > 0 and price > 0:
|
||||
gamma = (Decimal("0.5") * total_L * (price ** Decimal("-1.5")))
|
||||
if gamma > 0:
|
||||
# Equilibrium Price (Diff = 0)
|
||||
p_mid = price + (diff / gamma)
|
||||
# --- ASYMMETRIC HEDGE CHECK ---
|
||||
is_asymmetric_blocked = False
|
||||
p_mid_asym = Decimal("0")
|
||||
if is_buy_bool and not bypass_cooldown:
|
||||
total_L_asym = Decimal("0")
|
||||
for k_strat, strat_inst in self.strategies.items():
|
||||
if self.strategy_states[k_strat]['coin'] == coin:
|
||||
total_L_asym += strat_inst.L
|
||||
|
||||
gamma_asym = (Decimal("0.5") * total_L_asym * (price ** Decimal("-1.5")))
|
||||
if gamma_asym > 0:
|
||||
p_mid_asym = price - (diff_abs / gamma_asym)
|
||||
if not data.get('is_at_edge', False) and price >= p_mid_asym:
|
||||
is_asymmetric_blocked = True
|
||||
|
||||
# --- EXECUTION ---
|
||||
if not order_matched and not is_asymmetric_blocked:
|
||||
if action_needed or force_taker_retry:
|
||||
last_trade = self.last_trade_times.get(coin, 0)
|
||||
min_time = config.get("MIN_TIME_BETWEEN_TRADES", 60)
|
||||
|
||||
# Triggers
|
||||
p_buy = price + (dynamic_thresh + diff) / gamma
|
||||
p_sell = price - (dynamic_thresh - diff) / gamma
|
||||
|
||||
if int(time.time()) % 30 == 0:
|
||||
pad = " " if coin == "BNB" else ""
|
||||
adj_val = data.get('adj_pct', Decimal("0")) * 100
|
||||
|
||||
# PnL Calc
|
||||
unrealized = current_pnls.get(coin, Decimal("0"))
|
||||
closed_pnl_total = Decimal("0")
|
||||
fees_total = Decimal("0")
|
||||
for k, s_state in self.strategy_states.items():
|
||||
if s_state['coin'] == coin:
|
||||
closed_pnl_total += s_state.get('hedge_TotPnL', Decimal("0"))
|
||||
fees_total += s_state.get('fees', Decimal("0"))
|
||||
if bypass_cooldown or (time.time() - last_trade > min_time):
|
||||
if coin not in l2_snapshots: l2_snapshots[coin] = self.info.l2_snapshot(coin)
|
||||
levels = l2_snapshots[coin]['levels']
|
||||
if levels[0] and levels[1]:
|
||||
bid, ask = to_decimal(levels[0][0]['px']), to_decimal(levels[1][0]['px'])
|
||||
if bypass_cooldown and not force_maker:
|
||||
exec_price = ask * Decimal("1.001") if is_buy_bool else bid * Decimal("0.999")
|
||||
order_type = "Ioc"
|
||||
else:
|
||||
exec_price = bid if is_buy_bool else ask
|
||||
order_type = "Alo"
|
||||
|
||||
logger.info(f"[TRIG] {coin} {side_str} {diff_abs:.4f} | Cur: {current_pos:.4f} | Type: {order_type}")
|
||||
oid = self.place_limit_order(coin, is_buy_bool, diff_abs, exec_price, order_type)
|
||||
if oid:
|
||||
self.last_trade_times[coin] = time.time()
|
||||
if order_type == "Ioc":
|
||||
shadow_price = bid if is_buy_bool else ask
|
||||
self.shadow_orders.append({'coin': coin, 'side': side_str, 'price': shadow_price, 'expires_at': time.time() + config.get("SHADOW_ORDER_TIMEOUT", 600)})
|
||||
|
||||
total_pnl = (closed_pnl_total - fees_total) + unrealized
|
||||
|
||||
pnl_pad = " " if unrealized >= 0 else ""
|
||||
tot_pnl_pad = " " if total_pnl >= 0 else ""
|
||||
|
||||
logger.info(f"[IDLE] {coin} | Px: {price:.2f}{pad} | M: {p_mid:.1f}{pad} | B: {p_buy:.1f}{pad} / S: {p_sell:.1f}{pad} | delta: {target_position:.4f}({diff:+.4f}) | Adj: {adj_val:+.2f}%, Vol: {vol_mult:.2f}, Thr: {dynamic_thresh:.4f} | PnL: {unrealized:.2f}{pnl_pad} | TotPnL: {total_pnl:.2f}{tot_pnl_pad}")
|
||||
logger.info("Sleeping 10s for position update...")
|
||||
time.sleep(10)
|
||||
self._update_closed_pnl(coin)
|
||||
else:
|
||||
if int(time.time()) % 30 == 0:
|
||||
# Idle Cleanup
|
||||
if existing_orders and not order_matched:
|
||||
for o in existing_orders: self.cancel_order(coin, o['oid'])
|
||||
|
||||
# --- THROTTLED STATUS LOGGING ---
|
||||
now = time.time()
|
||||
last_log = self.last_idle_log_times.get(coin, 0)
|
||||
monitor_interval = config.get("MONITOR_INTERVAL_SECONDS", 60)
|
||||
|
||||
if now - last_log >= monitor_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_buy = price + (dynamic_thresh + diff) / gamma_log
|
||||
p_sell = price - (dynamic_thresh - diff) / gamma_log
|
||||
pad = " " if coin == "BNB" else ""
|
||||
|
||||
unrealized = current_pnls.get(coin, Decimal("0"))
|
||||
closed_pnl = sum(s['hedge_TotPnL'] for s in self.strategy_states.values() if s['coin'] == coin)
|
||||
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}")
|
||||
else:
|
||||
logger.info(f"[IDLE] {coin} | Px: {price:.2f} | delta: {target_position:.4f} | Diff: {diff:.4f} (Thresh: {dynamic_thresh:.4f})")
|
||||
else:
|
||||
if int(time.time()) % 30 == 0:
|
||||
logger.info(f"[IDLE] {coin} | Px: {price:.2f} | delta: {target_position:.4f} | Diff: {diff:.4f} (Thresh: {dynamic_thresh:.4f})")
|
||||
else:
|
||||
logger.info(f"[IDLE] {coin} | Px: {price:.2f} | delta: {target_position:.4f} | Diff: {diff:.4f}")
|
||||
|
||||
time.sleep(DEFAULT_STRATEGY.get("CHECK_INTERVAL", 1))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user