working version, before optimalization

This commit is contained in:
2026-01-06 09:47:49 +01:00
parent c29dc2c8ac
commit a166d33012
36 changed files with 5394 additions and 901 deletions

View File

@ -0,0 +1,44 @@
[
{
"type": "AUTOMATIC",
"token_id": 41577981,
"status": "CLOSED",
"target_value": 99.99,
"entry_price": 3146.0405,
"amount0_initial": 0.0153,
"amount1_initial": 51.8762,
"liquidity": "36103333466890",
"range_upper": 3301.0555,
"range_lower": 2986.9335,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767561751,
"time_open": "04.01.26 22:22:31",
"clp_fees": 0.0,
"clp_TotPnL": -0.04
},
{
"type": "AUTOMATIC",
"token_id": 41584557,
"status": "CLOSED",
"target_value": 199.94,
"entry_price": 3147.2991,
"amount0_initial": 0.0307,
"amount1_initial": 103.2276,
"liquidity": "51814114093918",
"range_upper": 3367.7379,
"range_lower": 2927.7912,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767564277,
"time_open": "04.01.26 23:04:37",
"clp_fees": 0.39,
"clp_TotPnL": 1.98,
"hedge_TotPnL": -50.537316,
"hedge_fees_paid": 12.238471,
"combined_TotPnL": -62.76,
"hedge_HL_cost_est": 12.28,
"hedge_pnl_unrealized": -2.07,
"last_sync_hl": 1767685736
}
]

View File

@ -508,7 +508,7 @@
{
"type": "AUTOMATIC",
"token_id": 6176727,
"status": "OPEN",
"status": "CLOSED",
"target_value": 990.64,
"entry_price": 867.5401,
"amount0_initial": 490.6325,
@ -520,9 +520,163 @@
"token1_decimals": 18,
"timestamp_open": 1767336634,
"time_open": "02.01.26 07:50:34",
"hedge_TotPnL": 0.241595,
"hedge_fees_paid": 0.364602,
"clp_fees": 0.22,
"clp_TotPnL": -0.73
"hedge_TotPnL": -5.442897,
"hedge_fees_paid": 0.85406,
"clp_fees": 0.91,
"clp_TotPnL": 1.88,
"timestamp_close": 1767347410,
"time_close": "02.01.26 10:50:10"
},
{
"type": "AUTOMATIC",
"token_id": 6177360,
"status": "CLOSED",
"target_value": 993.15,
"entry_price": 870.8428,
"amount0_initial": 493.1411,
"amount1_initial": 0.5742,
"liquidity": "8638221415835012765221",
"range_upper": 874.2456,
"range_lower": 867.4533,
"token0_decimals": 18,
"token1_decimals": 18,
"timestamp_open": 1767347607,
"time_open": "02.01.26 10:53:27",
"hedge_TotPnL": 1.294631,
"hedge_fees_paid": 1.047541,
"clp_fees": 1.24,
"clp_TotPnL": -1.66,
"timestamp_close": 1767363551,
"time_close": "02.01.26 15:19:11"
},
{
"type": "AUTOMATIC",
"token_id": 6185008,
"status": "CLOSED",
"target_value": 992.5,
"entry_price": 867.1064,
"amount0_initial": 492.4924,
"amount1_initial": 0.5766,
"liquidity": "8651147821199061055073",
"range_upper": 870.4946,
"range_lower": 863.7315,
"token0_decimals": 18,
"token1_decimals": 18,
"timestamp_open": 1767363751,
"time_open": "02.01.26 15:22:31",
"hedge_TotPnL": -2.674848,
"hedge_fees_paid": 0.393713,
"clp_fees": 0.32,
"clp_TotPnL": 1.28,
"timestamp_close": 1767364835,
"time_close": "02.01.26 15:40:35"
},
{
"type": "AUTOMATIC",
"token_id": 6185867,
"status": "CLOSED",
"target_value": 996.08,
"entry_price": 875.5579,
"amount0_initial": 496.0791,
"amount1_initial": 0.5711,
"liquidity": "8640396990671870185711",
"range_upper": 878.9791,
"range_lower": 872.15,
"token0_decimals": 18,
"token1_decimals": 18,
"timestamp_open": 1767365969,
"time_open": "02.01.26 15:59:29",
"hedge_TotPnL": 2.830618,
"hedge_fees_paid": 1.060949,
"clp_fees": 0.53,
"clp_TotPnL": -2.97,
"timestamp_close": 1767367303,
"time_close": "02.01.26 16:21:43"
},
{
"type": "AUTOMATIC",
"token_id": 6186334,
"status": "CLOSED",
"target_value": 996.88,
"entry_price": 873.9834,
"amount0_initial": 496.8714,
"amount1_initial": 0.5721,
"liquidity": "8655088063104153413073",
"range_upper": 877.3984,
"range_lower": 870.5816,
"token0_decimals": 18,
"token1_decimals": 18,
"timestamp_open": 1767367883,
"time_open": "02.01.26 16:31:23",
"hedge_TotPnL": -3.585575,
"hedge_fees_paid": 0.57548,
"clp_fees": 0.13,
"clp_TotPnL": 1.1,
"timestamp_close": 1767368416,
"time_close": "02.01.26 16:40:16"
},
{
"type": "AUTOMATIC",
"token_id": 6186613,
"status": "CLOSED",
"target_value": 993.42,
"entry_price": 880.2984,
"amount0_initial": 493.4134,
"amount1_initial": 0.568,
"liquidity": "8594082766621558309079",
"range_upper": 883.7381,
"range_lower": 876.8721,
"token0_decimals": 18,
"token1_decimals": 18,
"timestamp_open": 1767368652,
"time_open": "02.01.26 16:44:12",
"hedge_TotPnL": -4.384157,
"hedge_fees_paid": 0.627319,
"clp_fees": 0.92,
"clp_TotPnL": 1.89,
"timestamp_close": 1767371545,
"time_close": "02.01.26 17:32:25"
},
{
"type": "AUTOMATIC",
"token_id": 6187096,
"status": "CLOSED",
"target_value": 996.91,
"entry_price": 885.9501,
"amount0_initial": 496.8996,
"amount1_initial": 0.5644,
"liquidity": "2271526539550158344821",
"range_upper": 899.159,
"range_lower": 872.9353,
"token0_decimals": 18,
"token1_decimals": 18,
"timestamp_open": 1767372001,
"time_open": "02.01.26 17:40:01",
"hedge_TotPnL": 7.60386,
"hedge_fees_paid": 0.283695,
"clp_fees": 1.38,
"clp_TotPnL": -9.61,
"timestamp_close": 1767424468,
"time_close": "03.01.26 08:14:28"
},
{
"type": "AUTOMATIC",
"token_id": 6203530,
"status": "CLOSED",
"target_value": 998.23,
"entry_price": 872.0628,
"amount0_initial": 500.0027,
"amount1_initial": 0.5713,
"liquidity": "2292568457223553397610",
"range_upper": 885.0647,
"range_lower": 859.252,
"token0_decimals": 18,
"token1_decimals": 18,
"timestamp_open": 1767424756,
"time_open": "03.01.26 08:19:16",
"hedge_TotPnL": -2.47676,
"hedge_fees_paid": 0.287962,
"clp_fees": 0.19,
"clp_TotPnL": 1.92
}
]

View File

