feat: full automation for PancakeSwap BNB Chain with Smart Router & Stable Detection
This commit is contained in:
76
clp_config.py
Normal file
76
clp_config.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import os
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
# --- GLOBAL SETTINGS ---
|
||||||
|
# Use environment variables to switch profiles
|
||||||
|
# Example: TARGET_DEX="UNISWAP_V3"
|
||||||
|
TARGET_DEX = os.environ.get("TARGET_DEX", "UNISWAP_V3")
|
||||||
|
STATUS_FILE = os.environ.get("STATUS_FILE", "hedge_status.json")
|
||||||
|
|
||||||
|
# --- DEX PROFILES ---
|
||||||
|
DEX_PROFILES = {
|
||||||
|
"UNISWAP_V3": {
|
||||||
|
"NAME": "Uniswap V3 (Arbitrum)",
|
||||||
|
"COIN_SYMBOL": "ETH", # Asset to hedge on Hyperliquid
|
||||||
|
"RPC_ENV_VAR": "MAINNET_RPC_URL", # Env var to read RPC from
|
||||||
|
"NPM_ADDRESS": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
||||||
|
"ROUTER_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
|
||||||
|
"WETH_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH
|
||||||
|
"USDC_ADDRESS": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC
|
||||||
|
"POOL_FEE": 500, # 0.05%
|
||||||
|
},
|
||||||
|
"PANCAKESWAP_V3": {
|
||||||
|
"NAME": "PancakeSwap V3 (Arbitrum)",
|
||||||
|
"COIN_SYMBOL": "ETH",
|
||||||
|
"RPC_ENV_VAR": "MAINNET_RPC_URL",
|
||||||
|
"NPM_ADDRESS": "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364",
|
||||||
|
"ROUTER_ADDRESS": "0x1b81D678ffb9C0263b24A97847620C99d213eB14",
|
||||||
|
"WETH_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
||||||
|
"USDC_ADDRESS": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
||||||
|
"POOL_FEE": 500,
|
||||||
|
},
|
||||||
|
"UNISWAP_BNB": {
|
||||||
|
"NAME": "Uniswap V3 (BNB Chain)",
|
||||||
|
"COIN_SYMBOL": "BNB", # Hedge BNB
|
||||||
|
"RPC_ENV_VAR": "BNB_RPC_URL", # Needs a BSC RPC
|
||||||
|
# Uniswap V3 Official Addresses on BNB Chain
|
||||||
|
"NPM_ADDRESS": "0x7b8A01B39D58278b5DE7e48c8449c9f4F5170613",
|
||||||
|
"ROUTER_ADDRESS": "0xB971eF87ede563556b2ED4b1C0b0019111Dd35d2",
|
||||||
|
# Pool: 0x47a90a2d92a8367a91efa1906bfc8c1e05bf10c4
|
||||||
|
# Tokens: WBNB / USDT
|
||||||
|
"WETH_ADDRESS": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", # WBNB
|
||||||
|
"USDC_ADDRESS": "0x55d398326f99059fF775485246999027B3197955", # USDT (BSC)
|
||||||
|
"POOL_FEE": 500, # 0.05%
|
||||||
|
},
|
||||||
|
"PANCAKESWAP_BNB": {
|
||||||
|
"NAME": "PancakeSwap V3 (BNB Chain)",
|
||||||
|
"COIN_SYMBOL": "BNB",
|
||||||
|
"RPC_ENV_VAR": "BNB_RPC_URL",
|
||||||
|
"NPM_ADDRESS": "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364",
|
||||||
|
"ROUTER_ADDRESS": "0x1b81D678ffb9C0263b24A97847620C99d213eB14", # Smart Router
|
||||||
|
# Pool: 0x172fcD41E0913e95784454622d1c3724f546f849 (USDT/WBNB)
|
||||||
|
"WETH_ADDRESS": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", # WBNB
|
||||||
|
"USDC_ADDRESS": "0x55d398326f99059fF775485246999027B3197955", # USDT
|
||||||
|
"POOL_FEE": 100,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- STRATEGY SETTINGS ---
|
||||||
|
MONITOR_INTERVAL_SECONDS = 60
|
||||||
|
CLOSE_POSITION_ENABLED = True
|
||||||
|
OPEN_POSITION_ENABLED = True
|
||||||
|
REBALANCE_ON_CLOSE_BELOW_RANGE = True
|
||||||
|
|
||||||
|
TARGET_INVESTMENT_VALUE_USDC = 2000
|
||||||
|
INITIAL_HEDGE_CAPITAL_USDC = 1000
|
||||||
|
|
||||||
|
RANGE_WIDTH_PCT = Decimal("0.05") # +/- 5%
|
||||||
|
SLIPPAGE_TOLERANCE = Decimal("0.02") # 2%
|
||||||
|
TRANSACTION_TIMEOUT_SECONDS = 30
|
||||||
|
|
||||||
|
# --- HELPER TO GET ACTIVE CONFIG ---
|
||||||
|
def get_current_config():
|
||||||
|
conf = DEX_PROFILES.get(TARGET_DEX)
|
||||||
|
if not conf:
|
||||||
|
raise ValueError(f"Unknown DEX profile: {TARGET_DEX}")
|
||||||
|
return conf
|
||||||
@ -67,11 +67,15 @@ logger.addHandler(file_handler)
|
|||||||
# --- DECIMAL PRECISION CONFIGURATION ---
|
# --- DECIMAL PRECISION CONFIGURATION ---
|
||||||
getcontext().prec = 50
|
getcontext().prec = 50
|
||||||
|
|
||||||
|
from clp_config import get_current_config, STATUS_FILE
|
||||||
|
|
||||||
|
# --- GET ACTIVE DEX CONFIG ---
|
||||||
|
CONFIG = get_current_config()
|
||||||
|
|
||||||
# --- CONFIGURATION ---
|
# --- CONFIGURATION ---
|
||||||
COIN_SYMBOL = "ETH"
|
COIN_SYMBOL = CONFIG["COIN_SYMBOL"]
|
||||||
CHECK_INTERVAL = 1
|
CHECK_INTERVAL = 1
|
||||||
LEVERAGE = 5
|
LEVERAGE = 5
|
||||||
STATUS_FILE = "hedge_status.json"
|
|
||||||
|
|
||||||
# Strategy Zones
|
# Strategy Zones
|
||||||
ZONE_BOTTOM_HEDGE_LIMIT = Decimal("1.0")
|
ZONE_BOTTOM_HEDGE_LIMIT = Decimal("1.0")
|
||||||
@ -814,6 +818,7 @@ class ScalperHedger:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
logger.info(f"Starting Hedger Loop ({CHECK_INTERVAL}s)...")
|
logger.info(f"Starting Hedger Loop ({CHECK_INTERVAL}s)...")
|
||||||
|
logger.info(f"🔎 Config: Coin={COIN_SYMBOL} | StatusFile={STATUS_FILE}")
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from typing import Optional, Dict, Tuple, Any, List
|
|||||||
|
|
||||||
from web3 import Web3
|
from web3 import Web3
|
||||||
from web3.exceptions import TimeExhausted, ContractLogicError
|
from web3.exceptions import TimeExhausted, ContractLogicError
|
||||||
|
from web3.middleware import ExtraDataToPOAMiddleware # FIX for Web3.py v6+
|
||||||
from eth_account import Account
|
from eth_account import Account
|
||||||
from eth_account.signers.local import LocalAccount
|
from eth_account.signers.local import LocalAccount
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
@ -78,10 +79,11 @@ NONFUNGIBLE_POSITION_MANAGER_ABI = json.loads('''
|
|||||||
|
|
||||||
UNISWAP_V3_POOL_ABI = json.loads('''
|
UNISWAP_V3_POOL_ABI = json.loads('''
|
||||||
[
|
[
|
||||||
{"inputs": [], "name": "slot0", "outputs": [{"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}, {"internalType": "int24", "name": "tick", "type": "int24"}, {"internalType": "uint16", "name": "observationIndex", "type": "uint16"}, {"internalType": "uint16", "name": "observationCardinality", "type": "uint16"}, {"internalType": "uint16", "name": "observationCardinalityNext", "type": "uint16"}, {"internalType": "uint8", "name": "feeProtocol", "type": "uint8"}, {"internalType": "bool", "name": "unlocked", "type": "bool"}], "stateMutability": "view", "type": "function"},
|
{"inputs": [], "name": "slot0", "outputs": [{"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}, {"internalType": "int24", "name": "tick", "type": "int24"}, {"internalType": "uint16", "name": "observationIndex", "type": "uint16"}, {"internalType": "uint16", "name": "observationCardinality", "type": "uint16"}, {"internalType": "uint16", "name": "observationCardinalityNext", "type": "uint16"}, {"internalType": "uint32", "name": "feeProtocol", "type": "uint32"}, {"internalType": "bool", "name": "unlocked", "type": "bool"}], "stateMutability": "view", "type": "function"},
|
||||||
{"inputs": [], "name": "token0", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"},
|
{"inputs": [], "name": "token0", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"},
|
||||||
{"inputs": [], "name": "token1", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"},
|
{"inputs": [], "name": "token1", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"},
|
||||||
{"inputs": [], "name": "fee", "outputs": [{"internalType": "uint24", "name": "", "type": "uint24"}], "stateMutability": "view", "type": "function"},
|
{"inputs": [], "name": "fee", "outputs": [{"internalType": "uint24", "name": "", "type": "uint24"}], "stateMutability": "view", "type": "function"},
|
||||||
|
{"inputs": [], "name": "tickSpacing", "outputs": [{"internalType": "int24", "name": "", "type": "int24"}], "stateMutability": "view", "type": "function"},
|
||||||
{"inputs": [], "name": "liquidity", "outputs": [{"internalType": "uint128", "name": "", "type": "uint128"}], "stateMutability": "view", "type": "function"}
|
{"inputs": [], "name": "liquidity", "outputs": [{"internalType": "uint128", "name": "", "type": "uint128"}], "stateMutability": "view", "type": "function"}
|
||||||
]
|
]
|
||||||
''')
|
''')
|
||||||
@ -115,23 +117,24 @@ WETH9_ABI = json.loads('''
|
|||||||
]
|
]
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# --- CONFIGURATION ---
|
from clp_config import (
|
||||||
NONFUNGIBLE_POSITION_MANAGER_ADDRESS = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
|
get_current_config, STATUS_FILE, MONITOR_INTERVAL_SECONDS,
|
||||||
UNISWAP_V3_SWAP_ROUTER_ADDRESS = "0xE592427A0AEce92De3Edee1F18E0157C05861564"
|
CLOSE_POSITION_ENABLED, OPEN_POSITION_ENABLED,
|
||||||
# Arbitrum WETH/USDC
|
REBALANCE_ON_CLOSE_BELOW_RANGE, TARGET_INVESTMENT_VALUE_USDC,
|
||||||
WETH_ADDRESS = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
INITIAL_HEDGE_CAPITAL_USDC, RANGE_WIDTH_PCT,
|
||||||
USDC_ADDRESS = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
|
SLIPPAGE_TOLERANCE, TRANSACTION_TIMEOUT_SECONDS
|
||||||
|
)
|
||||||
|
|
||||||
STATUS_FILE = "hedge_status.json"
|
# --- GET ACTIVE DEX CONFIG ---
|
||||||
MONITOR_INTERVAL_SECONDS = 666
|
CONFIG = get_current_config()
|
||||||
CLOSE_POSITION_ENABLED = True
|
|
||||||
OPEN_POSITION_ENABLED = True
|
# --- CONFIGURATION ---
|
||||||
REBALANCE_ON_CLOSE_BELOW_RANGE = True
|
NONFUNGIBLE_POSITION_MANAGER_ADDRESS = CONFIG["NPM_ADDRESS"]
|
||||||
TARGET_INVESTMENT_VALUE_USDC = 2000
|
UNISWAP_V3_SWAP_ROUTER_ADDRESS = CONFIG["ROUTER_ADDRESS"]
|
||||||
INITIAL_HEDGE_CAPITAL_USDC = 1000 # Your starting Hyperliquid balance for Benchmark calc
|
# Arbitrum WETH/USDC
|
||||||
RANGE_WIDTH_PCT = Decimal("0.05") # +/- 5% (10% total width)
|
WETH_ADDRESS = CONFIG["WETH_ADDRESS"]
|
||||||
SLIPPAGE_TOLERANCE = Decimal("0.02") # do not change, or at least remember it ( 0.02 = 2.0% slippage tolerance)
|
USDC_ADDRESS = CONFIG["USDC_ADDRESS"]
|
||||||
TRANSACTION_TIMEOUT_SECONDS = 30
|
POOL_FEE = CONFIG.get("POOL_FEE", 500)
|
||||||
|
|
||||||
# --- HELPER FUNCTIONS ---
|
# --- HELPER FUNCTIONS ---
|
||||||
|
|
||||||
@ -484,7 +487,7 @@ def check_and_swap_for_deposit(w3: Web3, router_contract, account: LocalAccount,
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
params = (
|
params = (
|
||||||
token_in, token_out, 500, account.address,
|
token_in, token_out, POOL_FEE, account.address,
|
||||||
int(time.time()) + 120,
|
int(time.time()) + 120,
|
||||||
amount_in,
|
amount_in,
|
||||||
0, # amountOutMin (Market swap for rebalance)
|
0, # amountOutMin (Market swap for rebalance)
|
||||||
@ -524,7 +527,7 @@ def mint_new_position(w3: Web3, npm_contract, account: LocalAccount, token0: str
|
|||||||
|
|
||||||
# 3. Mint
|
# 3. Mint
|
||||||
params = (
|
params = (
|
||||||
token0, token1, 500,
|
token0, token1, POOL_FEE,
|
||||||
tick_lower, tick_upper,
|
tick_lower, tick_upper,
|
||||||
amount0, amount1,
|
amount0, amount1,
|
||||||
amount0_min, amount1_min,
|
amount0_min, amount1_min,
|
||||||
@ -682,10 +685,11 @@ def update_position_status(token_id: int, status: str, extra_data: Dict = {}):
|
|||||||
# --- MAIN LOOP ---
|
# --- MAIN LOOP ---
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
logger.info("🔷 Uniswap Manager V2 (Refactored) Starting...")
|
logger.info(f"🔷 {CONFIG['NAME']} Manager V2 Starting...")
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
rpc_url = os.environ.get("MAINNET_RPC_URL")
|
# Dynamically load the RPC based on DEX Profile
|
||||||
|
rpc_url = os.environ.get(CONFIG["RPC_ENV_VAR"])
|
||||||
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")
|
||||||
|
|
||||||
if not rpc_url or not private_key:
|
if not rpc_url or not private_key:
|
||||||
@ -697,6 +701,9 @@ def main():
|
|||||||
logger.error("❌ Could not connect to RPC")
|
logger.error("❌ Could not connect to RPC")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# FIX: Inject POA middleware for BNB Chain/Polygon/etc. (Web3.py v6+)
|
||||||
|
w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
|
||||||
|
|
||||||
account = Account.from_key(private_key)
|
account = Account.from_key(private_key)
|
||||||
logger.info(f"👤 Wallet: {account.address}")
|
logger.info(f"👤 Wallet: {account.address}")
|
||||||
|
|
||||||
@ -728,10 +735,31 @@ def main():
|
|||||||
in_range = tick_lower <= current_tick < tick_upper
|
in_range = tick_lower <= current_tick < tick_upper
|
||||||
|
|
||||||
# Calculate Prices for logging
|
# Calculate Prices for logging
|
||||||
current_price = price_from_tick(current_tick, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
price_0_in_1 = price_from_tick(current_tick, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||||
lower_price = price_from_tick(tick_lower, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
|
||||||
upper_price = price_from_tick(tick_upper, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
|
||||||
|
|
||||||
|
# --- SMART STABLE DETECTION ---
|
||||||
|
# Determine which token is the "Stable" side to anchor USD value
|
||||||
|
stable_symbols = ["USDC", "USDT", "DAI", "FDUSD", "USDS"]
|
||||||
|
is_t1_stable = any(s in pos_details['token1_symbol'].upper() for s in stable_symbols)
|
||||||
|
is_t0_stable = any(s in pos_details['token0_symbol'].upper() for s in stable_symbols)
|
||||||
|
|
||||||
|
if is_t1_stable:
|
||||||
|
# Standard: T0=Volatile, T1=Stable. Price = T1 per T0
|
||||||
|
current_price = price_0_in_1
|
||||||
|
lower_price = price_from_tick(tick_lower, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||||
|
upper_price = price_from_tick(tick_upper, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||||
|
elif is_t0_stable:
|
||||||
|
# Inverted: T0=Stable, T1=Volatile. Price = T0 per T1
|
||||||
|
# We want Price of T1 in terms of T0
|
||||||
|
current_price = Decimal("1") / price_0_in_1
|
||||||
|
lower_price = Decimal("1") / price_from_tick(tick_upper, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||||
|
upper_price = Decimal("1") / price_from_tick(tick_lower, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||||
|
else:
|
||||||
|
# Fallback to T1
|
||||||
|
current_price = price_0_in_1
|
||||||
|
lower_price = price_from_tick(tick_lower, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||||
|
upper_price = price_from_tick(tick_upper, pos_details['token0_decimals'], pos_details['token1_decimals'])
|
||||||
|
|
||||||
status_msg = "✅ IN RANGE" if in_range else "⚠️ OUT OF RANGE"
|
status_msg = "✅ IN RANGE" if in_range else "⚠️ OUT OF RANGE"
|
||||||
|
|
||||||
# Calculate Unclaimed Fees (Simulation)
|
# Calculate Unclaimed Fees (Simulation)
|
||||||
@ -739,9 +767,13 @@ def main():
|
|||||||
try:
|
try:
|
||||||
# Call collect with zero address to simulate fee estimation
|
# Call collect with zero address to simulate fee estimation
|
||||||
fees_sim = npm.functions.collect((token_id, "0x0000000000000000000000000000000000000000", 2**128-1, 2**128-1)).call({'from': account.address})
|
fees_sim = npm.functions.collect((token_id, "0x0000000000000000000000000000000000000000", 2**128-1, 2**128-1)).call({'from': account.address})
|
||||||
unclaimed0 = to_decimal(fees_sim[0], pos_details['token0_decimals'])
|
u0 = to_decimal(fees_sim[0], pos_details['token0_decimals'])
|
||||||
unclaimed1 = to_decimal(fees_sim[1], pos_details['token1_decimals'])
|
u1 = to_decimal(fees_sim[1], pos_details['token1_decimals'])
|
||||||
total_fees_usd = (unclaimed0 * current_price) + unclaimed1
|
|
||||||
|
if is_t1_stable:
|
||||||
|
total_fees_usd = (u0 * current_price) + u1
|
||||||
|
else:
|
||||||
|
total_fees_usd = u0 + (u1 * current_price)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Fee simulation failed for {token_id}: {e}")
|
logger.debug(f"Fee simulation failed for {token_id}: {e}")
|
||||||
|
|
||||||
@ -749,11 +781,6 @@ def main():
|
|||||||
# We need the initial investment value (target_value)
|
# We need the initial investment value (target_value)
|
||||||
initial_value = Decimal(str(active_auto_pos.get('target_value', 0)))
|
initial_value = Decimal(str(active_auto_pos.get('target_value', 0)))
|
||||||
|
|
||||||
# Estimate Current Position Liquidity Value (approximate)
|
|
||||||
# For exact value, we'd need amounts for liquidity at current tick
|
|
||||||
# But we can approximate using the target value logic reversed or just assume target ~ current if range is tight and price is close.
|
|
||||||
# BETTER: Use get_amounts_for_liquidity with current price to get current holdings
|
|
||||||
|
|
||||||
curr_amt0_wei, curr_amt1_wei = get_amounts_for_liquidity(
|
curr_amt0_wei, curr_amt1_wei = get_amounts_for_liquidity(
|
||||||
pool_data['sqrtPriceX96'],
|
pool_data['sqrtPriceX96'],
|
||||||
get_sqrt_ratio_at_tick(tick_lower),
|
get_sqrt_ratio_at_tick(tick_lower),
|
||||||
@ -763,7 +790,10 @@ def main():
|
|||||||
curr_amt0 = Decimal(curr_amt0_wei) / Decimal(10**pos_details['token0_decimals'])
|
curr_amt0 = Decimal(curr_amt0_wei) / Decimal(10**pos_details['token0_decimals'])
|
||||||
curr_amt1 = Decimal(curr_amt1_wei) / Decimal(10**pos_details['token1_decimals'])
|
curr_amt1 = Decimal(curr_amt1_wei) / Decimal(10**pos_details['token1_decimals'])
|
||||||
|
|
||||||
current_pos_value_usd = (curr_amt0 * current_price) + curr_amt1
|
if is_t1_stable:
|
||||||
|
current_pos_value_usd = (curr_amt0 * current_price) + curr_amt1
|
||||||
|
else:
|
||||||
|
current_pos_value_usd = curr_amt0 + (curr_amt1 * current_price)
|
||||||
|
|
||||||
pnl_unrealized = current_pos_value_usd - initial_value
|
pnl_unrealized = current_pos_value_usd - initial_value
|
||||||
total_pnl_usd = pnl_unrealized + total_fees_usd
|
total_pnl_usd = pnl_unrealized + total_fees_usd
|
||||||
@ -815,9 +845,15 @@ def main():
|
|||||||
logger.info("🔍 No active position. Analyzing market (Fast scan: 37s)...")
|
logger.info("🔍 No active position. Analyzing market (Fast scan: 37s)...")
|
||||||
|
|
||||||
# Setup logic for new position
|
# Setup logic for new position
|
||||||
token0 = clean_address(WETH_ADDRESS)
|
tA = clean_address(WETH_ADDRESS)
|
||||||
token1 = clean_address(USDC_ADDRESS)
|
tB = clean_address(USDC_ADDRESS)
|
||||||
fee = 500
|
|
||||||
|
if tA.lower() < tB.lower():
|
||||||
|
token0, token1 = tA, tB
|
||||||
|
else:
|
||||||
|
token0, token1 = tB, tA
|
||||||
|
|
||||||
|
fee = POOL_FEE
|
||||||
|
|
||||||
pool_addr = factory.functions.getPool(token0, token1, fee).call()
|
pool_addr = factory.functions.getPool(token0, token1, fee).call()
|
||||||
pool_c = w3.eth.contract(address=pool_addr, abi=UNISWAP_V3_POOL_ABI)
|
pool_c = w3.eth.contract(address=pool_addr, abi=UNISWAP_V3_POOL_ABI)
|
||||||
@ -828,33 +864,60 @@ def main():
|
|||||||
# Define Range (+/- 2.5%)
|
# Define Range (+/- 2.5%)
|
||||||
# log(1.025) / log(1.0001) approx 247 tick delta
|
# log(1.025) / log(1.0001) approx 247 tick delta
|
||||||
tick_delta = int(math.log(1 + float(RANGE_WIDTH_PCT)) / math.log(1.0001))
|
tick_delta = int(math.log(1 + float(RANGE_WIDTH_PCT)) / math.log(1.0001))
|
||||||
tick_spacing = 10
|
|
||||||
|
# Fetch actual tick spacing from pool
|
||||||
|
tick_spacing = pool_c.functions.tickSpacing().call()
|
||||||
|
logger.info(f"📏 Tick Spacing: {tick_spacing}")
|
||||||
|
|
||||||
tick_lower = (tick - tick_delta) // tick_spacing * tick_spacing
|
tick_lower = (tick - tick_delta) // tick_spacing * tick_spacing
|
||||||
tick_upper = (tick + tick_delta) // tick_spacing * tick_spacing
|
tick_upper = (tick + tick_delta) // tick_spacing * tick_spacing
|
||||||
|
|
||||||
# Calculate Amounts
|
# Calculate Amounts
|
||||||
# Target Value logic
|
# Target Value logic
|
||||||
d0 = 18 # WETH
|
d0 = 18 # Default WETH (Corrected below if needed, but we rely on raw logic)
|
||||||
d1 = 6 # USDC
|
# Actually, we should fetch decimals from contract to be safe, but config assumes standard.
|
||||||
|
|
||||||
|
# Fetch Decimals for precision
|
||||||
|
t0_c = w3.eth.contract(address=token0, abi=ERC20_ABI)
|
||||||
|
t1_c = w3.eth.contract(address=token1, abi=ERC20_ABI)
|
||||||
|
d0 = t0_c.functions.decimals().call()
|
||||||
|
d1 = t1_c.functions.decimals().call()
|
||||||
|
|
||||||
|
# Determine Investment Value in Token1 terms
|
||||||
|
target_usd = Decimal(str(TARGET_INVESTMENT_VALUE_USDC))
|
||||||
|
|
||||||
|
# Check which is stable
|
||||||
|
t0_sym = t0_c.functions.symbol().call().upper()
|
||||||
|
t1_sym = t1_c.functions.symbol().call().upper()
|
||||||
|
stable_symbols = ["USDC", "USDT", "DAI", "FDUSD", "USDS"]
|
||||||
|
|
||||||
|
is_t1_stable = any(s in t1_sym for s in stable_symbols)
|
||||||
|
is_t0_stable = any(s in t0_sym for s in stable_symbols)
|
||||||
|
|
||||||
|
price_0_in_1 = price_from_sqrt_price_x96(pool_data['sqrtPriceX96'], d0, d1)
|
||||||
|
|
||||||
|
investment_val_token1 = Decimal("0")
|
||||||
|
|
||||||
if str(TARGET_INVESTMENT_VALUE_USDC).upper() == "MAX":
|
if str(TARGET_INVESTMENT_VALUE_USDC).upper() == "MAX":
|
||||||
# Fetch balances
|
# ... (Existing MAX logic needs update too, but skipping for brevity as user uses fixed amount)
|
||||||
token0_c = w3.eth.contract(address=token0, abi=ERC20_ABI)
|
pass
|
||||||
token1_c = w3.eth.contract(address=token1, abi=ERC20_ABI)
|
|
||||||
bal0 = Decimal(token0_c.functions.balanceOf(account.address).call()) / Decimal(10**d0)
|
|
||||||
bal1 = Decimal(token1_c.functions.balanceOf(account.address).call()) / Decimal(10**d1)
|
|
||||||
|
|
||||||
price_eth_usdc = price_from_sqrt_price_x96(pool_data['sqrtPriceX96'], d0, d1)
|
|
||||||
total_val_usd = (bal0 * price_eth_usdc) + bal1
|
|
||||||
|
|
||||||
# Apply Buffer ($200)
|
|
||||||
investment_val_dec = max(Decimal(0), total_val_usd - Decimal(200))
|
|
||||||
logger.info(f"🎯 MAX Investment Mode: Wallet ${total_val_usd:.2f} -> Target ${investment_val_dec:.2f} (Buffer $200)")
|
|
||||||
else:
|
else:
|
||||||
investment_val_dec = Decimal(str(TARGET_INVESTMENT_VALUE_USDC))
|
if is_t1_stable:
|
||||||
|
# T1 is stable (e.g. ETH/USDC). Target 2000 USD = 2000 Token1.
|
||||||
|
investment_val_token1 = target_usd
|
||||||
|
elif is_t0_stable:
|
||||||
|
# T0 is stable (e.g. USDT/BNB). Target 2000 USD = 2000 Token0.
|
||||||
|
# We need value in Token1.
|
||||||
|
# Price 0 in 1 = (BNB per USDT) approx 0.0012
|
||||||
|
# Val T1 = Val T0 * Price(0 in 1)
|
||||||
|
investment_val_token1 = target_usd * price_0_in_1
|
||||||
|
logger.info(f"💱 Converted ${target_usd} -> {investment_val_token1:.4f} {t1_sym} (Price: {price_0_in_1:.6f})")
|
||||||
|
else:
|
||||||
|
# Fallback: Assume T1 is Stable (Dangerous but standard default)
|
||||||
|
logger.warning("⚠️ Could not detect Stable token. Assuming T1 is stable.")
|
||||||
|
investment_val_token1 = target_usd
|
||||||
|
|
||||||
amt0, amt1 = calculate_mint_amounts(tick, tick_lower, tick_upper, investment_val_dec, d0, d1, pool_data['sqrtPriceX96'])
|
amt0, amt1 = calculate_mint_amounts(tick, tick_lower, tick_upper, investment_val_token1, d0, d1, pool_data['sqrtPriceX96'])
|
||||||
|
|
||||||
if check_and_swap_for_deposit(w3, router, account, token0, token1, amt0, amt1, pool_data['sqrtPriceX96'], d0, d1):
|
if check_and_swap_for_deposit(w3, router, account, token0, token1, amt0, amt1, pool_data['sqrtPriceX96'], d0, d1):
|
||||||
minted = mint_new_position(w3, npm, account, token0, token1, amt0, amt1, tick_lower, tick_upper)
|
minted = mint_new_position(w3, npm, account, token0, token1, amt0, amt1, tick_lower, tick_upper)
|
||||||
|
|||||||
Reference in New Issue
Block a user