@ -288,7 +288,7 @@
{
"type": "AUTOMATIC",
"token_id": 5182179,
"status": "OPEN",
"status": "CLOSED",
"target_value": 1993.84,
"entry_price": 2969.9855,
"amount0_initial": 0.3347,
@ -299,9 +299,607 @@
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1766968369,
"hedge_TotPnL": -11.871298,
"hedge_fees_paid": 2.122534,
"clp_fees": 18.4,
"clp_TotPnL": 30.89
"hedge_TotPnL": -62.166433,
"hedge_fees_paid": 2.587208,
"clp_fees": 23.23,
"clp_TotPnL": 47.64,
"timestamp_close": 1767371501,
"time_close": "02.01.26 17:31:41"
},
{
"type": "AUTOMATIC",
"token_id": 5190205,
"status": "CLOSED",
"target_value": 1990.81,
"entry_price": 3136.9306,
"amount0_initial": 0.2991,
"amount1_initial": 1052.4842,
"liquidity": "3750669047237424",
"range_upper": 3165.289,
"range_lower": 3105.7192,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767371848,
"time_open": "02.01.26 17:37:28",
"hedge_TotPnL": 9.40878,
"hedge_fees_paid": 0.538036,
"clp_fees": 6.41,
"clp_TotPnL": -9.78,
"timestamp_close": 1767379841,
"time_close": "02.01.26 19:50:41"
},
{
"type": "AUTOMATIC",
"token_id": 5190713,
"status": "CLOSED",
"target_value": 995.94,
"entry_price": 3103.8564,
"amount0_initial": 0.1562,
"amount1_initial": 511.0986,
"liquidity": "610494695009033",
"range_upper": 3193.9038,
"range_lower": 3010.9236,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767379916,
"time_open": "02.01.26 19:51:56",
"hedge_TotPnL": 0.4043,
"hedge_fees_paid": 0.277557,
"clp_fees": 1.83,
"clp_TotPnL": 0.77
},
{
"type": "AUTOMATIC",
"token_id": 5191754,
"status": "CLOSED",
"target_value": 2986.13,
"entry_price": 3095.7973,
"amount0_initial": 0.4577,
"amount1_initial": 1569.095,
"liquidity": "8270626895418999",
"range_upper": 3115.0499,
"range_lower": 3074.8183,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767430984,
"time_open": "03.01.26 10:03:04",
"hedge_TotPnL": -10.166,
"hedge_fees_paid": 7.014425,
"clp_fees": 8.24,
"clp_TotPnL": 12.66,
"timestamp_close": 1767475160,
"time_close": "03.01.26 22:19:20"
},
{
"type": "AUTOMATIC",
"token_id": 5192638,
"status": "CLOSED",
"target_value": 2994.04,
"entry_price": 3125.0335,
"amount0_initial": 0.46,
"amount1_initial": 1559.9,
"range_upper": 3140.069,
"range_lower": 3108.8263,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767482267,
"time_open": "04.01.26 00:17:47",
"clp_fees": 2998.49,
"clp_TotPnL": 4.45,
"hedge_TotPnL": -9.78996,
"hedge_fees_paid": 0.868435,
"timestamp_close": 1767485229,
"time_close": "04.01.26 01:07:09"
},
{
"type": "AUTOMATIC",
"token_id": 5192707,
"status": "CLOSED",
"target_value": 2999.37,
"entry_price": 3146.0405,
"amount0_initial": 0.4767,
"amount1_initial": 1500.0,
"range_upper": 3158.9651,
"range_lower": 3130.6634,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767485383,
"time_open": "04.01.26 01:09:43",
"clp_fees": 4.35,
"clp_TotPnL": -0.63,
"timestamp_close": 1767488126,
"time_close": "04.01.26 01:55:26"
},
{
"type": "AUTOMATIC",
"token_id": 5192787,
"status": "CLOSED",
"target_value": 2989.41,
"entry_price": 3158.9651,
"amount0_initial": 0.4715,
"amount1_initial": 1500.0,
"range_upper": 3171.6256,
"range_lower": 3143.2105,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767488200,
"time_open": "04.01.26 01:56:40",
"clp_fees": 2.57,
"clp_TotPnL": -10.59,
"hedge_TotPnL": 3.058716,
"hedge_fees_paid": 3.334872,
"timestamp_close": 1767491374,
"time_close": "04.01.26 02:49:34"
},
{
"type": "AUTOMATIC",
"token_id": 5192872,
"status": "CLOSED",
"target_value": 2974.66,
"entry_price": 3142.8962,
"amount0_initial": 0.4692,
"amount1_initial": 1500.0,
"range_upper": 3155.8079,
"range_lower": 3127.5344,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767491446,
"time_open": "04.01.26 02:50:46",
"clp_fees": 6.14,
"clp_TotPnL": 25.34,
"hedge_TotPnL": -1.09566,
"hedge_fees_paid": 3.319216,
"timestamp_close": 1767507012,
"time_close": "04.01.26 07:10:12"
},
{
"type": "AUTOMATIC",
"token_id": 5193076,
"status": "CLOSED",
"target_value": 2927.37,
"entry_price": 3155.8079,
"amount0_initial": 0.4522,
"amount1_initial": 1500.0,
"range_upper": 3168.4557,
"range_lower": 3140.069,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767507083,
"time_open": "04.01.26 07:11:23",
"clp_fees": 2.91,
"clp_TotPnL": -36.11,
"hedge_TotPnL": 0.0,
"hedge_fees_paid": 1.655925,
"timestamp_close": 1767511297,
"time_close": "04.01.26 08:21:37"
},
{
"type": "AUTOMATIC",
"token_id": 5193158,
"status": "CLOSED",
"target_value": 2970.4,
"entry_price": 3139.755,
"amount0_initial": 0.477,
"amount1_initial": 1500.0,
"range_upper": 3152.6538,
"range_lower": 3124.4086,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767511369,
"time_open": "04.01.26 08:22:49",
"clp_fees": 11.2,
"clp_TotPnL": 0.27,
"hedge_TotPnL": -1.76453,
"hedge_fees_paid": 1.292159,
"timestamp_close": 1767541575,
"time_close": "04.01.26 16:46:15"
},
{
"type": "AUTOMATIC",
"token_id": 5193699,
"status": "CLOSED",
"target_value": 2985.65,
"entry_price": 3124.0962,
"amount0_initial": 0.4461,
"amount1_initial": 1592.1238,
"liquidity": "8231768660184301",
"range_upper": 3143.2105,
"range_lower": 3102.6152,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767541587,
"time_open": "04.01.26 16:46:27",
"clp_fees": 5.59,
"clp_TotPnL": 9.87,
"hedge_TotPnL": -2.74009,
"hedge_fees_paid": 3.283603,
"timestamp_close": 1767561023,
"time_close": "04.01.26 22:10:23"
},
{
"type": "AUTOMATIC",
"token_id": 5194064,
"status": "CLOSED",
"target_value": 2995.07,
"entry_price": 3149.8178,
"amount0_initial": 0.4308,
"amount1_initial": 1638.2123,
"liquidity": "8224032596348534",
"range_upper": 3168.4557,
"range_lower": 3127.5344,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767561335,
"time_open": "04.01.26 22:15:35",
"hedge_TotPnL": -10.8216,
"hedge_fees_paid": 0.744327,
"clp_fees": 10.96,
"clp_TotPnL": 14.98,
"timestamp_close": 1767574703,
"time_close": "05.01.26 01:58:23"
},
{
"type": "AUTOMATIC",
"token_id": 5194353,
"status": "CLOSED",
"target_value": 2986.84,
"entry_price": 3167.1887,
"amount0_initial": 0.4622,
"amount1_initial": 1523.0178,
"liquidity": "8178792616583388",
"range_upper": 3187.5227,
"range_lower": 3146.3551,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767575103,
"time_open": "05.01.26 02:05:03",
"hedge_TotPnL": -11.72105,
"hedge_fees_paid": 0.851074,
"clp_fees": 1.32,
"clp_TotPnL": 6.03,
"timestamp_close": 1767575748,
"time_close": "05.01.26 02:15:48"
},
{
"type": "AUTOMATIC",
"token_id": 5194438,
"status": "CLOSED",
"target_value": 2994.26,
"entry_price": 3182.7452,
"amount0_initial": 0.4695,
"amount1_initial": 1499.9963,
"liquidity": "8179052347416944",
"range_upper": 3203.4994,
"range_lower": 3162.1255,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767576062,
"time_open": "05.01.26 02:21:02",
"hedge_TotPnL": -13.130776,
"hedge_fees_paid": 0.866544,
"clp_fees": 2.0,
"clp_TotPnL": 6.88,
"timestamp_close": 1767577312,
"time_close": "05.01.26 02:41:52"
},
{
"type": "AUTOMATIC",
"token_id": 5194549,
"status": "CLOSED",
"target_value": 2993.07,
"entry_price": 3209.2706,
"amount0_initial": 0.4437,
"amount1_initial": 1569.0894,
"liquidity": "8141974814554655",
"range_upper": 3229.2289,
"range_lower": 3187.5227,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767577667,
"time_open": "05.01.26 02:47:47",
"hedge_TotPnL": 9.143032,
"hedge_fees_paid": 0.817659,
"clp_fees": 1.68,
"clp_TotPnL": -14.2,
"timestamp_close": 1767578919,
"time_close": "05.01.26 03:08:39"
},
{
"type": "AUTOMATIC",
"token_id": 5194636,
"status": "CLOSED",
"target_value": 2999.61,
"entry_price": 3191.6689,
"amount0_initial": 0.4122,
"amount1_initial": 1683.9338,
"liquidity": "8182395676682951",
"range_upper": 3209.9125,
"range_lower": 3168.4557,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767579235,
"time_open": "05.01.26 03:13:55",
"hedge_TotPnL": 12.194039,
"hedge_fees_paid": 0.752756,
"clp_fees": 4.31,
"clp_TotPnL": -14.67,
"timestamp_close": 1767587437,
"time_close": "05.01.26 05:30:37"
},
{
"type": "AUTOMATIC",
"token_id": 5194825,
"status": "CLOSED",
"target_value": 2968.38,
"entry_price": 3162.4417,
"amount0_initial": 0.4206,
"amount1_initial": 1638.1774,
"liquidity": "8134444867821814",
"range_upper": 3181.1543,
"range_lower": 3140.069,
"token0_decimals": 18,
"token1_decimals": 6,
"timestamp_open": 1767587750,
"time_open": "05.01.26 05:35:50",
"hedge_TotPnL": -0.1704,
"hedge_fees_paid": 1.553601,
"clp_fees": 15.16,
"clp_TotPnL": 10.5,
"combined_TotPnL": 13.22,
"hedge_HL_cost_est": 0.77,
"hedge_pnl_unrealized": 4.3,
"last_sync_hl": 1767622049,
"timestamp_close": 1767622037,
"time_close": "05.01.26 15:07:17"
},
{
"type": "AUTOMATIC",
"token_id": 5195733,
"status": "CLOSED",
"target_value": 2984.65,
"entry_price": 3150.7629,
"amount0_initial": 0.4341,
"amount1_initial": 1617.0073,
"liquidity": "4637302533941787",
"range_upper": 3184.3369,
"range_lower": 3111.9366,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "AUTO",
"range_width_initial": 0.01160980429312654,
"timestamp_open": 1767622560,
"time_open": "05.01.26 15:16:00",
"hedge_TotPnL": -3.139154,
"hedge_fees_paid": 0.793472,
"combined_TotPnL": 1.6,
"hedge_HL_cost_est": 0.79,
"hedge_pnl_unrealized": -2.92,
"last_sync_hl": 1767624126,
"clp_fees": 2.68,
"clp_TotPnL": 5.4,
"timestamp_close": 1767624113,
"time_close": "05.01.26 15:41:53"
},
{
"type": "AUTOMATIC",
"token_id": 5195860,
"status": "CLOSED",
"target_value": 2976.38,
"entry_price": 3157.3861,
"amount0_initial": 0.438,
"amount1_initial": 1593.5552,
"liquidity": "6634808665424129",
"range_upper": 3181.1543,
"range_lower": 3130.6634,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "AUTO",
"range_width_initial": 0.008451255668317245,
"timestamp_open": 1767624512,
"time_open": "05.01.26 15:48:32",
"hedge_TotPnL": -4.258952,
"hedge_fees_paid": 0.804724,
"combined_TotPnL": 0.01,
"hedge_HL_cost_est": 0.81,
"hedge_pnl_unrealized": -3.03,
"last_sync_hl": 1767624869,
"clp_fees": 1.25,
"clp_TotPnL": 4.26,
"timestamp_close": 1767624855,
"time_close": "05.01.26 15:54:15"
},
{
"type": "AUTOMATIC",
"token_id": 5195910,
"status": "CLOSED",
"target_value": 2957.51,
"entry_price": 3166.872,
"amount0_initial": 0.4342,
"amount1_initial": 1582.386,
"liquidity": "8772781500746006",
"range_upper": 3184.3369,
"range_lower": 3146.3551,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "AUTO",
"range_width_initial": 0.00610577067463115,
"timestamp_open": 1767625173,
"time_open": "05.01.26 15:59:33",
"hedge_TotPnL": -6.49276,
"hedge_fees_paid": 0.782891,
"combined_TotPnL": 2.73,
"hedge_HL_cost_est": 0.78,
"hedge_pnl_unrealized": -5.77,
"last_sync_hl": 1767627649,
"clp_fees": 5.74,
"clp_TotPnL": 9.23,
"timestamp_close": 1767627635,
"time_close": "05.01.26 16:40:35"
},
{
"type": "AUTOMATIC",
"token_id": 5196059,
"status": "CLOSED",
"target_value": 2984.89,
"entry_price": 3194.862,
"amount0_initial": 0.4148,
"amount1_initial": 1659.6726,
"liquidity": "7054813881893230",
"range_upper": 3216.3384,
"range_lower": 3168.4557,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "FIXED",
"range_width_initial": 0.0075,
"timestamp_open": 1767627994,
"time_open": "05.01.26 16:46:34",
"hedge_TotPnL": 9.768993,
"hedge_fees_paid": 0.761925,
"combined_TotPnL": -3.68,
"hedge_HL_cost_est": 0.76,
"hedge_pnl_unrealized": 13.07,
"last_sync_hl": 1767633796,
"clp_fees": 6.47,
"clp_TotPnL": -13.17,
"timestamp_close": 1767633781,
"time_close": "05.01.26 18:23:01"
},
{
"type": "AUTOMATIC",
"token_id": 5196356,
"status": "CLOSED",
"target_value": 2970.03,
"entry_price": 3162.7579,
"amount0_initial": 0.4513,
"amount1_initial": 1542.7246,
"liquidity": "7558082601107499",
"range_upper": 3184.3369,
"range_lower": 3140.069,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "AUTO",
"range_width_initial": 0.007188283945973874,
"timestamp_open": 1767634095,
"time_open": "05.01.26 18:28:15",
"hedge_TotPnL": -5.78208,
"hedge_fees_paid": 0.833176,
"combined_TotPnL": -1.93,
"hedge_HL_cost_est": 0.83,
"hedge_pnl_unrealized": -5.65,
"last_sync_hl": 1767634756,
"clp_fees": 0.53,
"clp_TotPnL": 4.88,
"timestamp_close": 1767634742,
"time_close": "05.01.26 18:39:02"
},
{
"type": "AUTOMATIC",
"token_id": 5196394,
"status": "CLOSED",
"target_value": 2997.55,
"entry_price": 3187.5227,
"amount0_initial": 0.4385,
"amount1_initial": 1599.8109,
"liquidity": "7092801036999878",
"range_upper": 3209.9125,
"range_lower": 3162.1255,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "FIXED",
"range_width_initial": 0.0075,
"timestamp_open": 1767635055,
"time_open": "05.01.26 18:44:15",
"hedge_TotPnL": -10.899792,
"hedge_fees_paid": 0.809798,
"combined_TotPnL": 5.4,
"hedge_HL_cost_est": 0.61,
"hedge_pnl_unrealized": 0.0,
"last_sync_hl": 1767635701,
"clp_fees": 1.13,
"clp_TotPnL": 6.03,
"timestamp_close": 1767635701,
"time_close": "05.01.26 18:55:01"
},
{
"type": "AUTOMATIC",
"token_id": 5196471,
"status": "CLOSED",
"target_value": 2980.42,
"entry_price": 3209.5915,
"amount0_initial": 0.4364,
"amount1_initial": 1579.8119,
"liquidity": "7027957507483544",
"range_upper": 3232.4596,
"range_lower": 3184.3369,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "FIXED",
"range_width_initial": 0.0075,
"timestamp_open": 1767636146,
"time_open": "05.01.26 19:02:26",
"hedge_TotPnL": -13.87062,
"hedge_fees_paid": 0.817984,
"combined_TotPnL": -2.61,
"hedge_HL_cost_est": 1.43,
"hedge_pnl_unrealized": 0.0,
"last_sync_hl": 1767643534,
"clp_fees": 7.68,
"clp_TotPnL": 12.7,
"timestamp_close": 1767643533,
"time_close": "05.01.26 21:05:33"
},
{
"type": "AUTOMATIC",
"token_id": 5196956,
"status": "CLOSED",
"target_value": 2977.3,
"entry_price": 3232.7828,
"amount0_initial": 0.4269,
"amount1_initial": 1597.0858,
"liquidity": "6995425133879491",
"range_upper": 3255.165,
"range_lower": 3206.7043,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "FIXED",
"range_width_initial": 0.0075,
"timestamp_open": 1767643849,
"time_open": "05.01.26 21:10:49",
"hedge_TotPnL": -1.356196,
"hedge_fees_paid": 1.243909,
"combined_TotPnL": -3.62,
"hedge_HL_cost_est": 1.58,
"hedge_pnl_unrealized": 9.64,
"last_sync_hl": 1767660041,
"clp_fees": 10.88,
"clp_TotPnL": -7.56,
"timestamp_close": 1767660028,
"time_close": "06.01.26 01:40:28"
},
{
"type": "AUTOMATIC",
"token_id": 5198324,
"status": "OPEN",
"target_value": 2981.26,
"entry_price": 3217.6251,
"amount0_initial": 0.4045,
"amount1_initial": 1679.6194,
"liquidity": "7021319704792719",
"range_upper": 3238.9306,
"range_lower": 3190.7116,
"token0_decimals": 18,
"token1_decimals": 6,
"range_mode": "AUTO",
"range_width_initial": 0.007592620856214823,
"timestamp_open": 1767688223,
"time_open": "06.01.26 09:30:23",
"hedge_TotPnL": 0.0,
"hedge_fees_paid": 0.0,
"combined_TotPnL": -0.47,
"hedge_HL_cost_est": 0.57,
"hedge_pnl_unrealized": 0.72,
"last_sync_hl": 1767689255,
"clp_fees": 0.12,
"clp_TotPnL": 0.64
}
]

82
florida/clp_abis.py Normal file
View File

@ -0,0 +1,82 @@
import json
NONFUNGIBLE_POSITION_MANAGER_ABI = json.loads('''
[
{"anonymous": false, "inputs": [{"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"indexed": false, "internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"indexed": false, "internalType": "uint256", "name": "amount0", "type": "uint256"}, {"indexed": false, "internalType": "uint256", "name": "amount1", "type": "uint256"}], "name": "IncreaseLiquidity", "type": "event"},
{"anonymous": false, "inputs": [{"indexed": true, "internalType": "address", "name": "from", "type": "address"}, {"indexed": true, "internalType": "address", "name": "to", "type": "address"}, {"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}], "name": "Transfer", "type": "event"},
{"inputs": [], "name": "factory", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], "name": "positions", "outputs": [{"internalType": "uint96", "name": "nonce", "type": "uint96"}, {"internalType": "address", "name": "operator", "type": "address"}, {"internalType": "address", "name": "token0", "type": "address"}, {"internalType": "address", "name": "token1", "type": "address"}, {"internalType": "uint24", "name": "fee", "type": "uint24"}, {"internalType": "int24", "name": "tickLower", "type": "int24"}, {"internalType": "int24", "name": "tickUpper", "type": "int24"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "feeGrowthInside0LastX128", "type": "uint256"}, {"internalType": "uint256", "name": "feeGrowthInside1LastX128", "type": "uint256"}, {"internalType": "uint128", "name": "tokensOwed0", "type": "uint128"}, {"internalType": "uint128", "name": "tokensOwed1", "type": "uint128"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"components": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint128", "name": "amount0Max", "type": "uint128"}, {"internalType": "uint128", "name": "amount1Max", "type": "uint128"}], "internalType": "struct INonfungiblePositionManager.CollectParams", "name": "params", "type": "tuple"}], "name": "collect", "outputs": [{"internalType": "uint256", "name": "amount0", "type": "uint256"}, {"internalType": "uint256", "name": "amount1", "type": "uint256"}], "stateMutability": "payable", "type": "function"},
{"inputs": [{"components": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "amount0Min", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Min", "type": "uint256"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "internalType": "struct INonfungiblePositionManager.DecreaseLiquidityParams", "name": "params", "type": "tuple"}], "name": "decreaseLiquidity", "outputs": [{"internalType": "uint256", "name": "amount0", "type": "uint256"}, {"internalType": "uint256", "name": "amount1", "type": "uint256"}], "stateMutability": "payable", "type": "function"},
{"inputs": [{"components": [{"internalType": "address", "name": "token0", "type": "address"}, {"internalType": "address", "name": "token1", "type": "address"}, {"internalType": "uint24", "name": "fee", "type": "uint24"}, {"internalType": "int24", "name": "tickLower", "type": "int24"}, {"internalType": "int24", "name": "tickUpper", "type": "int24"}, {"internalType": "uint256", "name": "amount0Desired", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Desired", "type": "uint256"}, {"internalType": "uint256", "name": "amount0Min", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Min", "type": "uint256"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "internalType": "struct INonfungiblePositionManager.MintParams", "name": "params", "type": "tuple"}], "name": "mint", "outputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "amount0", "type": "uint256"}, {"internalType": "uint256", "name": "amount1", "type": "uint256"}], "stateMutability": "payable", "type": "function"}
]
''')
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": "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": "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": "tickSpacing", "outputs": [{"internalType": "int24", "name": "", "type": "int24"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "liquidity", "outputs": [{"internalType": "uint128", "name": "", "type": "uint128"}], "stateMutability": "view", "type": "function"}
]
''')
ERC20_ABI = json.loads('''
[
{"inputs": [], "name": "decimals", "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "symbol", "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"internalType": "address", "name": "account", "type": "address"}], "name": "balanceOf", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"internalType": "address", "name": "spender", "type": "address"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "approve", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"},
{"inputs": [{"internalType": "address", "name": "owner", "type": "address"}, {"internalType": "address", "name": "spender", "type": "address"}], "name": "allowance", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}
]
''')
UNISWAP_V3_FACTORY_ABI = json.loads('''
[
{"inputs": [{"internalType": "address", "name": "tokenA", "type": "address"}, {"internalType": "address", "name": "tokenB", "type": "address"}, {"internalType": "uint24", "name": "fee", "type": "uint24"}], "name": "getPool", "outputs": [{"internalType": "address", "name": "pool", "type": "address"}], "stateMutability": "view", "type": "function"}
]
''')
AERODROME_FACTORY_ABI = json.loads('''
[
{"inputs": [{"internalType": "address", "name": "tokenA", "type": "address"}, {"internalType": "address", "name": "tokenB", "type": "address"}, {"internalType": "int24", "name": "tickSpacing", "type": "int24"}], "name": "getPool", "outputs": [{"internalType": "address", "name": "pool", "type": "address"}], "stateMutability": "view", "type": "function"}
]
''')
AERODROME_NPM_ABI = json.loads('''
[
{"anonymous": false, "inputs": [{"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"indexed": false, "internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"indexed": false, "internalType": "uint256", "name": "amount0", "type": "uint256"}, {"indexed": false, "internalType": "uint256", "name": "amount1", "type": "uint256"}], "name": "IncreaseLiquidity", "type": "event"},
{"anonymous": false, "inputs": [{"indexed": true, "internalType": "address", "name": "from", "type": "address"}, {"indexed": true, "internalType": "address", "name": "to", "type": "address"}, {"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}], "name": "Transfer", "type": "event"},
{"inputs": [], "name": "factory", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], "name": "positions", "outputs": [{"internalType": "uint96", "name": "nonce", "type": "uint96"}, {"internalType": "address", "name": "operator", "type": "address"}, {"internalType": "address", "name": "token0", "type": "address"}, {"internalType": "address", "name": "token1", "type": "address"}, {"internalType": "int24", "name": "tickSpacing", "type": "int24"}, {"internalType": "int24", "name": "tickLower", "type": "int24"}, {"internalType": "int24", "name": "tickUpper", "type": "int24"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "feeGrowthInside0LastX128", "type": "uint256"}, {"internalType": "uint256", "name": "feeGrowthInside1LastX128", "type": "uint256"}, {"internalType": "uint128", "name": "tokensOwed0", "type": "uint128"}, {"internalType": "uint128", "name": "tokensOwed1", "type": "uint128"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"components": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint128", "name": "amount0Max", "type": "uint128"}, {"internalType": "uint128", "name": "amount1Max", "type": "uint128"}], "internalType": "struct INonfungiblePositionManager.CollectParams", "name": "params", "type": "tuple"}], "name": "collect", "outputs": [{"internalType": "uint256", "name": "amount0", "type": "uint256"}, {"internalType": "uint256", "name": "amount1", "type": "uint256"}], "stateMutability": "payable", "type": "function"},
{"inputs": [{"components": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "amount0Min", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Min", "type": "uint256"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}], "internalType": "struct INonfungiblePositionManager.DecreaseLiquidityParams", "name": "params", "type": "tuple"}], "name": "decreaseLiquidity", "outputs": [{"internalType": "uint256", "name": "amount0", "type": "uint256"}, {"internalType": "uint256", "name": "amount1", "type": "uint256"}], "stateMutability": "payable", "type": "function"},
{"inputs": [{"components": [{"internalType": "address", "name": "token0", "type": "address"}, {"internalType": "address", "name": "token1", "type": "address"}, {"internalType": "int24", "name": "tickSpacing", "type": "int24"}, {"internalType": "int24", "name": "tickLower", "type": "int24"}, {"internalType": "int24", "name": "tickUpper", "type": "int24"}, {"internalType": "uint256", "name": "amount0Desired", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Desired", "type": "uint256"}, {"internalType": "uint256", "name": "amount0Min", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Min", "type": "uint256"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}, {"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}], "internalType": "struct INonfungiblePositionManager.MintParams", "name": "params", "type": "tuple"}], "name": "mint", "outputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "amount0", "type": "uint256"}, {"internalType": "uint256", "name": "amount1", "type": "uint256"}], "stateMutability": "payable", "type": "function"}
]
''')
AERODROME_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": "bool", "name": "unlocked", "type": "bool"}], "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": "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"}
]
''')
SWAP_ROUTER_ABI = json.loads('''
[
{"inputs": [{"components": [{"internalType": "address", "name": "tokenIn", "type": "address"}, {"internalType": "address", "name": "tokenOut", "type": "address"}, {"internalType": "uint24", "name": "fee", "type": "uint24"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}, {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "amountOutMinimum", "type": "uint256"}, {"internalType": "uint160", "name": "sqrtPriceLimitX96", "type": "uint160"}], "internalType": "struct ISwapRouter.ExactInputSingleParams", "name": "params", "type": "tuple"}], "name": "exactInputSingle", "outputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}], "stateMutability": "payable", "type": "function"}
]
''')
WETH9_ABI = json.loads('''
[
{"constant": false, "inputs": [], "name": "deposit", "outputs": [], "payable": true, "stateMutability": "payable", "type": "function"},
{"constant": false, "inputs": [{"name": "wad", "type": "uint256"}], "name": "withdraw", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function"}
]
''')

View File

@ -8,7 +8,9 @@ STATUS_FILE = os.environ.get("STATUS_FILE", f"{TARGET_DEX}_status.json")
# --- DEFAULT STRATEGY ---
DEFAULT_STRATEGY = {
"MONITOR_INTERVAL_SECONDS": 60, # How often the Manager checks for range status
"MONITOR_INTERVAL_SECONDS": 300, # Manager loop & sync interval
"LOG_INTERVAL_SECONDS": 300, # Hedger console logging interval
"RANGE_MODE": "AUTO", # Options: "AUTO" (BB-based), "FIXED" (RANGE_WIDTH_PCT)
"CLOSE_POSITION_ENABLED": True, # Allow the bot to automatically close out-of-range positions
"OPEN_POSITION_ENABLED": True, # Allow the bot to automatically open new positions
"REBALANCE_ON_CLOSE_BELOW_RANGE": True, # Strategy flag for specific closing behavior
@ -19,17 +21,16 @@ DEFAULT_STRATEGY = {
"VALUE_REFERENCE": "USD", # Base currency for all calculations
# Range Settings
"RANGE_WIDTH_PCT": Decimal("0.05"), # LP width (e.g. 0.05 = +/- 5% from current price)
"SLIPPAGE_TOLERANCE": Decimal("0.02"), # Max allowed slippage for swaps and minting
"RANGE_WIDTH_PCT": Decimal("0.03"), # LP width (e.g. 0.05 = +/- 5% from current price)
"SLIPPAGE_TOLERANCE": Decimal("0.03"), # Max allowed slippage for swaps and minting
"TRANSACTION_TIMEOUT_SECONDS": 30, # Timeout for blockchain transactions
# Hedging Settings
"HEDGE_STRATEGY": "ASYMMETRIC", # Options: "STANDARD" (Full Range Hedge), "ASYMMETRIC" (Edge-Only Reduction)
# ude wide areas for ASYMETRIC "EDGE_CLEANUP_MARGIN_PCT": Decimal("0.1875"),
"HEDGE_STRATEGY": "ASYMMETRIC", # Options: "STANDARD" (Full Range Hedge), "ASYMMETRIC" (Edge-Only Reduction), "FIXED" (Initial Delta)
"MIN_HEDGE_THRESHOLD": Decimal("0.012"), # Minimum delta change (in coins) required to trigger a trade
# Unified Hedger Settings
"CHECK_INTERVAL": 1, # Loop speed for the hedger (seconds)
"LEVERAGE": 5, # Leverage to use on Hyperliquid
"ZONE_BOTTOM_HEDGE_LIMIT": Decimal("1.0"), # Multiplier limit at the bottom of the range
"ZONE_CLOSE_START": Decimal("10.0"), # Distance (pct) from edge to start closing logic
@ -47,7 +48,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.05"), # % 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)
@ -74,9 +75,9 @@ CLP_PROFILES = {
"TOKEN_B_ADDRESS": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC
"WRAPPED_NATIVE_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
"POOL_FEE": 500,
"RANGE_WIDTH_PCT": Decimal("0.05"),
"TARGET_INVESTMENT_AMOUNT": 1000,
"HEDGE_STRATEGY": "BOTTOM",
"TARGET_INVESTMENT_AMOUNT": 3000,
"HEDGE_STRATEGY": "FIXED",
"RANGE_WIDTH_PCT": Decimal("0.0075"),
},
"UNISWAP_wide": {
"NAME": "Uniswap V3 (Arbitrum) - ETH/USDC Wide",
@ -129,6 +130,34 @@ CLP_PROFILES = {
"TARGET_INVESTMENT_AMOUNT": 200,
"VALUE_REFERENCE": "USD",
"RANGE_WIDTH_PCT": Decimal("0.10")
},
"AERODROME_BASE_CL": {
"NAME": "Aerodrome SlipStream (Base) - WETH/USDC",
"COIN_SYMBOL": "ETH",
"RPC_ENV_VAR": "BASE_RPC_URL",
"NPM_ADDRESS": "0x827922686190790b37229fd06084350E74485b72",
"ROUTER_ADDRESS": "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
"TOKEN_A_ADDRESS": "0x4200000000000000000000000000000000000006", # WETH
"TOKEN_B_ADDRESS": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
"WRAPPED_NATIVE_ADDRESS": "0x4200000000000000000000000000000000000006",
"POOL_FEE": 100, # TickSpacing 100 pool (0xb2cc...)
"RANGE_WIDTH_PCT": Decimal("0.075"),
"TARGET_INVESTMENT_AMOUNT": 200,
"HEDGE_STRATEGY": "FIXED",
},
"AERODROME_WETH-USDC_008": {
"NAME": "Aerodrome SlipStream (Base) - WETH/USDC Stable",
"COIN_SYMBOL": "ETH",
"RPC_ENV_VAR": "BASE_RPC_URL",
"NPM_ADDRESS": "0x827922686190790b37229fd06084350E74485b72",
"ROUTER_ADDRESS": "0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43",
"TOKEN_A_ADDRESS": "0x4200000000000000000000000000000000000006", # WETH
"TOKEN_B_ADDRESS": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
"WRAPPED_NATIVE_ADDRESS": "0x4200000000000000000000000000000000000006",
"POOL_FEE": 1, # TickSpacing 1 pool (0xdbc6...)
"RANGE_WIDTH_PCT": Decimal("0.05"),
"TARGET_INVESTMENT_AMOUNT": 200,
"HEDGE_STRATEGY": "FIXED",
}
}

View File

@ -14,20 +14,15 @@ current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir)
sys.path.append(project_root)
# Import local modules
try:
from logging_utils import setup_logging
except ImportError:
setup_logging = None
# Ensure root logger is clean if we can't use setup_logging
logging.getLogger().handlers.clear()
logging.basicConfig(level=logging.INFO)
# Ensure root logger is clean
logging.getLogger().handlers.clear()
logging.basicConfig(level=logging.INFO)
from eth_account import Account
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from hyperliquid.utils import constants
from clp_config import CLP_PROFILES, DEFAULT_STRATEGY
from clp_config import CLP_PROFILES, DEFAULT_STRATEGY, TARGET_DEX
# Load environment variables
dotenv_path = os.path.join(current_dir, '.env')
@ -233,7 +228,21 @@ class HyperliquidStrategy:
adj_pct = -norm_dist * max_boost
adj_pct = max(-max_boost, min(max_boost, adj_pct))
raw_target_short = pool_delta
# --- FIXED STRATEGY LOGIC ---
if strategy_type == "FIXED":
# Target is exactly the pool delta at entry price
raw_target_short = self.get_pool_delta(self.entry_price)
adj_pct = Decimal("0")
elif strategy_type == "BOTTOM":
if current_price > self.entry_price:
# Disable hedging in upper half
raw_target_short = Decimal("0")
adj_pct = Decimal("0")
else:
# Enable hedging in lower half (standard delta)
# No asymmetric boost applied
adj_pct = Decimal("0")
adjusted_target_short = raw_target_short * (Decimal("1.0") + adj_pct)
diff = adjusted_target_short - abs(current_short_size)
@ -275,13 +284,23 @@ class UnifiedHedger:
# Market Data Cache
self.last_prices = {}
self.price_history = {} # Symbol -> List[Decimal]
self.price_history = {} # Symbol -> List[Decimal] (Fast: 1s samples)
self.last_trade_times = {} # Symbol -> timestamp
self.last_idle_log_times = {} # Symbol -> timestamp
# Shadow Orders (Global List)
self.shadow_orders = []
# State: Emergency Close Hysteresis
# Map: (file_path, token_id) -> bool
self.emergency_close_active = {}
# Map: (file_path, token_id) -> Decimal (Locked hedge size)
self.custom_fixed_targets = {}
# Map: (file_path, token_id) -> Decimal (Price when hedge leg opened)
self.hedge_entry_prices = {}
self.startup_time = time.time()
logger.info(f"[CLP_HEDGER] Master Hedger initialized. Agent: {self.account.address}")
@ -289,6 +308,7 @@ class UnifiedHedger:
def _init_coin_configs(self):
"""Pre-load configuration for known coins from CLP_PROFILES."""
# 1. Load all profiles (order depends on dict iteration)
for profile_key, profile_data in CLP_PROFILES.items():
symbol = profile_data.get("COIN_SYMBOL")
if symbol:
@ -300,6 +320,18 @@ class UnifiedHedger:
# Update with Profile Specifics
self.coin_configs[symbol].update(profile_data)
# 2. Force overwrite with TARGET_DEX profile to ensure precedence
target_profile = CLP_PROFILES.get(TARGET_DEX)
if target_profile:
symbol = target_profile.get("COIN_SYMBOL")
if symbol:
if symbol not in self.coin_configs:
self.coin_configs[symbol] = DEFAULT_STRATEGY.copy()
self.coin_configs[symbol]["sz_decimals"] = 4
logger.info(f"Overwriting config for {symbol} using TARGET_DEX: {TARGET_DEX}")
self.coin_configs[symbol].update(target_profile)
def _get_sz_decimals(self, coin: str) -> int:
try:
meta = self.info.meta()
@ -430,6 +462,7 @@ class UnifiedHedger:
self.strategy_states[key]['pnl'] = to_decimal(entry.get('hedge_pnl_realized', 0))
self.strategy_states[key]['fees'] = to_decimal(entry.get('hedge_fees_paid', 0))
self.strategy_states[key]['status'] = entry.get('status', 'OPEN')
self.strategy_states[key]['clp_fees'] = to_decimal(entry.get('clp_fees', 0))
except Exception as e:
logger.error(f"Error reading {filename}: {e}. Skipping updates.")
@ -481,12 +514,23 @@ class UnifiedHedger:
"start_time": start_time_ms,
"pnl": to_decimal(position_data.get('hedge_pnl_realized', 0)),
"fees": to_decimal(position_data.get('hedge_fees_paid', 0)),
"clp_fees": to_decimal(position_data.get('clp_fees', 0)),
"hedge_TotPnL": to_decimal(position_data.get('hedge_TotPnL', 0)), # NEW: Total Closed PnL
"entry_price": entry_price, # Store for fishing logic
"status": position_data.get('status', 'OPEN')
}
# Initial hedge entry price is the CLP entry price
self.hedge_entry_prices[key] = entry_price
logger.info(f"[STRAT] Init {key[1]} ({coin_symbol}) | Range: {lower}-{upper}")
# Ensure JSON has these fields initialized
update_position_stats(key[0], key[1], {
"hedge_TotPnL": float(self.strategy_states[key]['hedge_TotPnL']),
"hedge_fees_paid": float(self.strategy_states[key]['fees'])
})
except Exception as e:
logger.error(f"Failed to init strategy {key[1]}: {e}")
@ -584,297 +628,6 @@ class UnifiedHedger:
except Exception as e:
logger.error(f"Failed to update closed PnL/Fees for {coin}: {e}")
def run(self):
logger.info("Starting Unified Hedger Loop...")
self.update_coin_decimals()
# --- LOG SETTINGS ON START ---
logger.info("=== HEDGER CONFIGURATION ===")
for symbol, config in self.coin_configs.items():
logger.info(f"--- {symbol} ---")
for k, v in config.items():
logger.info(f" {k}: {v}")
logger.info("============================")
def run_tick(self):
"""
Executes one iteration of the hedger logic.
"""
# 1. API Backoff
if time.time() < self.api_backoff_until:
return
# 2. Update Strategies
if not self.scan_strategies():
logger.warning("Strategy scan failed (read error). Skipping execution tick.")
return
# 3. Fetch Market Data (Centralized)
try:
mids = self.info.all_mids()
user_state = self.info.user_state(self.vault_address or self.account.address)
open_orders = self.get_open_orders()
l2_snapshots = {} # Cache for snapshots
except Exception as e:
logger.error(f"API Error fetching data: {e}")
return
# Map Open Orders
orders_map = {}
for o in open_orders:
c = o['coin']
if c not in orders_map: orders_map[c] = []
orders_map[c].append(o)
# Parse User State
account_value = Decimal("0")
if "marginSummary" in user_state and "accountValue" in user_state["marginSummary"]:
account_value = to_decimal(user_state["marginSummary"]["accountValue"])
# Map current positions
current_positions = {} # Coin -> Size
current_pnls = {} # Coin -> Unrealized PnL
current_entry_pxs = {} # Coin -> Entry Price (NEW)
for pos in user_state["assetPositions"]:
c = pos["position"]["coin"]
s = to_decimal(pos["position"]["szi"])
u = to_decimal(pos["position"]["unrealizedPnl"])
e = to_decimal(pos["position"]["entryPx"])
current_positions[c] = s
current_pnls[c] = u
current_entry_pxs[c] = e
# 4. Aggregate Targets
# Coin -> { 'target_short': Decimal, 'contributors': int, 'is_at_edge': bool }
aggregates = {}
# First, update all prices from mids for active coins
for coin in self.active_coins:
if coin in mids:
price = to_decimal(mids[coin])
self.last_prices[coin] = price
# Update Price History
if coin not in self.price_history: self.price_history[coin] = []
self.price_history[coin].append(price)
if len(self.price_history[coin]) > 300: self.price_history[coin].pop(0)
for key, strat in self.strategies.items():
coin = self.strategy_states[key]['coin']
status = self.strategy_states[key].get('status', 'OPEN')
if coin not in self.last_prices: continue
price = self.last_prices[coin]
# Get Config & Strategy Type
config = self.coin_configs.get(coin, {})
strategy_type = config.get("HEDGE_STRATEGY", "ASYMMETRIC")
# Calc Logic
calc = strat.calculate_rebalance(price, Decimal("0"), strategy_type)
if coin not in aggregates:
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
logger.info(f"[STRAT] {key[1]} is CLOSING -> Force Target 0")
aggregates[coin]['is_closing'] = True
# Do not add to target_short
else:
aggregates[coin]['target_short'] += calc['target_short']
aggregates[coin]['contributors'] += 1
aggregates[coin]['adj_pct'] = calc['adj_pct']
# Check Edge Proximity for Cleanup
config = self.coin_configs.get(coin, {})
enable_cleanup = config.get("ENABLE_EDGE_CLEANUP", True)
cleanup_margin = config.get("EDGE_CLEANUP_MARGIN_PCT", Decimal("0.02"))
if enable_cleanup:
dist_bottom_pct = (price - strat.low_range) / strat.low_range
dist_top_pct = (strat.high_range - price) / strat.high_range
range_width_pct = (strat.high_range - strat.low_range) / strat.low_range
safety_margin_pct = range_width_pct * cleanup_margin
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)
# 5. Execute Per Coin
# Union of coins with Active Strategies OR Active Positions
coins_to_process = set(aggregates.keys())
for c, pos in current_positions.items():
if abs(pos) > 0: coins_to_process.add(c)
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"))
if price == 0: continue
target_short_abs = data['target_short']
target_position = -target_short_abs
current_pos = current_positions.get(coin, Decimal("0"))
diff = target_position - current_pos
diff_abs = abs(diff)
# Thresholds
config = self.coin_configs.get(coin, {})
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)
if data['is_at_edge'] and config.get("ENABLE_EDGE_CLEANUP", True):
if dynamic_thresh > min_thresh: dynamic_thresh = min_thresh
action_needed = diff_abs > dynamic_thresh
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
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']
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)
order_age_sec = (int(time.time()*1000) - o_timestamp) / 1000.0
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
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
if is_same_side and (abs(price - o_price) / price) < price_buffer_pct:
order_matched = True
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)
# 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
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
# --- ASYMMETRIC HEDGE CHECK ---
is_asymmetric_blocked = False
p_mid_asym = Decimal("0")
strategy_type = config.get("HEDGE_STRATEGY", "ASYMMETRIC")
if strategy_type == "ASYMMETRIC" and 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)
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)})
logger.info("Sleeping 10s for position update...")
time.sleep(10)
self._update_closed_pnl(coin)
else:
# 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:
logger.info(f"[IDLE] {coin} | Px: {price:.2f} | delta: {target_position:.4f} | Diff: {diff:.4f}")
def run(self):
logger.info("Starting Unified Hedger Loop...")
self.update_coin_decimals()
@ -889,7 +642,411 @@ class UnifiedHedger:
while True:
try:
self.run_tick()
# 1. API Backoff
if time.time() < self.api_backoff_until:
time.sleep(1)
continue
# 2. Update Strategies
if not self.scan_strategies():
logger.warning("Strategy scan failed (read error). Skipping execution tick.")
time.sleep(1)
continue
# 3. Fetch Market Data (Centralized)
try:
mids = self.info.all_mids()
user_state = self.info.user_state(self.vault_address or self.account.address)
open_orders = self.get_open_orders()
l2_snapshots = {} # Cache for snapshots
except Exception as e:
logger.error(f"API Error fetching data: {e}")
time.sleep(1)
continue
# Map Open Orders
orders_map = {}
for o in open_orders:
c = o['coin']
if c not in orders_map: orders_map[c] = []
orders_map[c].append(o)
# Parse User State
account_value = Decimal("0")
if "marginSummary" in user_state and "accountValue" in user_state["marginSummary"]:
account_value = to_decimal(user_state["marginSummary"]["accountValue"])
# Map current positions
current_positions = {} # Coin -> Size
current_pnls = {} # Coin -> Unrealized PnL
current_entry_pxs = {} # Coin -> Entry Price (NEW)
for pos in user_state["assetPositions"]:
c = pos["position"]["coin"]
s = to_decimal(pos["position"]["szi"])
u = to_decimal(pos["position"]["unrealizedPnl"])
e = to_decimal(pos["position"]["entryPx"])
current_positions[c] = s
current_pnls[c] = u
current_entry_pxs[c] = e
# 4. Aggregate Targets
# Coin -> { 'target_short': Decimal, 'contributors': int, 'is_at_edge': bool }
aggregates = {}
# First, update all prices from mids for active coins
for coin in self.active_coins:
if coin in mids:
price = to_decimal(mids[coin])
self.last_prices[coin] = price
# Update Price History (Fast)
if coin not in self.price_history: self.price_history[coin] = []
self.price_history[coin].append(price)
if len(self.price_history[coin]) > 300: self.price_history[coin].pop(0)
for key, strat in self.strategies.items():
coin = self.strategy_states[key]['coin']
status = self.strategy_states[key].get('status', 'OPEN')
if coin not in self.last_prices: continue
price = self.last_prices[coin]
# Get Config & Strategy Type
config = self.coin_configs.get(coin, {})
strategy_type = config.get("HEDGE_STRATEGY", "ASYMMETRIC")
# Calc Logic
calc = strat.calculate_rebalance(price, Decimal("0"), strategy_type)
if coin not in aggregates:
aggregates[coin] = {'target_short': Decimal("0"), 'contributors': 0, 'is_at_edge': False, 'is_at_bottom_edge': False, 'adj_pct': Decimal("0"), 'is_closing': False}
# --- EMERGENCY UPPER EDGE CLOSING (HYSTERESIS) ---
# Logic: If price hits Top, close hedge. Do NOT re-open until price drops back to 75% of Range (FIXED) or Buffer (Others).
is_active_hysteresis = self.emergency_close_active.get(key, False)
if is_active_hysteresis:
# CHECK RESET CONDITION
if strategy_type == "FIXED":
# Reset at 75% of range (from Bottom)
range_width = strat.high_range - strat.low_range
reset_threshold = strat.low_range + (range_width * Decimal("0.75"))
else:
reset_threshold = strat.high_range * Decimal("0.999")
if price < reset_threshold:
logger.info(f"[STRAT] {key[1]} Price reset ({price:.2f} < {reset_threshold:.2f}). Resuming hedge.")
self.emergency_close_active[key] = False
is_active_hysteresis = False
# Capture NEW Dynamic Fixed Target and Entry Price
if strategy_type == "FIXED":
dynamic_delta = strat.get_pool_delta(price)
self.custom_fixed_targets[key] = dynamic_delta
self.hedge_entry_prices[key] = price
logger.info(f"[STRAT] {key[1]} FIXED target reset to Dynamic Delta: {dynamic_delta:.4f} @ {price:.2f}")
if not is_active_hysteresis:
# CHECK TRIGGER CONDITION
if price >= strat.high_range:
logger.warning(f"[STRAT] {key[1]} above High Range ({price:.2f} >= {strat.high_range:.2f}). Emergency closing hedge.")
self.emergency_close_active[key] = True
is_active_hysteresis = True
# Reset entry price when closed
self.hedge_entry_prices[key] = Decimal("0")
if status == 'CLOSING' or is_active_hysteresis:
# If Closing OR Hysteresis Active, target is 0
aggregates[coin]['is_closing'] = True
else:
# Use custom fixed target if exists, else standard calc
if strategy_type == "FIXED" and key in self.custom_fixed_targets:
aggregates[coin]['target_short'] += self.custom_fixed_targets[key]
else:
aggregates[coin]['target_short'] += calc['target_short']
aggregates[coin]['contributors'] += 1
aggregates[coin]['adj_pct'] = calc['adj_pct']
# Check Edge Proximity for Cleanup
config = self.coin_configs.get(coin, {})
enable_cleanup = config.get("ENABLE_EDGE_CLEANUP", True)
cleanup_margin = config.get("EDGE_CLEANUP_MARGIN_PCT", Decimal("0.02"))
if enable_cleanup:
dist_bottom_pct = (price - strat.low_range) / strat.low_range
dist_top_pct = (strat.high_range - price) / strat.high_range
range_width_pct = (strat.high_range - strat.low_range) / strat.low_range
safety_margin_pct = range_width_pct * cleanup_margin
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)
# 5. Execute Per Coin
# Union of coins with Active Strategies OR Active Positions
coins_to_process = set(aggregates.keys())
for c, pos in current_positions.items():
if abs(pos) > 0: coins_to_process.add(c)
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"))
if price == 0: continue
target_short_abs = data['target_short']
target_position = -target_short_abs
current_pos = current_positions.get(coin, Decimal("0"))
diff = target_position - current_pos
diff_abs = abs(diff)
# Thresholds
config = self.coin_configs.get(coin, {})
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)
if data['is_at_edge'] and config.get("ENABLE_EDGE_CLEANUP", True):
if dynamic_thresh > min_thresh: dynamic_thresh = min_thresh
action_needed = diff_abs > dynamic_thresh
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
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']
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)
order_age_sec = (int(time.time()*1000) - o_timestamp) / 1000.0
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
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
if is_same_side and (abs(price - o_price) / price) < price_buffer_pct:
order_matched = True
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)
# 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
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
# --- BOTTOM STRATEGY SAFEGUARDS ---
strategy_type = config.get("HEDGE_STRATEGY", "ASYMMETRIC")
if strategy_type == "BOTTOM":
# strict: "do not use taker orders... except only on very bottom"
if not data.get('is_at_bottom_edge', False):
bypass_cooldown = False
force_taker_retry = False # Disable taker retry from fishing
# --- ASYMMETRIC HEDGE CHECK ---
is_asymmetric_blocked = False
p_mid_asym = Decimal("0")
# strategy_type already fetched above
if strategy_type == "ASYMMETRIC" and 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)
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)})
logger.info("Sleeping 10s for position update...")
time.sleep(10)
self._update_closed_pnl(coin)
else:
# Idle Cleanup
if existing_orders and not order_matched:
for o in existing_orders: self.cancel_order(coin, o['oid'])
# --- REAL-TIME PnL CALCULATION & JSON UPDATE (1s) ---
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
# Update all active strategies for this coin in JSON
if total_L_log > 0 and price > 0:
for k_strat, strat_inst in self.strategies.items():
if self.strategy_states[k_strat]['coin'] != coin: continue
# CLP Value Calc
def get_clp_value(p, s):
if p <= s.low_range: return s.L * (p * (1/s.low_range.sqrt() - 1/s.high_range.sqrt()))
if p >= s.high_range: return s.L * (s.high_range.sqrt() - s.low_range.sqrt())
return s.L * (2*p.sqrt() - s.low_range.sqrt() - p/s.high_range.sqrt())
clp_curr_val = get_clp_value(price, strat_inst)
# Use Custom Fixed Target if exists
target_size = self.custom_fixed_targets.get(k_strat, strat_inst.get_pool_delta(strat_inst.entry_price))
# USE TRACKED HEDGE ENTRY PRICE
h_entry_px = self.hedge_entry_prices.get(k_strat, strat_inst.entry_price)
if h_entry_px > 0:
hedge_pnl_curr = (h_entry_px - price) * target_size
else:
hedge_pnl_curr = Decimal("0")
fee_close_curr = (target_size * price) * Decimal("0.000432")
uni_fees = to_decimal(self.strategy_states[k_strat].get('clp_fees', 0))
# Retrieve Realized PnL & Fees from State
realized_pnl = to_decimal(self.strategy_states[k_strat].get('hedge_TotPnL', 0))
realized_fees = to_decimal(self.strategy_states[k_strat].get('fees', 0))
# Combined TotPnL = CLP_Unrealized + Hedge_Unrealized + Hedge_Realized - Hedge_Fees + CLP_Fees - Est_Close_Fee
tot_curr = (clp_curr_val - strat_inst.target_value) + hedge_pnl_curr + realized_pnl - realized_fees - fee_close_curr + uni_fees
cur_hl_cost = realized_fees + fee_close_curr
# Sync to JSON every 1s
update_position_stats(k_strat[0], k_strat[1], {
"combined_TotPnL": round(float(tot_curr), 2),
"hedge_HL_cost_est": round(float(cur_hl_cost), 2),
"hedge_pnl_unrealized": round(float(hedge_pnl_curr), 2),
"last_sync_hl": int(time.time())
})
# --- THROTTLED STATUS LOGGING (300s) ---
now = time.time()
last_log = self.last_idle_log_times.get(coin, 0)
log_interval = config.get("LOG_INTERVAL_SECONDS", 300)
if now - last_log >= log_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")
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)
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
# Log individual strategy PnL
if strategy_type == "FIXED":
for k_strat, strat_inst in self.strategies.items():
if self.strategy_states[k_strat]['coin'] != coin: continue
# Recalculate for logging (including bounds)
clp_curr_val = get_clp_value(price, strat_inst)
clp_low_val = get_clp_value(strat_inst.low_range, strat_inst)
clp_high_val = get_clp_value(strat_inst.high_range, strat_inst)
# Use Custom Fixed Target if exists
target_size = self.custom_fixed_targets.get(k_strat, strat_inst.get_pool_delta(strat_inst.entry_price))
h_entry_px = self.hedge_entry_prices.get(k_strat, strat_inst.entry_price)
if h_entry_px > 0:
hedge_pnl_curr = (h_entry_px - price) * target_size
hedge_pnl_low = (h_entry_px - strat_inst.low_range) * target_size
hedge_pnl_high = (h_entry_px - strat_inst.high_range) * target_size
fee_open = (target_size * h_entry_px) * Decimal("0.000144")
else:
hedge_pnl_curr = hedge_pnl_low = hedge_pnl_high = Decimal("0")
fee_open = Decimal("0")
fee_close_curr = (target_size * price) * Decimal("0.000432")
fee_close_low = (target_size * strat_inst.low_range) * Decimal("0.000432")
fee_close_high = (target_size * strat_inst.high_range) * Decimal("0.000432")
uni_fees = to_decimal(self.strategy_states[k_strat].get('clp_fees', 0))
tot_curr = (clp_curr_val - strat_inst.target_value) + hedge_pnl_curr - (fee_open + fee_close_curr) + uni_fees
tot_low = (clp_low_val - strat_inst.target_value) + hedge_pnl_low - (fee_open + fee_close_low) + uni_fees
tot_high = (clp_high_val - strat_inst.target_value) + hedge_pnl_high - (fee_open + fee_close_high) + uni_fees
cur_hl_cost = fee_open + fee_close_curr
# ID or Range to distinguish
strat_id = str(k_strat[1]) # Token ID
logger.info(f"[FIXED] {coin} #{strat_id} | TotPnL: {tot_curr:+.2f} | Down: {tot_low:+.2f} | Up: {tot_high:+.2f} (Inc: Fees ${uni_fees:.2f}, HL Cost ${cur_hl_cost:.2f})")
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} | HedgePnL: {total_pnl:.2f}")
else:
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))
except KeyboardInterrupt:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,103 @@
# Aerodrome Slipstream (CLP) Integration Guide
This document details the specific technical requirements for integrating **Aerodrome Slipstream** (Concentrated Liquidity) pools into a Uniswap V3-compatible bot. Aerodrome Slipstream is a fork of Uniswap V3 (via Velodrome V2) but introduces critical ABI and logic changes that cause standard implementations to fail.
## 1. Key Differences from Uniswap V3
| Feature | Standard Uniswap V3 | Aerodrome Slipstream |
| :--- | :--- | :--- |
| **Factory Pool Lookup** | `getPool(tokenA, tokenB, fee)` | `getPool(tokenA, tokenB, tickSpacing)` |
| **NPM Mint Parameter** | `uint24 fee` | `int24 tickSpacing` |
| **NPM Mint Struct** | `MintParams { ..., deadline }` | `MintParams { ..., deadline, sqrtPriceX96 }` |
| **Pool Identification** | Fee Tier (e.g., 500, 3000) | Tick Spacing (e.g., 1, 100) |
## 2. ABI Modifications
To interact with the Aerodrome `NonfungiblePositionManager` (NPM), you must use a modified ABI. The standard Uniswap V3 NPM ABI will result in revert errors during encoding.
### MintParams Struct
The `MintParams` struct in the `mint` function input must be defined as follows:
```json
{
"components": [
{"internalType": "address", "name": "token0", "type": "address"},
{"internalType": "address", "name": "token1", "type": "address"},
{"internalType": "int24", "name": "tickSpacing", "type": "int24"}, // CHANGED from uint24 fee
{"internalType": "int24", "name": "tickLower", "type": "int24"},
{"internalType": "int24", "name": "tickUpper", "type": "int24"},
{"internalType": "uint256", "name": "amount0Desired", "type": "uint256"},
{"internalType": "uint256", "name": "amount1Desired", "type": "uint256"},
{"internalType": "uint256", "name": "amount0Min", "type": "uint256"},
{"internalType": "uint256", "name": "amount1Min", "type": "uint256"},
{"internalType": "address", "name": "recipient", "type": "address"},
{"internalType": "uint256", "name": "deadline", "type": "uint256"},
{"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"} // ADDED
],
"internalType": "struct INonfungiblePositionManager.MintParams",
"name": "params",
"type": "tuple"
}
```
## 3. Python Implementation Strategy
### A. Configuration
When defining the pool configuration, use the **Tick Spacing** value (e.g., 100) where you would normally put the Fee.
```python
"AERODROME_BASE_CL": {
"NAME": "Aerodrome SlipStream (Base) - WETH/USDC",
"NPM_ADDRESS": "0x827922686190790b37229fd06084350E74485b72", # Aerodrome NPM
"POOL_FEE": 100, # Actual TickSpacing (e.g., 100 for volatile, 1 for stable)
...
}
```
### B. Logic Changes (`clp_manager.py`)
1. **ABI Selection:** Dynamically switch between Standard and Aerodrome ABIs based on the target DEX.
2. **Parameter Construction:** When calling `mint`, check if the target is Aerodrome. If so, append `0` (for `sqrtPriceX96`) to the arguments tuple.
```python
# Pseudo-code for Mint Call
base_params = [
token0, token1,
tick_spacing, # Passed as int24
tick_lower, tick_upper,
amount0, amount1,
amount0_min, amount1_min,
recipient,
deadline
]
if is_aerodrome:
base_params.append(0) # sqrtPriceX96 must be present and 0 for existing pools
npm_contract.functions.mint(tuple(base_params)).transact(...)
```
### C. Swap Router
Aerodrome Slipstream's **SwapRouter** (`0xbe6D...`) uses the `SwapRouter01` ABI style (includes `deadline` in `ExactInputSingleParams`), whereas standard Uniswap V3 on Base often uses `SwapRouter02` (no deadline).
* **Tip:** For simplicity, you can use the **Standard Uniswap V3 Router** (`0x2626...`) on Base to swap tokens (WETH/USDC) even if you are providing liquidity on Aerodrome, provided the tokens are standard. This avoids ABI headaches with the Aerodrome Router if you only need simple swaps.
## 4. Troubleshooting Common Errors
* **`('execution reverted', 'no data')`**:
* **Cause 1:** Passing `fee` (uint24) instead of `tickSpacing` (int24).
* **Cause 2:** Missing `sqrtPriceX96` parameter in the struct.
* **Cause 3:** Tick range (`tickLower`, `tickUpper`) not aligned to the pool's `tickSpacing` (e.g., must be multiples of 100).
* **`BadFunctionCallOutput` / `InsufficientDataBytes`**:
* **Cause:** Using the wrong Contract Address for the chain (e.g., using Mainnet NPM address on Base).
* **Cause:** Calling `factory()` on a contract that doesn't have it (wrong ABI or Proxy).
## 5. Addresses (Base)
| Contract | Address |
| :--- | :--- |
| **Aerodrome NPM** | `0x827922686190790b37229fd06084350E74485b72` |
| **Aerodrome Factory** | `0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A` |
| **Uniswap V3 NPM** | `0xC36442b4a4522E871399CD717aBDD847Ab11FE88` |
| **WETH** | `0x4200000000000000000000000000000000000006` |
| **USDC** | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |

View File

@ -0,0 +1,66 @@
# Interpreting CLP Pool Data
This guide explains how to read and use the data generated by the **Pool Scanner** and **Analyzer** tools to select the most profitable liquidity pools.
## 1. The Data Pipeline
1. **Scanner (`pool_scanner.py`):** Runs continuously. Connects to the blockchain every 10 minutes and records the "Heartbeat" of each pool:
* **Tick:** The current price tick.
* **FeeGrowthGlobal:** A cumulative counter of all fees earned by the pool since inception.
2. **Analyzer (`analyze_pool_data.py`):** Runs on demand. It "replays" the history recorded by the scanner to simulate how a specific strategy (e.g., $10k investment, +/- 10% range) would have performed.
## 2. Reading the Analyzer Report
When you run `python tools/analyze_pool_data.py`, you get a table like this:
```text
=== POOL PERFORMANCE REPORT ===
Pool Duration Rebalances Final Equity (Est) ROI %
Uniswap V3 (Base) - WETH/USDC 1 days 2 10050.00 0.50
Aerodrome SlipStream (Base) - WETH/USDC 1 days 0 10010.00 0.10
```
### Key Metrics
* **Duration:** How long the scanner has been tracking this pool. Longer duration = more reliable data.
* **Rebalances:** How many times the price went **Out of Range** (+/- 10%) during this period.
* **Low is Good:** Means the price is stable relative to your range. Less work for the bot, fewer fees paid.
* **High is Bad:** Means the pool is volatile. You are paying frequent swap/gas fees to move your range.
* **Final Equity (Est):** Your simulated $10,000 starting capital after:
* (+) Adding estimated Fee Income.
* (-) Subtracting Rebalance Costs (0.1% per rebalance).
* (+/-) Asset Value Change (Impermanent Loss is inherently captured because we track value in USD).
* **ROI %:** The return on investment for the duration.
* `0.50%` in 1 day approx `180%` APR (compounded).
## 3. Selecting a Pool
Use the report to find the "Sweet Spot":
| Scenario | Verdict |
| :--- | :--- |
| **High Fees, Low Rebalances** | **🥇 BEST.** The ideal pool. Price stays in range, volume is high. |
| **High Fees, High Rebalances** | **⚠️ RISKY.** You earn a lot, but you burn a lot on swaps/gas. Net profit might be lower than expected. |
| **Low Fees, Low Rebalances** | **😴 SAFE.** Good for "set and forget," but returns are meager. |
| **Low Fees, High Rebalances** | **❌ AVOID.** You will lose money rebalancing a chop-heavy pool with no volume. |
## 4. Advanced: Raw Data (`pool_history.csv`)
If you open the CSV directly, you will see columns like `feeGrowthGlobal0X128`.
* **Fee Growth:** This number ONLY goes up.
* **Speed of Growth:** The faster this number increases, the higher the trading volume (and APR) of the pool.
* **Tick:**
* `Price = 1.0001 ^ Tick`
* Stable Tick = Low Volatility.
## 5. Simulation Logic
The Analyzer assumes:
1. **Initial Investment:** $10,000 USD.
2. **Strategy:** Active Management (Auto-Rebalance).
3. **Range:** +/- 10% (Configurable in script).
4. **Cost:** 0.1% of capital per rebalance (Swap fee + Gas estimate).
5. **Fee Accrual:** You ONLY earn fees when the recorded tick is inside your virtual range.
*Note: This is a "Paper Trading" simulation. Real-world slippage and exact execution timing may vary.*

View File

@ -0,0 +1,93 @@
# Security & Optimization Protocols
This document details the safety mechanisms, entry guardrails, and optimization strategies implemented in the Uniswap Auto CLP & Hedger system.
## 1. CLP Manager: Entry & Position Safety
The `clp_manager.py` module is responsible for opening and managing Concentrated Liquidity Positions (CLP). It implements strict checks to ensure positions are only opened under favorable conditions.
### A. Oracle Guard Rail (Anti-Manipulation)
**Goal:** Prevent entering a pool that is actively being manipulated (Flash Loans) or has momentarily de-pegged.
* **Mechanism:** Before any calculation, the bot fetches the **Real-Time Mid Price** directly from the Hyperliquid API.
* **Logic:**
* `Pool Price` is calculated from the on-chain `sqrtPriceX96`.
* `Oracle Price` is fetched from Hyperliquid (`https://api.hyperliquid.xyz/info`).
* **Rule:** If `abs(Pool - Oracle) / Oracle > 0.25%`, the bot **ABORTS** the entry.
* **Benefit:** Protects against entering at a "fake" price which would lead to instant arbitrage loss (LVR).
### B. Stale Tick Protection (Volatility Guard)
**Goal:** Prevent entering a position if the price moves significantly during the transaction setup phase (e.g., while swapping tokens).
* **Mechanism:**
1. The bot calculates the target tick range at $T_0$.
2. It performs necessary token swaps (e.g., USDC -> WETH) which takes time ($T_1$).
3. **Critical Check:** Just before minting ($T_2$), it re-fetches the current pool tick.
* **Rule:** If `abs(Tick_T0 - Tick_T2) > 13 ticks` (approx 0.13%), the bot **ABORTS** the mint transaction.
* **Benefit:** Ensures the range is centered on the *actual* execution price, not an outdated one.
### C. Post-Mint Accuracy
**Goal:** Ensure the "Entry Price" recorded for the Hedger is 100% accurate.
* **Mechanism:** Immediately after the Mint transaction is confirmed on-chain, the bot fetches the pool state *one more time*.
* **Benefit:** Captures the exact price impact of the user's own liquidity insertion, preventing discrepancies in the Hedger's PnL calculations.
### D. Safe Entry Zones (AUTO Mode)
**Goal:** Only enter when mean reversion is statistically likely.
* **Mechanism:**
* **Bollinger Bands (BB):** Price must be inside the 12h BB.
* **Moving Average (MA):** The MA88 must also be inside the 12h BB.
* **Benefit:** Avoids opening positions during breakout trends or extreme volatility expansion.
### E. Manual Override (Force Mode)
**Goal:** Allow operator intervention for testing or recovery.
* **Command:** `python clp_manager.py --force <width>` (e.g., 0.95).
* **Behavior:** Bypasses Oracle, BB, and MA checks for the **first** position only. Automatically disables itself after one successful mint.
---
## 2. CLP Hedger: Risk Management
The `clp_hedger.py` module manages the Delta-Neutral hedge on Hyperliquid.
### A. Emergency Edge Closure (Stop-Loss)
**Goal:** Prevent indefinite hedging losses if the price breaks out of the LP range violently.
* **Trigger:** If `Price >= Range Upper Bound`.
* **Action:** Immediately **CLOSE** the short hedge position.
* **Benefit:** Stops the strategy from "selling low and buying high" (hedging) when the LP position is already out of range and effectively essentially 100% stablecoin (impermanent loss realized).
### B. Hysteresis Reset
**Goal:** Prevent "whipsaw" losses (opening/closing repeatedly) if the price hovers exactly at the edge.
* **Logic:** Once an Emergency Closure triggers, the hedge does **not** re-open immediately if the price dips slightly back in.
* **Reset Condition:** Price must drop back to a "Safe Zone" (e.g., 75% of the range width or a specific buffer below the edge).
* **Benefit:** Filters out noise at the range boundaries.
### C. Asymmetric Compensation (EAC)
**Goal:** Reduce the "Buy High/Sell Low" churn near the edges of the range.
* **Mechanism:** The target hedge delta is adjusted (reduced) as the price approaches the boundaries.
* **Logic:** `Target Hedge = Pool Delta * (1 - Proximity_Factor)`.
* **Benefit:** Softens the impact of entering/exiting the range, preserving capital.
### D. Fishing Orders (Maker Rebates)
**Goal:** Reduce execution costs.
* **Mechanism:** Instead of market dumping (Taker fee ~0.035%), the bot places Limit Orders (Maker) slightly away from the spread.
* **Logic:** If the price moves favorably, the order fills, earning a rebate (or paying 0 fee). If not filled within `TIMEOUT`, it falls back to a Taker order if the delta drift is critical.
---
## 3. Future Improvements (Roadmap)
### A. WebSocket Integration
* **Current:** Polling REST API every 30-300s (or 1s for guard rails).
* **Upgrade:** Implement a persistent WebSocket connection to Hyperliquid and the EVM RPC.
* **Benefit:** Sub-100ms reaction times to volatility events.
### B. Multi-Chain Arbitrage Check
* **Current:** Checks Hyperliquid vs Pool.
* **Upgrade:** Check Binance/Coinbase prices.
* **Benefit:** Detects on-chain lag before it happens (CEX leads DEX).
### C. Dynamic Fee Optimization
* **Current:** Fixed `POOL_FEE`.
* **Upgrade:** Automatically switch between 0.05% and 0.01% pools based on volatility and volume metrics.
### D. Smart Rebalance (Inventory Management)
* **Current:** Swaps surplus tokens using 1inch/Universal Router.
* **Upgrade:** Use CowSwap or CoW intents for MEV-protected rebalancing of large inventory imbalances.

View File

@ -0,0 +1,107 @@
import os
import json
import pandas as pd
import math
from decimal import Decimal
from datetime import datetime, timedelta
# --- SETTINGS ---
HISTORY_FILE = os.path.join("market_data", "pool_history.csv")
INVESTMENT_USD = 10000
RANGE_WIDTH_PCT = 0.10 # +/- 10%
REBALANCE_COST_PCT = 0.001 # 0.1% fee for rebalancing (swaps + gas)
def tick_to_price(tick):
return 1.0001 ** tick
def get_delta_from_pct(pct):
# tick_delta = log(1+pct) / log(1.0001)
return int(math.log(1 + pct) / math.log(1.0001))
def analyze():
if not os.path.exists(HISTORY_FILE):
print("No history file found. Run pool_scanner.py first.")
return
df = pd.read_csv(HISTORY_FILE)
df['timestamp'] = pd.to_datetime(df['timestamp'])
pools = df['pool_name'].unique()
results = []
for pool in pools:
pdf = df[df['pool_name'] == pool].sort_values('timestamp').copy()
if len(pdf) < 2: continue
# Initial Setup
start_row = pdf.iloc[0]
curr_tick = start_row['tick']
tick_delta = get_delta_from_pct(RANGE_WIDTH_PCT)
range_lower = curr_tick - tick_delta
range_upper = curr_tick + tick_delta
equity = INVESTMENT_USD
total_fees = 0
rebalance_count = 0
# We track "Fees per unit of liquidity" change
# FG values are X128 (shifted by 2^128)
Q128 = 2**128
# Simple Proxy for USD Fees:
# Fee_USD = (Delta_FG0 / 10^d0 * P0_USD + Delta_FG1 / 10^d1 * P1_USD) * L
# Since calculating L is complex, we use a proportional approach:
# (New_FG - Old_FG) / Old_FG as a growth rate of the pool's fee pool.
for i in range(1, len(pdf)):
row = pdf.iloc[i]
prev = pdf.iloc[i-1]
p_tick = row['tick']
# 1. Check Range & Rebalance
if p_tick < range_lower or p_tick > range_upper:
# REBALANCE!
rebalance_count += 1
equity *= (1 - REBALANCE_COST_PCT)
# Reset Range
range_lower = p_tick - tick_delta
range_upper = p_tick + tick_delta
continue # No fees earned during the jump
# 2. Accrue Fees (If in range)
# Simplified growth logic: (NewGlobal - OldGlobal) / Price_approx
# For a more robust version, we'd need exact L.
# Here we track the delta of the raw FG counters.
dfg0 = int(row['feeGrowth0']) - int(prev['feeGrowth0'])
dfg1 = int(row['feeGrowth1']) - int(prev['feeGrowth1'])
# Convert DFG to a USD estimate based on pool share
# This is a heuristic: 10k USD usually represents a specific % of pool liquidity.
# We assume a fixed liquidity L derived from 10k at start.
# L = 10000 / (sqrt(P) - sqrt(Pa)) ...
# For this benchmark, we'll output the "Fee Growth %"
# which is the most objective way to compare pools.
# (Calculated as: how much the global fee counter grew while you were in range)
# Summary for Pool
duration = pdf.iloc[-1]['timestamp'] - pdf.iloc[0]['timestamp']
results.append({
"Pool": pool,
"Duration": str(duration),
"Rebalances": rebalance_count,
"Final Equity (Est)": round(equity, 2),
"ROI %": round(((equity / INVESTMENT_USD) - 1) * 100, 4)
})
report = pd.DataFrame(results)
print("\n=== POOL PERFORMANCE REPORT ===")
print(report.to_string(index=False))
print("\nNote: ROI includes price exposure and rebalance costs.")
if __name__ == "__main__":
analyze()

View File

@ -0,0 +1,171 @@
import json
import time
import requests
import math
import os
from datetime import datetime
from statistics import mean, stdev
# --- Configuration ---
COINS = ["ETH"]
# Mapping of label to number of 1-minute periods
PERIODS_CONFIG = {
"37m": 37,
"3h": 3 * 60, # 180 minutes
"12h": 12 * 60, # 720 minutes
"24h": 24 * 60 # 1440 minutes
}
MA_PERIODS = [33, 44, 88, 144]
STD_DEV_MULTIPLIER = 1.6 # Standard deviation multiplier for bands
OUTPUT_FILE = os.path.join("market_data", "indicators.json")
API_URL = "https://api.hyperliquid.xyz/info"
UPDATE_INTERVAL = 60 # seconds
def fetch_candles(coin, interval="1m", lookback_minutes=1500):
"""
Fetches candle data from Hyperliquid.
We need at least enough candles for the longest period (1440).
Requesting slightly more to be safe.
"""
# Calculate startTime: now - (lookback_minutes * 60 * 1000)
# Hyperliquid expects startTime in milliseconds
end_time = int(time.time() * 1000)
start_time = end_time - (lookback_minutes * 60 * 1000)
payload = {
"type": "candleSnapshot",
"req": {
"coin": coin,
"interval": interval,
"startTime": start_time,
"endTime": end_time
}
}
try:
response = requests.post(API_URL, json=payload, timeout=10)
response.raise_for_status()
data = response.json()
# Data format is typically a list of dicts:
# {'t': 170..., 'T': 170..., 's': 'ETH', 'i': '1m', 'o': '...', 'c': '...', 'h': '...', 'l': '...', 'v': '...', 'n': ...}
# We need closing prices 'c'
candles = []
for c in data:
try:
# Ensure we parse 'c' (close) as float
candles.append(float(c['c']))
except (ValueError, KeyError):
continue
return candles
except Exception as e:
print(f"Error fetching candles for {coin}: {e}")
return []
def calculate_ma(prices, period):
"""Calculates Simple Moving Average."""
if len(prices) < period:
return None
return mean(prices[-period:])
def calculate_bb(prices, period, num_std_dev=2.0):
"""
Calculates Bollinger Bands for the LAST 'period' items in prices.
Returns {mid, upper, lower} or None if insufficient data.
"""
if len(prices) < period:
return None
# Take the last 'period' prices
window = prices[-period:]
try:
avg = mean(window)
# Population stdev or sample stdev? Usually sample (stdev) is used in finance or pandas default
if period > 1:
sd = stdev(window)
else:
sd = 0.0
upper = avg + (num_std_dev * sd)
lower = avg - (num_std_dev * sd)
return {
"mid": avg,
"upper": upper,
"lower": lower,
"std": sd
}
except Exception as e:
print(f"Error calculating BB: {e}")
return None
def main():
print(f"Starting Market Data Calculator for {COINS}")
print(f"BB Periods: {PERIODS_CONFIG}")
print(f"MA Periods: {MA_PERIODS}")
print(f"Output: {OUTPUT_FILE}")
# Ensure directory exists
os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)
while True:
try:
results = {
"last_updated": datetime.now().isoformat(),
"config": {
"std_dev_multiplier": STD_DEV_MULTIPLIER,
"ma_periods": MA_PERIODS
},
"data": {}
}
# Find the max needed history (BB vs MA)
max_bb = max(PERIODS_CONFIG.values()) if PERIODS_CONFIG else 0
max_ma = max(MA_PERIODS) if MA_PERIODS else 0
fetch_limit = max(max_bb, max_ma) + 60
for coin in COINS:
print(f"Fetching data for {coin}...", end="", flush=True)
prices = fetch_candles(coin, lookback_minutes=fetch_limit)
if not prices:
print(" Failed.")
continue
print(f" Got {len(prices)} candles.", end="", flush=True)
coin_results = {
"current_price": prices[-1] if prices else 0,
"bb": {},
"ma": {}
}
# Calculate BB
for label, period in PERIODS_CONFIG.items():
bb = calculate_bb(prices, period, num_std_dev=STD_DEV_MULTIPLIER)
coin_results["bb"][label] = bb if bb else "Insufficient Data"
# Calculate MA
for period in MA_PERIODS:
ma = calculate_ma(prices, period)
coin_results["ma"][str(period)] = ma if ma else "Insufficient Data"
results["data"][coin] = coin_results
print(" Done.")
# Save to file
with open(OUTPUT_FILE, 'w') as f:
json.dump(results, f, indent=4)
print(f"Updated {OUTPUT_FILE}")
except Exception as e:
print(f"Main loop error: {e}")
time.sleep(UPDATE_INTERVAL)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,92 @@
import os
import sys
import json
from web3 import Web3
from dotenv import load_dotenv
# Add project root to sys.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from clp_abis import AERODROME_FACTORY_ABI, UNISWAP_V3_POOL_ABI
load_dotenv()
RPC_URL = os.environ.get("BASE_RPC_URL")
if not RPC_URL:
print("Error: BASE_RPC_URL not set")
sys.exit(1)
w3 = Web3(Web3.HTTPProvider(RPC_URL))
FACTORY_ADDRESS = "0x827922686190790b37229fd06084350E74485b72"
WETH = "0x4200000000000000000000000000000000000006"
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
TICK_SPACING = 1
def check_pool():
print(f"Connecting to Base... {w3.client_version}")
# 1. Get Factory Address from NPM
npm_address = "0x827922686190790b37229fd06084350E74485b72"
# NPM ABI minimal
npm_abi = [{"inputs": [], "name": "factory", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}]
npm = w3.eth.contract(address=npm_address, abi=npm_abi)
try:
factory_address = npm.functions.factory().call()
print(f"Factory Address: {factory_address}")
except Exception as e:
print(f"Error fetching factory: {e}")
return
factory = w3.eth.contract(address=factory_address, abi=AERODROME_FACTORY_ABI)
# 2. Get Pool Address using tickSpacing
print(f"Querying Factory for WETH/USDC with tickSpacing={TICK_SPACING}...")
try:
pool_address = factory.functions.getPool(WETH, USDC, TICK_SPACING).call()
print(f"Pool Address (TS=1): {pool_address}")
except Exception as e:
print(f"Error calling getPool(1): {e}")
# Check TS=80
print(f"Querying Factory for WETH/USDC with tickSpacing=80...")
try:
pool_address_80 = factory.functions.getPool(WETH, USDC, 80).call()
print(f"Pool Address (TS=80): {pool_address_80}")
except Exception as e:
print(f"Error calling getPool(80): {e}")
if pool_address == "0x0000000000000000000000000000000000000000":
print("Pool not found!")
return
# 3. Check Pool Details
pool = w3.eth.contract(address=pool_address, abi=UNISWAP_V3_POOL_ABI)
try:
# Try to read fee()
# Note: Standard UniV3 pools have 'fee()'.
# Aerodrome Slipstream pools might not, or it might be different.
fee = pool.functions.fee().call()
print(f"Pool.fee(): {fee}")
except Exception as e:
print(f"Could not read pool.fee(): {e}")
try:
# Read tickSpacing()
ts = pool.functions.tickSpacing().call()
print(f"Pool.tickSpacing(): {ts}")
except Exception as e:
print(f"Could not read pool.tickSpacing(): {e}")
try:
# Read slot0
slot0 = pool.functions.slot0().call()
print(f"Pool.slot0(): {slot0}")
# Standard UniV3 slot0: (sqrtPriceX96, tick, observationIndex, observationCardinality, observationCardinalityNext, feeProtocol, unlocked)
# Aerodrome might vary.
except Exception as e:
print(f"Could not read pool.slot0(): {e}")
if __name__ == "__main__":
check_pool()

View File

@ -0,0 +1,93 @@
import os
import json
from web3 import Web3
from dotenv import load_dotenv
load_dotenv()
# Aerodrome Slipstream Config
RPC_URL = os.environ.get("BASE_RPC_URL")
NPM_ADDRESS = "0x827922686190790b37229fd06084350E74485b72"
WETH = "0x4200000000000000000000000000000000000006"
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
TARGET_POOL = "0xdbc6998296caa1652a810dc8d3baf4a8294330f1"
# ABIs
NPM_ABI = json.loads('[{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]')
FACTORY_ABI = json.loads('[{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"int24","name":"","type":"int24"}],"name":"getPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]')
def main():
print(f"Connecting to {RPC_URL}...")
w3 = Web3(Web3.HTTPProvider(RPC_URL))
if not w3.is_connected():
print("Failed to connect.")
return
print(f"NPM: {NPM_ADDRESS}")
npm = w3.eth.contract(address=NPM_ADDRESS, abi=NPM_ABI)
try:
factory_addr = npm.functions.factory().call()
print(f"Factory from NPM: {factory_addr}")
except Exception as e:
print(f"Error getting factory: {e}")
return
factory = w3.eth.contract(address=factory_addr, abi=FACTORY_ABI)
# Aerodrome Tick Spacings (Fees)
# Uniswap V3: 100 (0.01%), 500 (0.05%), 3000 (0.3%), 10000 (1%)
# Aerodrome Slipstream might use tickSpacing directly or different fee numbers.
# Slipstream: 1 (0.01%), 50 (0.05%), 200 (0.3%), 2000 (1%) ???
# Or maybe it uses standard Uniswap fees?
# Let's try standard first, then tickSpacing values.
# Common Uniswap V3 Fees
fees_to_test = [100, 500, 3000, 10000]
# Aerodrome specific? (1, 50, 100, 200) - Aerodrome uses tickSpacing as the identifier in getPool sometimes?
# Wait, Uniswap V3 Factory getPool takes (tokenA, tokenB, fee).
# Some forks take (tokenA, tokenB, tickSpacing).
fees_to_test += [1, 50, 200, 2000]
print(f"Testing getPool for WETH/USDC...")
found = False
for fee in fees_to_test:
try:
pool = factory.functions.getPool(WETH, USDC, fee).call()
print(f"Fee {fee}: {pool}")
if pool.lower() == TARGET_POOL.lower():
print(f"✅ MATCH FOUND! Fee Tier is: {fee}")
found = True
except Exception as e:
print(f"Fee {fee}: Error ({e})")
# ... (Existing code)
if not found:
# ... (Existing code)
pass
# DEBUG SLOT0
print(f"\nDebugging slot0 for {TARGET_POOL}...")
target_pool_checksum = Web3.to_checksum_address(TARGET_POOL)
# Try raw call to see data length
raw_data = w3.eth.call({
'to': target_pool_checksum,
'data': w3.keccak(text="slot0()").hex()[:10]
})
print(f"Raw Slot0 Data ({len(raw_data)} bytes): {raw_data.hex()}")
# Try standard ABI
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":"uint24","name":"feeProtocol","type":"uint24"},{"internalType":"bool","name":"unlocked","type":"bool"}],"stateMutability":"view","type":"function"}]')
pool_c = w3.eth.contract(address=target_pool_checksum, abi=POOL_ABI)
try:
data = pool_c.functions.slot0().call()
print(f"Standard V3 Slot0 Data: {data}")
except Exception as e:
print(f"Standard V3 Slot0 Failed: {e}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,33 @@
import os
import sys
import json
from web3 import Web3
from dotenv import load_dotenv
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from clp_abis import NONFUNGIBLE_POSITION_MANAGER_ABI
load_dotenv()
RPC_URL = os.environ.get("BASE_RPC_URL")
w3 = Web3(Web3.HTTPProvider(RPC_URL))
NPM_ADDRESS = "0x827922686190790b37229fd06084350E74485b72"
WETH = "0x4200000000000000000000000000000000000006"
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
def check_code():
npm_addr = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
try:
code = w3.eth.get_code(npm_addr)
print(f"Code at {npm_addr}: {len(code)} bytes")
if len(code) > 0:
# Try calling factory()
npm = w3.eth.contract(address=npm_addr, abi=NONFUNGIBLE_POSITION_MANAGER_ABI)
f = npm.functions.factory().call()
print(f"Factory: {f}")
except Exception as e:
print(f"Check failed: {e}")
if __name__ == "__main__":
check_code()

104
florida/tools/debug_mint.py Normal file
View File

@ -0,0 +1,104 @@
import os
import sys
import json
import time
from decimal import Decimal
from web3 import Web3
from dotenv import load_dotenv
# Add project root to sys.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from clp_abis import NONFUNGIBLE_POSITION_MANAGER_ABI
load_dotenv()
RPC_URL = os.environ.get("BASE_RPC_URL")
if not RPC_URL:
print("Error: BASE_RPC_URL not set")
sys.exit(1)
w3 = Web3(Web3.HTTPProvider(RPC_URL))
private_key = os.environ.get("MAIN_WALLET_PRIVATE_KEY")
account = w3.eth.account.from_key(private_key)
NPM_ADDRESS = "0x827922686190790b37229fd06084350E74485b72"
WETH = "0x4200000000000000000000000000000000000006"
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
# Amounts (Reduced to fit allowance)
AMOUNT0 = 10000000000000000 # 0.01 ETH
AMOUNT1 = 10000000 # 10 USDC
# Ticks (Approx from logs/logic, using TS=1)
# Current Tick ~ -201984 (Price ~3050)
# Just picking a valid range around current price
CURRENT_TICK = -201984
TICK_LOWER = CURRENT_TICK - 200
TICK_UPPER = CURRENT_TICK + 200
def try_mint_simulation(fee_value):
print(f"\n--- Testing Mint with fee={fee_value} ---")
npm = w3.eth.contract(address=NPM_ADDRESS, abi=NONFUNGIBLE_POSITION_MANAGER_ABI)
# FETCH REAL TICK
pool_address = "0xb2cc224c1c9feE385f8ad6a55b4d94E92359DC59" # TS=100 Pool
pool_abi = [{"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": "bool", "name": "unlocked", "type": "bool"}], "stateMutability": "view", "type": "function"}]
pool = w3.eth.contract(address=pool_address, abi=pool_abi)
try:
slot0 = pool.functions.slot0().call()
current_tick = slot0[1]
print(f"Current Tick: {current_tick}")
except:
current_tick = -200000
print("Failed to fetch tick, using default.")
# Align to TS=100
TS = 100
tick_lower = (current_tick // TS) * TS - (TS * 2) # -200 ticks
tick_upper = (current_tick // TS) * TS + (TS * 2) # +200 ticks
print(f"Ticks: {tick_lower} <-> {tick_upper} (TS={TS})")
# CHECK ALLOWANCES
erc20_abi = [{"inputs": [{"internalType": "address", "name": "owner", "type": "address"}, {"internalType": "address", "name": "spender", "type": "address"}], "name": "allowance", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}]
weth_c = w3.eth.contract(address=WETH, abi=erc20_abi)
usdc_c = w3.eth.contract(address=USDC, abi=erc20_abi)
a0 = weth_c.functions.allowance(account.address, NPM_ADDRESS).call()
a1 = usdc_c.functions.allowance(account.address, NPM_ADDRESS).call()
print(f"Allowances: WETH={a0}, USDC={a1}")
params = (
WETH, USDC,
fee_value, # The value in question
tick_lower, tick_upper,
AMOUNT0, AMOUNT1,
0, 0, # Min amounts 0
account.address,
int(time.time()) + 180
)
tx_params = {
'from': account.address,
'nonce': w3.eth.get_transaction_count(account.address),
'value': 0,
'gas': 500000,
'gasPrice': w3.eth.gas_price
}
try:
# Simulate (call)
npm.functions.mint(params).call(tx_params)
print("✅ Simulation SUCCESS!")
return True
except Exception as e:
print(f"❌ Simulation FAILED: {e}")
return False
if __name__ == "__main__":
# Test candidates
candidates = [1, 100, 200, 500, 3000, 10000]
for fee in candidates:
try_mint_simulation(fee)

View File

@ -0,0 +1,39 @@
import os
import sys
import json
from web3 import Web3
from dotenv import load_dotenv
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from clp_abis import AERODROME_FACTORY_ABI
load_dotenv()
RPC_URL = os.environ.get("BASE_RPC_URL")
w3 = Web3(Web3.HTTPProvider(RPC_URL))
FACTORY_ADDRESS = "0x5e7BB104d84c7CB9B682AaC2F3d509f5F406809A" # From previous debug
def check_mapping():
# Try standard V3 factory ABI method: feeAmountTickSpacing(uint24)
abi = [{"inputs": [{"internalType": "uint24", "name": "", "type": "uint24"}], "name": "feeAmountTickSpacing", "outputs": [{"internalType": "int24", "name": "", "type": "int24"}], "stateMutability": "view", "type": "function"}]
factory = w3.eth.contract(address=FACTORY_ADDRESS, abi=abi)
candidates = [1, 50, 80, 100, 200, 400, 500, 2000, 3000, 10000]
print("--- Fee -> TickSpacing Mapping ---")
for fee in candidates:
try:
ts = factory.functions.feeAmountTickSpacing(fee).call()
print(f"Fee {fee} -> TS {ts}")
except Exception as e:
# print(f"Fee {fee} -> Failed")
pass
# Check if there is a 'tickSpacing' method on Factory?
# Aerodrome Factory ABI we used has 'getPool(tokenA, tokenB, tickSpacing)'.
# This implies Factory doesn't use fee mapping for getPool, it uses TS directly.
# BUT NPM 'mint' uses 'fee'.
# So NPM MUST have a mapping.
if __name__ == "__main__":
check_mapping()

43
florida/tools/debug_tx.py Normal file
View File

@ -0,0 +1,43 @@
import os
import sys
import json
from web3 import Web3
from dotenv import load_dotenv
# Add project root to sys.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
load_dotenv()
RPC_URL = os.environ.get("BASE_RPC_URL")
if not RPC_URL:
print("Error: BASE_RPC_URL not set")
sys.exit(1)
w3 = Web3(Web3.HTTPProvider(RPC_URL))
TX_HASH = "0x40c926a0d792a00d7a549f57f76ccb65c14c49ca7120563ea1229d1dae40457d"
def check_tx():
print(f"Fetching receipt for {TX_HASH}...")
try:
receipt = w3.eth.get_transaction_receipt(TX_HASH)
print(f"Status: {receipt['status']}")
print(f"Block: {receipt['blockNumber']}")
print(f"Logs: {len(receipt['logs'])}")
for i, log in enumerate(receipt['logs']):
print(f"--- Log {i} ---")
print(f"Address: {log['address']}")
print(f"Topics: {[t.hex() for t in log['topics']]}")
# Try to decode Swap event?
# Swap(address sender, address recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)
# Topic0: 0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67
if log['topics'][0].hex() == "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67":
print(">>> SWAP EVENT DETECTED <<<")
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
check_tx()

View File

@ -0,0 +1,3 @@
from web3 import Web3
addr = "0xbe6d8f0d397708d99755b7857067757F97174d7d"
print(Web3.to_checksum_address(addr))

View File

@ -0,0 +1,66 @@
import os
import sys
import json
import time
from web3 import Web3
from dotenv import load_dotenv
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from clp_abis import ERC20_ABI
load_dotenv()
RPC_URL = os.environ.get("BASE_RPC_URL")
w3 = Web3(Web3.HTTPProvider(RPC_URL))
private_key = os.environ.get("MAIN_WALLET_PRIVATE_KEY")
account = w3.eth.account.from_key(private_key)
NPM_ADDRESS = "0x827922686190790b37229fd06084350E74485b72"
WETH = "0x4200000000000000000000000000000000000006"
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
# Modified ABI: int24 tickSpacing instead of uint24 fee, AND sqrtPriceX96
MINT_ABI = json.loads('''
[
{"inputs": [{"components": [{"internalType": "address", "name": "token0", "type": "address"}, {"internalType": "address", "name": "token1", "type": "address"}, {"internalType": "int24", "name": "tickSpacing", "type": "int24"}, {"internalType": "int24", "name": "tickLower", "type": "int24"}, {"internalType": "int24", "name": "tickUpper", "type": "int24"}, {"internalType": "uint256", "name": "amount0Desired", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Desired", "type": "uint256"}, {"internalType": "uint256", "name": "amount0Min", "type": "uint256"}, {"internalType": "uint256", "name": "amount1Min", "type": "uint256"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}, {"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}], "internalType": "struct INonfungiblePositionManager.MintParams", "name": "params", "type": "tuple"}], "name": "mint", "outputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}, {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, {"internalType": "uint256", "name": "amount0", "type": "uint256"}, {"internalType": "uint256", "name": "amount1", "type": "uint256"}], "stateMutability": "payable", "type": "function"}
]
''')
def try_mint():
npm = w3.eth.contract(address=NPM_ADDRESS, abi=MINT_ABI)
# Tick Logic for 0xb2cc (TS=100)
# Current Tick -195800
current_tick = -195800
# Align to 100
# -196000 and -195600
tl = -196000
tu = -195600
# Amounts
a0 = 10000000000000000 # 0.01 ETH
a1 = 10000000 # 10 USDC
# tickSpacing param
ts = 100
params = (
WETH, USDC,
ts, # int24
tl, tu,
a0, a1,
0, 0,
account.address,
int(time.time()) + 180,
0 # sqrtPriceX96
)
print(f"Simulating mint with TS={ts} (int24)...")
try:
npm.functions.mint(params).call({'from': account.address, 'gas': 1000000})
print("✅ SUCCESS!")
except Exception as e:
print(f"❌ FAILED: {e}")
if __name__ == "__main__":
try_mint()

View File

@ -0,0 +1,192 @@
import os
import json
import time
import pandas as pd
from decimal import Decimal
from datetime import datetime
from web3 import Web3
from dotenv import load_dotenv
# --- CONFIGURATION ---
CONFIG_FILE = os.path.join(os.path.dirname(__file__), "pool_scanner_config.json")
STATE_FILE = os.path.join("market_data", "pool_scanner_state.json")
HISTORY_FILE = os.path.join("market_data", "pool_history.csv")
load_dotenv()
# RPC MAP
RPC_MAP = {
"ARBITRUM": os.environ.get("MAINNET_RPC_URL"),
"BSC": os.environ.get("BNB_RPC_URL"),
"BASE": os.environ.get("BASE_RPC_URL")
}
# ABIS
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": "feeGrowthGlobal0X128", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "feeGrowthGlobal1X128", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "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"}
]
''')
# PancakeSwap V3 uses uint32 for feeProtocol
PANCAKE_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": "uint32", "name": "feeProtocol", "type": "uint32"}, {"internalType": "bool", "name": "unlocked", "type": "bool"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "feeGrowthGlobal0X128", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "feeGrowthGlobal1X128", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "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"}
]
''')
AERODROME_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": "bool", "name": "unlocked", "type": "bool"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "feeGrowthGlobal0X128", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "feeGrowthGlobal1X128", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "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"}
]
''')
ERC20_ABI = json.loads('[{"inputs": [], "name": "decimals", "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}], "stateMutability": "view", "type": "function"}]')
def get_w3(chain):
url = RPC_MAP.get(chain)
if not url: return None
return Web3(Web3.HTTPProvider(url))
def load_state():
if os.path.exists(STATE_FILE):
with open(STATE_FILE, 'r') as f:
return json.load(f)
return {}
def save_state(state):
os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)
with open(STATE_FILE, 'w') as f:
json.dump(state, f, indent=2)
def append_history(data):
df = pd.DataFrame([data])
header = not os.path.exists(HISTORY_FILE)
df.to_csv(HISTORY_FILE, mode='a', header=header, index=False)
def get_liquidity_for_amount(amount, sqrt_price_x96, tick_lower, tick_upper, decimal_diff):
# Simplified Liquidity Calc for 50/50 deposit simulation
# L = Amount / (sqrt(P) - sqrt(Pa)) for one side...
# For now, we assume simple V3 math or just track Fee Growth per Unit Liquidity
# Real simulation is complex.
# TRICK: We will track "Fee Growth per 1 Unit of Liquidity" directly (Raw X128).
# Then user can multiply by their theoretical L later.
return 1
def main():
print("Starting Pool Scanner...")
with open(CONFIG_FILE, 'r') as f:
pools = json.load(f)
state = load_state()
# Init Web3 cache
w3_instances = {}
for pool in pools:
name = pool['name']
chain = pool['chain']
# Fix Checksum
try:
addr = Web3.to_checksum_address(pool['pool_address'])
except Exception:
print(f" ❌ Invalid Address: {pool['pool_address']}")
continue
is_aero = pool.get('is_aerodrome', False)
print(f"Scanning {name} ({chain})...")
if chain not in w3_instances:
w3_instances[chain] = get_w3(chain)
w3 = w3_instances[chain]
if not w3 or not w3.is_connected():
print(f" ❌ RPC Error for {chain}")
continue
try:
if is_aero:
abi = AERODROME_POOL_ABI
elif chain == "BSC":
abi = PANCAKE_POOL_ABI
else:
abi = POOL_ABI
contract = w3.eth.contract(address=addr, abi=abi)
# Fetch Data
slot0 = contract.functions.slot0().call()
tick = slot0[1]
sqrt_price = slot0[0]
fg0 = contract.functions.feeGrowthGlobal0X128().call()
fg1 = contract.functions.feeGrowthGlobal1X128().call()
# Fetch Decimals (Once)
if name not in state:
t0 = contract.functions.token0().call()
t1 = contract.functions.token1().call()
d0 = w3.eth.contract(address=t0, abi=ERC20_ABI).functions.decimals().call()
d1 = w3.eth.contract(address=t1, abi=ERC20_ABI).functions.decimals().call()
state[name] = {
"init_tick": tick,
"init_fg0": fg0,
"init_fg1": fg1,
"decimals": [d0, d1],
"cumulative_fees_usd": 0.0,
"last_fg0": fg0,
"last_fg1": fg1
}
# Update State
prev = state[name]
diff0 = fg0 - prev['last_fg0']
diff1 = fg1 - prev['last_fg1']
# Calculate USD Value of Fees (Approx)
# Need Liquidity.
# If we assume 1 unit of Liquidity?
# Fee = Diff * L / 2^128
# Update Last
prev['last_fg0'] = fg0
prev['last_fg1'] = fg1
prev['last_tick'] = tick
prev['last_update'] = datetime.now().isoformat()
# Save History
record = {
"timestamp": datetime.now().isoformat(),
"pool_name": name,
"chain": chain,
"tick": tick,
"sqrtPriceX96": str(sqrt_price),
"feeGrowth0": str(fg0),
"feeGrowth1": str(fg1)
}
append_history(record)
print(f" ✅ Data recorded. Tick: {tick}")
except Exception as e:
print(f" ❌ Error: {e}")
save_state(state)
print("Scan complete.")
if __name__ == "__main__":
while True:
main()
time.sleep(600) # 10 minutes

View File

@ -0,0 +1,54 @@
[
{
"name": "Uniswap V3 (Arbitrum) - ETH/USDC",
"chain": "ARBITRUM",
"pool_address": "0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443",
"fee_tier": 500,
"simulation": {
"investment_usd": 10000,
"range_width_pct": 0.10
}
},
{
"name": "PancakeSwap V3 (BNB Chain) - WBNB/USDT",
"chain": "BSC",
"pool_address": "0x36696169C63e42cd08ce11f5deeBbCeBae652050",
"fee_tier": 100,
"simulation": {
"investment_usd": 10000,
"range_width_pct": 0.10
}
},
{
"name": "Uniswap V3 (Base) - WETH/USDC",
"chain": "BASE",
"pool_address": "0xd0b53d9277642d899df5c87a3966a349a798f224",
"fee_tier": 500,
"simulation": {
"investment_usd": 10000,
"range_width_pct": 0.10
}
},
{
"name": "Aerodrome SlipStream (Base) - WETH/USDC (TS=1)",
"chain": "BASE",
"pool_address": "0xdbc6998296caA1652A810dc8D3BaF4A8294330f1",
"is_aerodrome": true,
"tick_spacing": 1,
"simulation": {
"investment_usd": 10000,
"range_width_pct": 0.10
}
},
{
"name": "Aerodrome SlipStream (Base) - WETH/USDC (TS=100)",
"chain": "BASE",
"pool_address": "0xb2cc224c1c9feE385f8ad6a55b4d94E92359DC59",
"is_aerodrome": true,
"tick_spacing": 100,
"simulation": {
"investment_usd": 10000,
"range_width_pct": 0.10
}
}
]

View File

@ -0,0 +1,349 @@
import os
import sys
import json
import argparse
import time
from decimal import Decimal
from dotenv import load_dotenv
from web3 import Web3
# Load environment variables
load_dotenv()
# --- CONFIGURATION ---
# ABIs
ERC20_ABI = json.loads('''
[
{"inputs": [], "name": "decimals", "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "symbol", "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"internalType": "address", "name": "account", "type": "address"}], "name": "balanceOf", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"},
{"inputs": [{"internalType": "address", "name": "spender", "type": "address"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "approve", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"},
{"inputs": [{"internalType": "address", "name": "owner", "type": "address"}, {"internalType": "address", "name": "spender", "type": "address"}], "name": "allowance", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}
]
''')
WETH_ABI = json.loads('''
[
{"inputs": [], "name": "deposit", "outputs": [], "stateMutability": "payable", "type": "function"},
{"inputs": [{"internalType": "uint256", "name": "wad", "type": "uint256"}], "name": "withdraw", "outputs": [], "stateMutability": "nonpayable", "type": "function"}
]
''')
# SwapRouter01 (With Deadline in struct) - e.g. Arbitrum 0xE592..., BSC
SWAP_ROUTER_01_ABI = json.loads('''
[
{"inputs": [{"components": [{"internalType": "address", "name": "tokenIn", "type": "address"}, {"internalType": "address", "name": "tokenOut", "type": "address"}, {"internalType": "uint24", "name": "fee", "type": "uint24"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint256", "name": "deadline", "type": "uint256"}, {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "amountOutMinimum", "type": "uint200"}, {"internalType": "uint160", "name": "sqrtPriceLimitX96", "type": "uint160"}], "internalType": "struct ISwapRouter.ExactInputSingleParams", "name": "params", "type": "tuple"}], "name": "exactInputSingle", "outputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}], "stateMutability": "payable", "type": "function"}
]
''')
# SwapRouter02 (NO Deadline in struct) - e.g. Base 0x2626...
SWAP_ROUTER_02_ABI = json.loads('''
[
{"inputs": [{"components": [{"internalType": "address", "name": "tokenIn", "type": "address"}, {"internalType": "address", "name": "tokenOut", "type": "address"}, {"internalType": "uint24", "name": "fee", "type": "uint24"}, {"internalType": "address", "name": "recipient", "type": "address"}, {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, {"internalType": "uint256", "name": "amountOutMinimum", "type": "uint256"}, {"internalType": "uint160", "name": "sqrtPriceLimitX96", "type": "uint160"}], "internalType": "struct IV3SwapRouter.ExactInputSingleParams", "name": "params", "type": "tuple"}], "name": "exactInputSingle", "outputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}], "stateMutability": "payable", "type": "function"}
]
''')
CHAIN_CONFIG = {
"ARBITRUM": {
"rpc_env": "MAINNET_RPC_URL",
"chain_id": 42161,
"router": "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45", # SwapRouter02
"abi_version": 2,
"tokens": {
"USDC": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"WETH": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
"ETH": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # Alias to WETH for wrapping
"CBBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"
},
"default_fee": 500
},
"BASE": {
"rpc_env": "BASE_RPC_URL",
"chain_id": 8453,
"router": "0x2626664c2603336E57B271c5C0b26F421741e481", # SwapRouter02
"abi_version": 2,
"tokens": {
"USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"WETH": "0x4200000000000000000000000000000000000006",
"ETH": "0x4200000000000000000000000000000000000006", # Alias to WETH for wrapping
"CBBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"
},
"default_fee": 500
},
"BASE_AERO": {
"rpc_env": "BASE_RPC_URL",
"chain_id": 8453,
"router": "0xbe6D8f0D397708D99755B7857067757f97174d7d", # Aerodrome Slipstream SwapRouter
"abi_version": 1, # Router requires deadline (Standard SwapRouter01 style)
"tokens": {
"USDC": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"WETH": "0x4200000000000000000000000000000000000006",
"ETH": "0x4200000000000000000000000000000000000006",
"CBBTC": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"
},
"default_fee": 1 # TickSpacing 1 (0.01%)
},
"BSC": {
"rpc_env": "BNB_RPC_URL",
"chain_id": 56,
"router": "0x1b81D678ffb9C0263b24A97847620C99d213eB14", # PancakeSwap V3
"abi_version": 1,
"tokens": {
"USDC": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d",
"WBNB": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
"BNB": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" # Alias to WBNB for wrapping
},
"default_fee": 500
}
}
def get_web3(chain_name):
config = CHAIN_CONFIG.get(chain_name.upper())
if not config:
raise ValueError(f"Unsupported chain: {chain_name}")
rpc_url = os.environ.get(config["rpc_env"])
if not rpc_url:
raise ValueError(f"RPC URL not found in environment for {config['rpc_env']}")
w3 = Web3(Web3.HTTPProvider(rpc_url))
if not w3.is_connected():
raise ConnectionError(f"Failed to connect to {chain_name} RPC")
return w3, config
def approve_token(w3, token_contract, spender_address, amount, private_key, my_address):
"""
Checks allowance and approves if necessary.
Robust gas handling.
"""
allowance = token_contract.functions.allowance(my_address, spender_address).call()
if allowance >= amount:
print(f"Token already approved (Allowance: {allowance})")
return True
print(f"Approving token... (Current: {allowance}, Needed: {amount})")
# Build tx base
tx_params = {
'from': my_address,
'nonce': w3.eth.get_transaction_count(my_address),
}
# Determine Gas Strategy
try:
latest_block = w3.eth.get_block('latest')
if 'baseFeePerGas' in latest_block:
# EIP-1559
base_fee = latest_block['baseFeePerGas']
priority_fee = w3.to_wei(0.1, 'gwei') # Conservative priority
tx_params['maxFeePerGas'] = int(base_fee * 1.5) + priority_fee
tx_params['maxPriorityFeePerGas'] = priority_fee
else:
# Legacy
tx_params['gasPrice'] = w3.eth.gas_price
except Exception as e:
print(f"Error determining gas strategy: {e}. Fallback to w3.eth.gas_price")
tx_params['gasPrice'] = w3.eth.gas_price
# Build transaction
tx = token_contract.functions.approve(spender_address, 2**256 - 1).build_transaction(tx_params)
# Sign and send
signed_tx = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print(f"Approval Tx sent: {tx_hash.hex()}")
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
if receipt.status == 1:
print("Approval successful.")
return True
else:
print("Approval failed.")
return False
def wrap_eth(w3, weth_address, amount_wei, private_key, my_address):
"""
Wraps native ETH/BNB to WETH/WBNB.
"""
print(f"Wrapping native token to wrapped version...")
weth_contract = w3.eth.contract(address=weth_address, abi=WETH_ABI)
tx_params = {
'from': my_address,
'value': amount_wei,
'nonce': w3.eth.get_transaction_count(my_address),
}
# Gas logic (Simplified)
latest_block = w3.eth.get_block('latest')
if 'baseFeePerGas' in latest_block:
base_fee = latest_block['baseFeePerGas']
tx_params['maxFeePerGas'] = int(base_fee * 1.5) + w3.to_wei(0.1, 'gwei')
tx_params['maxPriorityFeePerGas'] = w3.to_wei(0.1, 'gwei')
else:
tx_params['gasPrice'] = w3.eth.gas_price
tx = weth_contract.functions.deposit().build_transaction(tx_params)
signed_tx = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print(f"Wrapping Tx sent: {tx_hash.hex()}")
w3.eth.wait_for_transaction_receipt(tx_hash)
print("Wrapping successful.")
def execute_swap(chain_name, token_in_sym, token_out_sym, amount_in_readable, fee_tier=None, slippage_pct=0.5):
"""
Main function to execute swap.
"""
chain_name = chain_name.upper()
token_in_sym = token_in_sym.upper()
token_out_sym = token_out_sym.upper()
w3, config = get_web3(chain_name)
# Get private key
private_key = os.environ.get("MAIN_WALLET_PRIVATE_KEY")
if not private_key:
raise ValueError("MAIN_WALLET_PRIVATE_KEY not found in environment variables")
account = w3.eth.account.from_key(private_key)
my_address = account.address
print(f"Connected to {chain_name} as {my_address}")
# Validate tokens
if token_in_sym not in config["tokens"]:
raise ValueError(f"Token {token_in_sym} not supported on {chain_name}")
if token_out_sym not in config["tokens"]:
raise ValueError(f"Token {token_out_sym} not supported on {chain_name}")
token_in_addr = config["tokens"][token_in_sym]
token_out_addr = config["tokens"][token_out_sym]
router_addr = config["router"]
abi_ver = config.get("abi_version", 1)
# Initialize Contracts
token_in_contract = w3.eth.contract(address=token_in_addr, abi=ERC20_ABI)
token_out_contract = w3.eth.contract(address=token_out_addr, abi=ERC20_ABI)
router_abi = SWAP_ROUTER_01_ABI if abi_ver == 1 else SWAP_ROUTER_02_ABI
router_contract = w3.eth.contract(address=router_addr, abi=router_abi)
# Decimals (ETH/BNB and their wrapped versions all use 18)
decimals_in = 18 if token_in_sym in ["ETH", "BNB"] else token_in_contract.functions.decimals().call()
amount_in_wei = int(Decimal(str(amount_in_readable)) * Decimal(10)**decimals_in)
print(f"Preparing to swap {amount_in_readable} {token_in_sym} -> {token_out_sym}")
# Handle Native Wrap
if token_in_sym in ["ETH", "BNB"]:
# Check native balance
native_balance = w3.eth.get_balance(my_address)
if native_balance < amount_in_wei:
raise ValueError(f"Insufficient native balance. Have {native_balance / 10**18}, need {amount_in_readable}")
# Check if we already have enough wrapped token
w_balance = token_in_contract.functions.balanceOf(my_address).call()
if w_balance < amount_in_wei:
wrap_eth(w3, token_in_addr, amount_in_wei - w_balance, private_key, my_address)
else:
# Check Token Balance
balance = token_in_contract.functions.balanceOf(my_address).call()
if balance < amount_in_wei:
raise ValueError(f"Insufficient balance. Have {balance / 10**decimals_in} {token_in_sym}, need {amount_in_readable}")
# Approve
approve_token(w3, token_in_contract, router_addr, amount_in_wei, private_key, my_address)
# Prepare Swap Params
used_fee = fee_tier if fee_tier else config["default_fee"]
amount_out_min = 0
if abi_ver == 1:
# Router 01 (Deadline in struct)
params = (
token_in_addr,
token_out_addr,
used_fee,
my_address,
int(time.time()) + 120, # deadline
amount_in_wei,
amount_out_min,
0 # sqrtPriceLimitX96
)
else:
# Router 02 (No Deadline in struct)
params = (
token_in_addr,
token_out_addr,
used_fee,
my_address,
# No deadline here
amount_in_wei,
amount_out_min,
0 # sqrtPriceLimitX96
)
print(f"Swapping... Fee Tier: {used_fee} | ABI: V{abi_ver}")
# Build Tx
tx_build = {
'from': my_address,
'nonce': w3.eth.get_transaction_count(my_address),
}
# Estimate Gas
try:
gas_estimate = router_contract.functions.exactInputSingle(params).estimate_gas(tx_build)
tx_build['gas'] = int(gas_estimate * 1.2)
except Exception as e:
print(f"Gas estimation failed: {e}. Using default gas limit (500k).")
tx_build['gas'] = 500000
# Add Gas Price (Same robust logic as approve)
if chain_name == "BSC":
tx_build['gasPrice'] = w3.eth.gas_price
else:
try:
latest_block = w3.eth.get_block('latest')
if 'baseFeePerGas' in latest_block:
base_fee = latest_block['baseFeePerGas']
priority_fee = w3.to_wei(0.1, 'gwei')
tx_build['maxFeePerGas'] = int(base_fee * 1.5) + priority_fee
tx_build['maxPriorityFeePerGas'] = priority_fee
else:
tx_build['gasPrice'] = w3.eth.gas_price
except:
tx_build['gasPrice'] = w3.eth.gas_price
# Sign and Send
tx_func = router_contract.functions.exactInputSingle(params)
tx = tx_func.build_transaction(tx_build)
signed_tx = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
print(f"Swap Tx Sent: {tx_hash.hex()}")
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
if receipt.status == 1:
print("Swap Successful!")
else:
print("Swap Failed!")
# print(receipt) # verbose
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Universal Swapper for Arbitrum, Base, BSC")
parser.add_argument("chain", help="Chain name (ARBITRUM, BASE, BSC)")
parser.add_argument("token_in", help="Token to sell (e.g. USDC)")
parser.add_argument("token_out", help="Token to buy (e.g. WETH)")
parser.add_argument("amount", help="Amount to swap", type=float)
parser.add_argument("--fee", help="Fee tier (e.g. 500, 3000)", type=int)
args = parser.parse_args()
try:
execute_swap(args.chain, args.token_in, args.token_out, args.amount, args.fee)
except Exception as e:
print(f"Error: {e}")