Compare commits
57 Commits
5ca16ec33f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a166d33012 | |||
| c29dc2c8ac | |||
| 138e98fc49 | |||
| 30aad78285 | |||
| f1946102fb | |||
| e37369608b | |||
| 9eb1abcfc8 | |||
| b2a11ca449 | |||
| 6ac0f4478f | |||
| 5cc14d485a | |||
| b1a86b81fc | |||
| 95f514b563 | |||
| c5b148ed6c | |||
| 5cdf71515b | |||
| b22fdcf741 | |||
| 69fbf389c8 | |||
| e6adbaffef | |||
| 90c4453ab4 | |||
| c04eb3f377 | |||
| 4f7bb429b7 | |||
| 27edce0085 | |||
| 22c92cb3ca | |||
| b3cdf98161 | |||
| e5151d9d66 | |||
| 1a12fde839 | |||
| 73c77521d9 | |||
| 85e4e6508d | |||
| ee457d0a51 | |||
| 2194c71d5f | |||
| e10e3062ff | |||
| cc3b012087 | |||
| 246983ba08 | |||
| 50aa497037 | |||
| 857d1b91f0 | |||
| 42e0dfc5c6 | |||
| 149800b426 | |||
| 4b30f4a62b | |||
| b1913ec870 | |||
| 7d772a628a | |||
| 738321a7e9 | |||
| 4ab35ab879 | |||
| 1c3a1338d0 | |||
| 0cba52b60c | |||
| 98bda8d71a | |||
| 7c72dd3a1f | |||
| 271bea4653 | |||
| 4bf84d29bb | |||
| bbb7614a60 | |||
| ccf25c1643 | |||
| a318bb04ce | |||
| d339c0e668 | |||
| d37707941c | |||
| 17bc3fad03 | |||
| 63c01bcf51 | |||
| 215cde556c | |||
| b2b353312d | |||
| aaa39c1e8c |
4
.gitignore
vendored
4
.gitignore
vendored
@ -28,3 +28,7 @@ hedge_status.json
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
|
||||
# Data
|
||||
*.csv
|
||||
florida/market_data/
|
||||
|
||||
54
AGENTS.md
Normal file
54
AGENTS.md
Normal file
@ -0,0 +1,54 @@
|
||||
# AGENTS.md - Repository Guidelines for Agentic Coding
|
||||
|
||||
## Project Overview
|
||||
This is a Python blockchain trading system for Uniswap CLP (Concentrated Liquidity Pool) management and hedging on Hyperliquid. The system consists of CLP hedgers, Uniswap managers, and KPI tracking tools.
|
||||
|
||||
## Build/Lint/Test Commands
|
||||
```bash
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Run Python with type checking (recommended workflow)
|
||||
python -m mypy clp_hedger.py
|
||||
python -m mypy uniswap_manager.py
|
||||
|
||||
# Lint with flake8 (optional)
|
||||
flake8 --max-line-length=100 *.py
|
||||
|
||||
# Run specific modules
|
||||
python clp_hedger.py
|
||||
python uniswap_manager.py
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### Imports
|
||||
- Standard library imports first, then third-party, then local imports
|
||||
- Use `from typing import Optional, Dict, Any, List, Union`
|
||||
- Import logging_utils with try/except fallback pattern
|
||||
|
||||
### Types & Precision
|
||||
- Use `Decimal` for all financial calculations with `getcontext().prec = 60`
|
||||
- Function signatures: `def func_name(param: Type) -> ReturnType:`
|
||||
- Convert to float only for SDK compatibility at the last moment
|
||||
|
||||
### Naming Conventions
|
||||
- Constants: `UPPER_SNAKE_CASE`
|
||||
- Functions/variables: `lower_snake_case`
|
||||
- Classes: `PascalCase` (rarely used)
|
||||
- Loggers: `logger = logging.getLogger("MODULE_NAME")`
|
||||
|
||||
### Error Handling
|
||||
- Use try/except blocks for external dependencies (web3, hyperliquid, logging_utils)
|
||||
- Log warnings for missing optional dependencies
|
||||
- Return Decimal("0") for failed conversions, not None
|
||||
|
||||
### Logging
|
||||
- Use structured logging with UnixMsLogFilter for timestamp consistency
|
||||
- Log to files in `logs/` directory (auto-created)
|
||||
- Logger names should be uppercase: "HEDGER", "UNISWAP_MANAGER", "KPI_TRACKER"
|
||||
|
||||
### Environment
|
||||
- Load `.env` files from current directory with fallback
|
||||
- Use absolute paths for cross-directory imports
|
||||
- Append project root to `sys.path` for local modules
|
||||
95
GEMINI.md
Normal file
95
GEMINI.md
Normal file
@ -0,0 +1,95 @@
|
||||
# GEMINI Project Context: Uniswap Auto CLP & Hedger
|
||||
|
||||
**Last Updated:** January 6, 2026
|
||||
**Project:** Uniswap V3 Automated Concentrated Liquidity (CLP) & Delta-Neutral Hedger
|
||||
**Operating System:** Windows (win32)
|
||||
|
||||
## 1. Project Overview
|
||||
This project is an automated high-frequency trading system designed to:
|
||||
1. **Provide Concentrated Liquidity (CLP):** Automatically manages positions on Uniswap V3 and its forks (Aerodrome, PancakeSwap) to capture trading fees.
|
||||
2. **Hedge Delta Exposure:** Simultaneously executes delta-neutral hedges on Hyperliquid (Perp DEX) to neutralize the price risk of the underlying assets (Impermanent Loss protection).
|
||||
|
||||
The system operates as a **Delta-Zero Yield Farmer**, effectively capturing LP fees with reduced market exposure.
|
||||
|
||||
## 2. System Architecture
|
||||
The system consists of two main independent Python processes that coordinate via a shared JSON state file.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[clp_manager.py] -- Reads/Writes --> C{status.json}
|
||||
B[clp_hedger.py] -- Reads --> C
|
||||
A -- On-Chain Tx --> D[EVM Chain (Base/Arb)]
|
||||
B -- API Calls --> E[Hyperliquid Perp]
|
||||
F[telegram_monitor.py] -- Reads --> C
|
||||
F -- Alerts --> G[Telegram]
|
||||
```
|
||||
|
||||
### Components
|
||||
* **`clp_manager.py` (The Strategist):**
|
||||
* **Role:** Active Liquidity Provision on EVM chains.
|
||||
* **Actions:** Mints new positions, monitors ranges, auto-closes out-of-range positions, collects fees, and handles auto-wrapping/swapping.
|
||||
* **State:** Updates `{TARGET_DEX}_status.json` with entry price, token amounts, and range details.
|
||||
|
||||
* **`clp_hedger.py` (The Guardian):**
|
||||
* **Role:** Delta Neutralization on Hyperliquid.
|
||||
* **Actions:** Calculates the LP's real-time delta. Executes short/long trades to maintain `Net Delta ≈ 0`.
|
||||
* **Features:**
|
||||
* **Dynamic Thresholds:** Adjusts rebalance sensitivity based on market volatility (StdDev).
|
||||
* **Fishing Orders:** Places passive maker orders to capture spread and rebates.
|
||||
* **Edge Protection:** Reduces hedging logic near range edges to prevent "buying high/selling low".
|
||||
|
||||
* **`clp_config.py`:** Centralized configuration. Uses `TARGET_DEX` env var to switch between profiles (e.g., `UNISWAP_V3`, `AERODROME_BASE_CL`).
|
||||
|
||||
## 3. Key Files & Directories
|
||||
|
||||
| File/Directory | Description |
|
||||
| :--- | :--- |
|
||||
| **`clp_manager.py`** | Main entry point for the LP Manager. |
|
||||
| **`clp_hedger.py`** | Main entry point for the Delta Hedger. |
|
||||
| **`clp_config.py`** | Configuration profiles and strategy settings. |
|
||||
| **`requirements.txt`** | Python dependencies (`web3`, `hyperliquid-python-sdk`, `dotenv`). |
|
||||
| **`doc/`** | Extensive documentation (`CHANGELOG.md`, `UNISWAP_MANAGER_WORKFLOW.md`). |
|
||||
| **`florida/`** | A secondary deployment or research directory containing specific strategy configurations. |
|
||||
| **`tools/`** | Utility scripts (e.g., `git_agent.py`, `universal_swapper.py`). |
|
||||
| **`logs/`** | Runtime logs. |
|
||||
| **`*_status.json`** | The shared state file (IPC). Specific name depends on `TARGET_DEX`. |
|
||||
|
||||
## 4. Setup & Usage
|
||||
|
||||
### Prerequisites
|
||||
* Python 3.10+
|
||||
* Hyperliquid Account & API Key (in `.env`)
|
||||
* EVM Wallet Private Key (in `.env`)
|
||||
* RPC URLs (in `.env`)
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Configuration
|
||||
1. Create a `.env` file (see `.env.example` in `tools/`).
|
||||
2. Edit `clp_config.py` or set `TARGET_DEX` environment variable to choose a profile (e.g., `AERODROME_WETH-USDC_008`).
|
||||
|
||||
### Running the System
|
||||
You typically run the Manager and Hedger in separate terminals:
|
||||
|
||||
**Terminal 1 (Manager):**
|
||||
```bash
|
||||
python clp_manager.py
|
||||
```
|
||||
|
||||
**Terminal 2 (Hedger):**
|
||||
```bash
|
||||
python clp_hedger.py
|
||||
```
|
||||
|
||||
## 5. Recent Developments (Jan 2026)
|
||||
* **Low Volatility Optimization:** Increased base rebalance thresholds (from 5% to 8%) to reduce churn in sideways markets.
|
||||
* **Fishing Orders:** Implemented passive limit orders to profit from rebalancing.
|
||||
* **Shadow Logging:** The system now logs "Shadow" trades to simulate maker order performance vs taker execution.
|
||||
* **Dynamic Edge Proximity:** Safety buffers now scale with position size.
|
||||
|
||||
## 6. Active Context
|
||||
* **Current Profile:** The root directory contains `AERODROME_WETH-USDC_008_status.json`, indicating an active or recent session on **Aerodrome (Base Chain)**.
|
||||
* **Workflows:** Refer to `doc/UNISWAP_MANAGER_WORKFLOW.md` for detailed state machine logic of the Manager.
|
||||
82
clp_abis.py
Normal file
82
clp_abis.py
Normal 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"}
|
||||
]
|
||||
''')
|
||||
174
clp_config.py
Normal file
174
clp_config.py
Normal file
@ -0,0 +1,174 @@
|
||||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
# --- GLOBAL SETTINGS ---
|
||||
# Use environment variables to switch profiles
|
||||
TARGET_DEX = os.environ.get("TARGET_DEX", "UNISWAP_V3")
|
||||
STATUS_FILE = os.environ.get("STATUS_FILE", f"{TARGET_DEX}_status.json")
|
||||
|
||||
# --- DEFAULT STRATEGY ---
|
||||
DEFAULT_STRATEGY = {
|
||||
"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
|
||||
|
||||
# Investment Settings
|
||||
"TARGET_INVESTMENT_AMOUNT": 2000, # Total USD value to deploy into the LP position
|
||||
"INITIAL_HEDGE_CAPITAL": 1000, # Capital reserved on Hyperliquid for hedging
|
||||
"VALUE_REFERENCE": "USD", # Base currency for all calculations
|
||||
|
||||
# Range Settings
|
||||
"RANGE_WIDTH_PCT": Decimal("0.03"), # LP width (e.g. 0.05 = +/- 5% from current price)
|
||||
"SLIPPAGE_TOLERANCE": Decimal("0.05"), # 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), "FIXED" (Initial Delta)
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.012"), # Minimum delta change (in coins) required to trigger a trade
|
||||
|
||||
# Unified Hedger Settings
|
||||
|
||||
"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
|
||||
"ZONE_CLOSE_END": Decimal("11.0"), # Distance (pct) from edge to finish closing logic
|
||||
"ZONE_TOP_HEDGE_START": Decimal("10.0"), # Distance (pct) from top edge to adjust hedging
|
||||
"PRICE_BUFFER_PCT": Decimal("0.0025"), # Buffer for limit order pricing (0.15%)
|
||||
"MIN_ORDER_VALUE_USD": Decimal("10.0"), # Minimum order size allowed by Hyperliquid
|
||||
"DYNAMIC_THRESHOLD_MULTIPLIER": Decimal("1.2"), # Expansion factor for thresholds
|
||||
"MIN_TIME_BETWEEN_TRADES": 60, # Cooldown (seconds) between rebalance trades
|
||||
"MAX_HEDGE_MULTIPLIER": Decimal("1.25"), # Max allowed hedge size relative to calculated target
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": Decimal("0.25"), # Base tolerance for delta drift (20%)
|
||||
"EDGE_PROXIMITY_PCT": Decimal("0.04"), # Distance to range edge where protection activates
|
||||
"VELOCITY_THRESHOLD_PCT": Decimal("0.0005"), # Minimum price velocity to trigger volatility logic
|
||||
"POSITION_OPEN_EDGE_PROXIMITY_PCT": Decimal("0.06"), # Safety margin when opening new positions
|
||||
"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.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)
|
||||
"FISHING_ORDER_SIZE_PCT": Decimal("0.10"), # Size of individual fishing orders
|
||||
"FISHING_TIMEOUT_FALLBACK": 30, # Seconds before converting fishing order to taker
|
||||
|
||||
# EAC (Enhanced Asymmetric Compensation)
|
||||
"EAC_NARROW_RANGE_THRESHOLD": Decimal("0.02"), # <2% = narrow
|
||||
"EAC_MEDIUM_RANGE_THRESHOLD": Decimal("0.05"), # <5% = medium
|
||||
"EAC_NARROW_BOOST": Decimal("0.15"), # 15% boost
|
||||
"EAC_MEDIUM_BOOST": Decimal("0.10"), # 10% boost
|
||||
"EAC_WIDE_BOOST": Decimal("0.075"), # 7.5% boost
|
||||
}
|
||||
|
||||
# --- CLP PROFILES ---
|
||||
CLP_PROFILES = {
|
||||
"UNISWAP_V3": {
|
||||
"NAME": "Uniswap V3 (Arbitrum) - ETH/USDC",
|
||||
"COIN_SYMBOL": "ETH",
|
||||
"RPC_ENV_VAR": "MAINNET_RPC_URL",
|
||||
"NPM_ADDRESS": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
||||
"ROUTER_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
|
||||
"TOKEN_A_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH
|
||||
"TOKEN_B_ADDRESS": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC
|
||||
"WRAPPED_NATIVE_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
||||
"POOL_FEE": 500,
|
||||
"TARGET_INVESTMENT_AMOUNT": 3000,
|
||||
"HEDGE_STRATEGY": "FIXED",
|
||||
"RANGE_WIDTH_PCT": Decimal("0.0075"),
|
||||
},
|
||||
"UNISWAP_wide": {
|
||||
"NAME": "Uniswap V3 (Arbitrum) - ETH/USDC Wide",
|
||||
"COIN_SYMBOL": "ETH",
|
||||
"RPC_ENV_VAR": "MAINNET_RPC_URL",
|
||||
"NPM_ADDRESS": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
||||
"ROUTER_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
|
||||
"TOKEN_A_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH
|
||||
"TOKEN_B_ADDRESS": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC
|
||||
"WRAPPED_NATIVE_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
||||
"POOL_FEE": 500,
|
||||
"RANGE_WIDTH_PCT": Decimal("0.05"),
|
||||
"TARGET_INVESTMENT_AMOUNT": 2000,
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.01"),
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": Decimal("0.15"),
|
||||
},
|
||||
"PANCAKESWAP_BNB": {
|
||||
"NAME": "PancakeSwap V3 (BNB Chain) - BNB/USDT",
|
||||
"COIN_SYMBOL": "BNB",
|
||||
"RPC_ENV_VAR": "BNB_RPC_URL",
|
||||
"NPM_ADDRESS": "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364",
|
||||
"ROUTER_ADDRESS": "0x1b81D678ffb9C0263b24A97847620C99d213eB14",
|
||||
"TOKEN_A_ADDRESS": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", # WBNB
|
||||
"TOKEN_B_ADDRESS": "0x55d398326f99059fF775485246999027B3197955", # USDT
|
||||
"WRAPPED_NATIVE_ADDRESS": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
||||
"POOL_FEE": 100,
|
||||
"EDGE_CLEANUP_MARGIN_PCT": Decimal("0.1875"), # 0.1875 only for asymmetric shedge % of range width used for edge detection
|
||||
"RANGE_WIDTH_PCT": Decimal("0.004"),
|
||||
"TARGET_INVESTMENT_AMOUNT": 1000,
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.015"),
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": Decimal("0.10"),
|
||||
"EDGE_PROXIMITY_PCT": Decimal("0.015"),
|
||||
"DYNAMIC_THRESHOLD_MULTIPLIER": Decimal("1.1"),
|
||||
"MIN_TIME_BETWEEN_TRADES": 20,
|
||||
"ENABLE_FISHING": False,
|
||||
"FISHING_ORDER_SIZE_PCT": Decimal("0.05"),
|
||||
"MAKER_ORDER_TIMEOUT": 180,
|
||||
"FISHING_TIMEOUT_FALLBACK": 60,
|
||||
},
|
||||
"WETH_CBBTC_BASE": {
|
||||
"NAME": "Aerodrome/Uni (Base) - WETH/cbBTC",
|
||||
"COIN_SYMBOL": "ETH",
|
||||
"RPC_ENV_VAR": "BASE_RPC_URL",
|
||||
"NPM_ADDRESS": "0x0000000000000000000000000000000000000000", # Placeholder
|
||||
"ROUTER_ADDRESS": "0x0000000000000000000000000000000000000000", # Placeholder
|
||||
"TOKEN_A_ADDRESS": "0x4200000000000000000000000000000000000006", # WETH (Base)
|
||||
"TOKEN_B_ADDRESS": "0xcbB7C915AB58735a1391B9fE18541b4d8926D412", # cbBTC (Base)
|
||||
"WRAPPED_NATIVE_ADDRESS": "0x4200000000000000000000000000000000000006",
|
||||
"POOL_FEE": 3000,
|
||||
"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.0075"),
|
||||
"TARGET_INVESTMENT_AMOUNT": 3000,
|
||||
"HEDGE_STRATEGY": "FIXED",
|
||||
}
|
||||
}
|
||||
|
||||
# --- HELPER TO GET ACTIVE CONFIG ---
|
||||
def get_current_config():
|
||||
profile = CLP_PROFILES.get(TARGET_DEX)
|
||||
if not profile:
|
||||
raise ValueError(f"Unknown CLP profile: {TARGET_DEX}")
|
||||
|
||||
# Merge Default Strategy with Profile (Profile wins)
|
||||
config = DEFAULT_STRATEGY.copy()
|
||||
config.update(profile)
|
||||
|
||||
return config
|
||||
1276
clp_hedger.py
1276
clp_hedger.py
File diff suppressed because it is too large
Load Diff
1199
clp_manager.py
Normal file
1199
clp_manager.py
Normal file
File diff suppressed because it is too large
Load Diff
57
doc/CHANGELOG.md
Normal file
57
doc/CHANGELOG.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [2025-12-21]
|
||||
|
||||
### Optimization
|
||||
- **Low Volatility Optimization**:
|
||||
- Introduced `BASE_REBALANCE_THRESHOLD_PCT` configuration flag in `clp_hedger.py`.
|
||||
- Increased base rebalance threshold from **0.05 (5%)** to **0.08 (8%)** to reduce "churn" (excessive trading) during low volatility/sideways markets.
|
||||
- This change aims to improve net profitability by ensuring that rebalancing fees are only paid when the delta imbalance is statistically significant.
|
||||
- **Logging Improvements**:
|
||||
- Updated `[IDLE]` log format to show estimated **BUY (B)** and **SELL (S)** trigger price levels instead of raw delta difference. This provides better visual feedback on how far the price is from the next rebalance.
|
||||
- **Fishing Order Logic**:
|
||||
- Implemented a "Fishing Order" mechanism that keeps a Maker order (10% of hedge size) resting at the `entry_price` while in the safe zone.
|
||||
- **Logic Update:** Configured to always place a **Limit BUY** at the entry price (when Px > Entry) to reduce the hedge size at break-even.
|
||||
- **Fix:** Updated position tracking to use the actual Hedge Entry Price from Hyperliquid instead of the LP Strategy entry price.
|
||||
- Integrated fishing order tracking to distinguish it from standard rebalance orders.
|
||||
|
||||
## [2025-12-20]
|
||||
|
||||
### Added
|
||||
- **Dynamic Rebalance Threshold**: Implemented volatility-based threshold adjustment in `clp_hedger.py`.
|
||||
- **Volatility Metric**: Added `calculate_volatility()` using rolling Standard Deviation (5-minute window).
|
||||
- **Adaptive Logic**: Rebalance threshold now scales (1.0x - 3.0x) based on market volatility to reduce fee costs during "chop" (noise) while maintaining precision during trends.
|
||||
- **Safety Cap**: Threshold is capped at 20% of the range width to prevent dangerous unhedged exposure in tight ranges.
|
||||
- **Log Explanation**: `(Vol: 0.029% x1.0 | Thresh: 7.4%)`
|
||||
- **Vol:** Standard Deviation of last 300 prices relative to mean.
|
||||
- **x1.0 (Multiplier):** Scales based on reference volatility (0.05%). Multiplier = `max(1.0, Vol / 0.05%)`.
|
||||
- **Thresh:** The % of max delta deviation required to trigger a trade. Calculated as `Base (Range Dependent) * Multiplier`.
|
||||
- **Execution Analysis Logs**: Added Bid/Ask book state to the `[TRIG]` log in `clp_hedger.py`. This data enables "Shadow Logging" to compare current Taker execution costs against theoretical Maker savings (Spread capture + Rebates).
|
||||
- **Shadow Order Simulator**: Implemented a simulation engine in `clp_hedger.py` to verify if Maker orders would have filled.
|
||||
- **Trigger**: Created automatically alongside every Taker trade.
|
||||
- **Logic**: Tracks if the market price crosses the passive Maker price within a timeframe.
|
||||
- **Extended Timeout**: Fixed at **600s (10 min)** to capture the full distribution of fill times. This data will help determine the optimal "Time to Live" for future Maker strategies.
|
||||
- **Goal**: Gather empirical data to determine if switching to Maker orders is profitable.
|
||||
|
||||
### Documentation
|
||||
- **Analysis**: Completed optimization analysis in `todo/optymalization rebalance_threshol.md` with technical justification for using StdDev over ATR.
|
||||
|
||||
## [2025-12-19]
|
||||
|
||||
### Added
|
||||
- **Dynamic Edge Proximity**: Implemented `get_dynamic_edge_proximity` in `clp_hedger.py` and `clp_scalper_hedger.py`. This scales the edge protection buffer based on position size (Base 4% + 4% per $10k, capped at 15%) to better protect larger positions.
|
||||
- **Large Hedge Override**: Added logic to bypass trade cooldowns in `clp_hedger.py` when a rebalance requirement significantly exceeds the threshold (`LARGE_HEDGE_MULTIPLIER`).
|
||||
|
||||
### Fixed
|
||||
- **Double Logging**: Resolved duplicate log entries in the terminal by setting `logger.propagate = False` in both `clp_hedger.py` and `clp_scalper_hedger.py` and cleaning up root logger handlers.
|
||||
- **Bug Fixes**: Fixed a `NameError` (undefined `dynamic_buffer`) and an `IndentationError` in `clp_hedger.py`.
|
||||
|
||||
### Removed
|
||||
- **clp_scalper_hedger.py**: Removed the scalper-hedger script as the main `clp_hedger.py` now includes all necessary edge protection and dynamic proximity features.
|
||||
|
||||
### Configuration Changes
|
||||
- **Weekend Strategy Update**:
|
||||
- Updated `uniswap_manager.py`: Increased capital to $2,000 (`TARGET_INVESTMENT_VALUE_USDC`) and set a tighter range of +/- 1% (`RANGE_WIDTH_PCT = 0.01`).
|
||||
- Updated `clp_hedger.py`: Lowered `MIN_THRESHOLD_ETH` to 0.008 for finer control and reduced `DYNAMIC_THRESHOLD_MULTIPLIER` to 1.2 for lower volatility environment.
|
||||
104
doc/CLP_HEDGING_SIMULATIONS_2026.md
Normal file
104
doc/CLP_HEDGING_SIMULATIONS_2026.md
Normal file
@ -0,0 +1,104 @@
|
||||
# CLP Hedging Strategy Matrix (Jan 2026)
|
||||
|
||||
*Based on ETH/USDC 0.05% Arbitrum Pool Stats: TVL $74.53M, 24h Vol $23.27M.*
|
||||
*Calculations include 0.045% round-trip Hyperliquid fees and concentration-adjusted Uniswap fees.*
|
||||
|
||||
This document outlines the projected PnL, required hedge sizes, and estimated earnings for different CLP configurations, assuming a fixed hedge position is maintained from entry to exit.
|
||||
|
||||
## Technical Assumptions & Methodology
|
||||
1. **Transaction Costs:** Calculations include Hyperliquid fees (~0.045% round-trip: 0.01% Maker Entry + 0.035% Taker Exit).
|
||||
2. **Uniswap Fees:** Est. Fees (1h) are calculated using the pool's volume/TVL ratio and a concentration multiplier based on the range width.
|
||||
3. **Static Hedge:** Assumes the hedge position is opened at the exact same time as the CLP and is **never rebalanced** until the CLP is closed at the range boundary.
|
||||
4. **Delta Calculation:**
|
||||
* **Lower Edge:** Position becomes 100% ETH (Max Long Delta).
|
||||
* **Upper Edge:** Position becomes 100% USDC (Zero Delta).
|
||||
|
||||
---
|
||||
|
||||
## Capital: $1000 USDC
|
||||
| Range | Strategy | Hedge Size | Margin (5x) | PnL Lower | PnL Upper | Est. Fees (1h) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| +/- 0.5 % | Neutral Down | 0.2746 ETH | $167.94 | +0.00 | -3.40 | $1.30 |
|
||||
| | Neutral Up | 0.0520 ETH | $31.81 | -3.40 | +0.00 | |
|
||||
| | Fixed | 0.1631 ETH | $99.75 | -1.70 | -1.70 | |
|
||||
| +/- 1.0 % | Neutral Down | 0.2598 ETH | $158.88 | -0.00 | -5.91 | $0.65 |
|
||||
| | Neutral Up | 0.0664 ETH | $40.63 | -5.91 | +0.00 | |
|
||||
| | Fixed | 0.1627 ETH | $99.50 | -2.97 | -2.94 | |
|
||||
| +/- 2.0 % | Neutral Down | 0.2522 ETH | $154.26 | +0.00 | -10.95 | $0.33 |
|
||||
| | Neutral Up | 0.0732 ETH | $44.76 | -10.95 | +0.00 | |
|
||||
| | Fixed | 0.1619 ETH | $99.00 | -5.53 | -5.42 | |
|
||||
| +/- 4.0 % | Neutral Down | 0.2482 ETH | $151.78 | +0.00 | -21.10 | $0.17 |
|
||||
| | Neutral Up | 0.0757 ETH | $46.28 | -21.10 | +0.00 | |
|
||||
| | Fixed | 0.1603 ETH | $98.02 | -10.75 | -10.35 | |
|
||||
| +/- 5.0 % | Neutral Down | 0.2473 ETH | $151.22 | +0.00 | -26.21 | $0.13 |
|
||||
| | Neutral Up | 0.0758 ETH | $46.37 | -26.21 | +0.00 | |
|
||||
| | Fixed | 0.1595 ETH | $97.53 | -13.42 | -12.79 | |
|
||||
| +/- 10.0 % | Neutral Down | 0.2450 ETH | $149.84 | +0.00 | -52.16 | $0.07 |
|
||||
| | Neutral Up | 0.0744 ETH | $45.52 | -52.16 | +0.00 | |
|
||||
| | Fixed | 0.1555 ETH | $95.12 | -27.36 | -24.80 | |
|
||||
|
||||
## Capital: $2000 USDC
|
||||
| Range | Strategy | Hedge Size | Margin (5x) | PnL Lower | PnL Upper | Est. Fees (1h) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| +/- 0.5 % | Neutral Down | 0.5492 ETH | $335.88 | +0.00 | -6.81 | $2.61 |
|
||||
| | Neutral Up | 0.1040 ETH | $63.63 | -6.81 | -0.00 | |
|
||||
| | Fixed | 0.3262 ETH | $199.50 | -3.41 | -3.40 | |
|
||||
| +/- 1.0 % | Neutral Down | 0.5196 ETH | $317.75 | +0.00 | -11.83 | $1.31 |
|
||||
| | Neutral Up | 0.1329 ETH | $81.25 | -11.83 | -0.00 | |
|
||||
| | Fixed | 0.3254 ETH | $199.00 | -5.94 | -5.89 | |
|
||||
| +/- 2.0 % | Neutral Down | 0.5045 ETH | $308.52 | +0.00 | -21.90 | $0.66 |
|
||||
| | Neutral Up | 0.1464 ETH | $89.51 | -21.90 | -0.00 | |
|
||||
| | Fixed | 0.3238 ETH | $198.01 | -11.05 | -10.85 | |
|
||||
| +/- 4.0 % | Neutral Down | 0.4964 ETH | $303.56 | +0.00 | -42.20 | $0.33 |
|
||||
| | Neutral Up | 0.1514 ETH | $92.56 | -42.20 | +0.00 | |
|
||||
| | Fixed | 0.3206 ETH | $196.04 | -21.50 | -20.70 | |
|
||||
| +/- 5.0 % | Neutral Down | 0.4946 ETH | $302.44 | +0.00 | -52.43 | $0.27 |
|
||||
| | Neutral Up | 0.1517 ETH | $92.74 | -52.43 | +0.00 | |
|
||||
| | Fixed | 0.3190 ETH | $195.06 | -26.85 | -25.58 | |
|
||||
| +/- 10.0 % | Neutral Down | 0.4900 ETH | $299.68 | +0.00 | -104.31 | $0.14 |
|
||||
| | Neutral Up | 0.1489 ETH | $91.05 | -104.31 | +0.00 | |
|
||||
| | Fixed | 0.3111 ETH | $190.23 | -54.72 | -49.59 | |
|
||||
|
||||
## Capital: $4000 USDC
|
||||
| Range | Strategy | Hedge Size | Margin (5x) | PnL Lower | PnL Upper | Est. Fees (1h) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| +/- 0.5 % | Neutral Down | 1.0985 ETH | $671.75 | -0.00 | -13.61 | $5.22 |
|
||||
| | Neutral Up | 0.2081 ETH | $127.25 | -13.61 | +0.00 | |
|
||||
| | Fixed | 0.6525 ETH | $399.00 | -6.82 | -6.79 | |
|
||||
| +/- 1.0 % | Neutral Down | 1.0392 ETH | $635.51 | -0.00 | -23.65 | $2.61 |
|
||||
| | Neutral Up | 0.2657 ETH | $162.51 | -23.65 | +0.00 | |
|
||||
| | Fixed | 0.6508 ETH | $398.00 | -11.88 | -11.77 | |
|
||||
| +/- 2.0 % | Neutral Down | 1.0090 ETH | $617.03 | +0.00 | -43.80 | $1.31 |
|
||||
| | Neutral Up | 0.2928 ETH | $179.03 | -43.80 | +0.00 | |
|
||||
| | Fixed | 0.6476 ETH | $396.02 | -22.10 | -21.70 | |
|
||||
| +/- 4.0 % | Neutral Down | 0.9928 ETH | $607.12 | +0.00 | -84.40 | $0.66 |
|
||||
| | Neutral Up | 0.3027 ETH | $185.12 | -84.40 | +0.00 | |
|
||||
| | Fixed | 0.6411 ETH | $392.08 | -43.01 | -41.39 | |
|
||||
| +/- 5.0 % | Neutral Down | 0.9891 ETH | $604.89 | +0.00 | -104.85 | $0.53 |
|
||||
| | Neutral Up | 0.3033 ETH | $185.48 | -104.85 | +0.00 | |
|
||||
| | Fixed | 0.6379 ETH | $390.12 | -53.69 | -51.16 | |
|
||||
| +/- 10.0 % | Neutral Down | 0.9801 ETH | $599.36 | +0.00 | -208.63 | $0.27 |
|
||||
| | Neutral Up | 0.2978 ETH | $182.10 | -208.63 | +0.00 | |
|
||||
| | Fixed | 0.6222 ETH | $380.46 | -109.45 | -99.18 | |
|
||||
|
||||
## Capital: $8000 USDC
|
||||
| Range | Strategy | Hedge Size | Margin (5x) | PnL Lower | PnL Upper | Est. Fees (1h) |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| +/- 0.5 % | Neutral Down | 2.1970 ETH | $1343.50 | +0.00 | -27.23 | $10.43 |
|
||||
| | Neutral Up | 0.4162 ETH | $254.50 | -27.23 | +0.00 | |
|
||||
| | Fixed | 1.3049 ETH | $798.00 | -13.64 | -13.59 | |
|
||||
| +/- 1.0 % | Neutral Down | 2.0784 ETH | $1271.02 | -0.00 | -47.30 | $5.23 |
|
||||
| | Neutral Up | 0.5315 ETH | $325.01 | -47.30 | +0.00 | |
|
||||
| | Fixed | 1.3017 ETH | $796.01 | -23.75 | -23.55 | |
|
||||
| +/- 2.0 % | Neutral Down | 2.0180 ETH | $1234.06 | +0.00 | -87.60 | $2.63 |
|
||||
| | Neutral Up | 0.5855 ETH | $358.06 | -87.60 | +0.00 | |
|
||||
| | Fixed | 1.2952 ETH | $792.04 | -44.20 | -43.40 | |
|
||||
| +/- 4.0 % | Neutral Down | 1.9856 ETH | $1214.24 | +0.00 | -168.80 | $1.33 |
|
||||
| | Neutral Up | 0.6054 ETH | $370.23 | -168.80 | -0.00 | |
|
||||
| | Fixed | 1.2823 ETH | $784.16 | -86.02 | -82.78 | |
|
||||
| +/- 5.0 % | Neutral Down | 1.9783 ETH | $1209.78 | +0.00 | -209.70 | $1.07 |
|
||||
| | Neutral Up | 0.6066 ETH | $370.96 | -209.70 | -0.00 | |
|
||||
| | Fixed | 1.2759 ETH | $780.24 | -107.38 | -102.32 | |
|
||||
| +/- 10.0 % | Neutral Down | 1.9602 ETH | $1198.71 | +0.00 | -417.26 | $0.55 |
|
||||
| | Neutral Up | 0.5956 ETH | $364.20 | -417.26 | +0.00 | |
|
||||
| | Fixed | 1.2443 ETH | $760.93 | -218.89 | -198.36 | |
|
||||
BIN
doc/CLP_HEDGING_SIMULATIONS_2026_GRID.md
Normal file
BIN
doc/CLP_HEDGING_SIMULATIONS_2026_GRID.md
Normal file
Binary file not shown.
46
doc/GEMINI.md
Normal file
46
doc/GEMINI.md
Normal file
@ -0,0 +1,46 @@
|
||||
# GEMINI Project Context & Setup
|
||||
|
||||
**Last Updated:** 2025-12-26
|
||||
**Project:** Uniswap V3 Automated Concentrated Liquidity Pool (CLP) Hedger
|
||||
|
||||
## 1. Project Overview
|
||||
This project automates the management and hedging of Uniswap V3 Concentrated Liquidity Positions (CLP). It consists of two main components:
|
||||
* **`uniswap_manager.py`**: Monitors the market, mints/burns Uniswap V3 positions based on range and profitability, and handles rebalancing.
|
||||
* **`clp_hedger.py`**: A delta-neutral hedging bot that executes trades on Hyperliquid to offset the delta exposure of the Uniswap position.
|
||||
|
||||
## 2. Current Configuration (Weekend / Low Volatility)
|
||||
**Date Set:** 2025-12-19
|
||||
|
||||
### A. Uniswap Manager Settings
|
||||
* **Capital Target:** `$2,000` (USDC equivalent)
|
||||
* **Range Width:** `+/- 1%` (0.01) relative to entry price.
|
||||
* **Slippage Tolerance:** `2%` (0.02)
|
||||
|
||||
### B. Hedger Settings (Hyperliquid)
|
||||
* **Minimum Trade Threshold:** `0.008 ETH` (~$24 USD)
|
||||
* *Reasoning:* Tighter threshold for precise hedging in a narrow 1% range.
|
||||
* **Dynamic Threshold Multiplier:** `1.2x`
|
||||
* *Reasoning:* Reduced volatility buffer for stable weekend conditions.
|
||||
* **Price Buffer:** `0.15%`
|
||||
|
||||
### C. Safety Mechanisms
|
||||
1. **Dynamic Edge Proximity:**
|
||||
* **Logic:** Calculates a dynamic safety buffer based on position size to prevent slippage on large hedges near range edges.
|
||||
* **Formula:** `Base 4% + (0.000004 * Position Value USD)`
|
||||
* **Limits:** Min 4%, Max 15%.
|
||||
* **Current Effect:** For a $2,000 position, the edge buffer is approx **4.8%**.
|
||||
2. **Large Hedge Override:**
|
||||
* **Logic:** Bypasses trade cooldowns if the required hedge size exceeds `2.8x` the rebalance threshold.
|
||||
3. **Cooldowns:**
|
||||
* `MIN_TIME_BETWEEN_TRADES`: 25 seconds (bypassed for critical/urgent hedges).
|
||||
|
||||
## 3. Recent Changes & Status
|
||||
* **Refactoring:** Removed `clp_scalper_hedger.py` after merging its advanced features into `clp_hedger.py`.
|
||||
* **Logging:** Fixed duplicate terminal output by disabling logger propagation.
|
||||
* **Feature:** Implemented "Comprehensive Edge Protection" in `clp_hedger.py` (Dynamic Proximity + Large Hedge Override).
|
||||
* **Logic:** Disabled Volatility Multiplier when price is strictly inside range edges (to prevent hedging pauses during volatility spikes when safe).
|
||||
|
||||
## 4. Key Files
|
||||
* `uniswap_manager.py`: Core logic for Uniswap V3 interaction.
|
||||
* `clp_hedger.py`: Core logic for Hyperliquid hedging.
|
||||
* `doc/CHANGELOG.md`: Detailed history of changes.
|
||||
95
doc/GIT_AGENT_INTEGRATION_COMPLETE.md
Normal file
95
doc/GIT_AGENT_INTEGRATION_COMPLETE.md
Normal file
@ -0,0 +1,95 @@
|
||||
# Git Agent Integration Summary - Complete ✅
|
||||
|
||||
## 🎯 Integration Achieved
|
||||
|
||||
Your Git Agent is now fully integrated with OpenCode and ready for production use!
|
||||
|
||||
### ✅ What Was Created
|
||||
|
||||
**📁 Complete File Structure:**
|
||||
```
|
||||
tools/
|
||||
├── git_agent.py # Main automation script (fixed Unicode issues)
|
||||
├── git_opencode.py # OpenCode direct commands (NEW)
|
||||
├── git_slash.py # Slash commands backup
|
||||
├── slash_commands_main.py # Slash command orchestrator
|
||||
├── agent_config.json # Configuration with Gitea settings
|
||||
├── git_utils.py # Git operations wrapper
|
||||
├── backup_manager.py # Backup branch management
|
||||
├── change_detector.py # File change analysis
|
||||
├── cleanup_manager.py # 100-backup rotation
|
||||
├── commit_formatter.py # Detailed commit messages
|
||||
└── README_GIT_AGENT.md # Complete documentation
|
||||
```
|
||||
|
||||
**🚀 Two Integration Options Available:**
|
||||
|
||||
1. **Direct Commands (Recommended):**
|
||||
```bash
|
||||
python tools/git_opencode.py backup
|
||||
python tools/git_opencode.py status
|
||||
python tools/git_opencode.py cleanup
|
||||
python tools/git_opencode.py restore 2025-12-19-14
|
||||
```
|
||||
|
||||
2. **Slash Commands (Advanced):**
|
||||
```bash
|
||||
python tools/git_slash.py git-status
|
||||
python tools/git_slash.py git-backup
|
||||
python tools/git_slash.py git-cleanup
|
||||
python tools/git_slash.py git-restore 2025-12-19-14
|
||||
```
|
||||
|
||||
### 📊 Current System Status
|
||||
|
||||
**✅ Active Backups:** 4 total
|
||||
**✅ Remote Connected:** Gitea server working
|
||||
**✅ Integration:** Direct commands ready in OpenCode
|
||||
**✅ Main Branch:** Clean and under your control
|
||||
**✅ Security:** All backups exclude sensitive files
|
||||
|
||||
### 🎯 Ready for OpenCode Use
|
||||
|
||||
You can now tell me in OpenCode:
|
||||
|
||||
1. **"Create backup"** → I'll run `python tools/git_opencode.py backup`
|
||||
2. **"Check status"** → I'll run `python tools/git_opencode.py status`
|
||||
3. **"Restore from 2 hours ago"** → I'll run `python tools/git_opencode.py restore 2025-12-19-14`
|
||||
4. **"Clean old backups"** → I'll run `python tools/git_opencode.py cleanup`
|
||||
|
||||
### 🔧 Automated Scheduling
|
||||
|
||||
**Set up hourly backups** with Task Scheduler:
|
||||
```powershell
|
||||
schtasks /create /tn "Git Backup" /tr "python tools/git_opencode.py backup" /sc hourly
|
||||
```
|
||||
|
||||
### 💡 Usage Workflow
|
||||
|
||||
**Normal Development:**
|
||||
1. Tell me: "Create backup"
|
||||
2. Make your changes to clp_hedger.py or uniswap_manager.py
|
||||
3. Tell me: "Check status"
|
||||
4. Push to main when ready: `git add . && git commit -m "message" && git push origin main`
|
||||
|
||||
**Emergency Recovery:**
|
||||
1. Tell me: "Check status"
|
||||
2. Choose backup from list I show
|
||||
3. Tell me: "Restore from backup-2025-12-19-14"
|
||||
4. Fix issues and return to main: `git checkout main`
|
||||
|
||||
### 🎉 Integration Benefits Achieved
|
||||
|
||||
✅ **Zero Friction** - Just tell me what you need
|
||||
✅ **Voice Control** - Natural language Git operations
|
||||
✅ **Automated Backups** - Continuous protection without intervention
|
||||
✅ **Emergency Recovery** - Quick rollback from any point
|
||||
✅ **Parameter Tracking** - Automatic detection of trading strategy changes
|
||||
✅ **Remote Storage** - Offsite backup to your Gitea server
|
||||
✅ **Security First** - All sensitive files excluded automatically
|
||||
✅ **100-Backup Rotation** - Efficient storage management
|
||||
✅ **Non-Intrusive** - Your main workflow stays completely manual
|
||||
|
||||
## 🚀 Your System Is Production Ready!
|
||||
|
||||
Your Uniswap Auto CLP project now has enterprise-grade Git automation integrated with OpenCode. Start using it immediately - no additional setup required!
|
||||
34
doc/TELEGRAM_QUICKSTART.md
Normal file
34
doc/TELEGRAM_QUICKSTART.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Quick Start Guide
|
||||
|
||||
## 1. Test the Monitor
|
||||
```bash
|
||||
cd tools
|
||||
python test_telegram_simple.py
|
||||
```
|
||||
|
||||
## 2. Setup Telegram Bot (Optional)
|
||||
```bash
|
||||
python telegram_setup.py
|
||||
```
|
||||
|
||||
## 3. Configure Environment
|
||||
Copy `tools/.env.example` to `.env` and add:
|
||||
```bash
|
||||
TELEGRAM_MONITOR_ENABLED=True
|
||||
TELEGRAM_BOT_TOKEN=your_token_here
|
||||
TELEGRAM_CHAT_ID=your_chat_id_here
|
||||
```
|
||||
|
||||
## 4. Run Monitor
|
||||
```bash
|
||||
python tools/telegram_monitor.py
|
||||
```
|
||||
|
||||
## Files Created
|
||||
- `tools/telegram_monitor.py` - Main monitoring script
|
||||
- `tools/telegram_setup.py` - Setup helper
|
||||
- `tools/test_telegram_simple.py` - Test script
|
||||
- `tools/.env.example` - Environment template
|
||||
- `tools/README_TELEGRAM.md` - Full documentation
|
||||
|
||||
Monitor checks `hedge_status.json` every 60 seconds for new positions and sends formatted notifications to Telegram.
|
||||
10
florida/.gemini/settings.json
Normal file
10
florida/.gemini/settings.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"ui": {
|
||||
"useAlternateBuffer": true,
|
||||
"incrementalRendering": true,
|
||||
"multiline": true
|
||||
},
|
||||
"tools": {
|
||||
"truncateToolOutputLines": 10000
|
||||
}
|
||||
}
|
||||
44
florida/AERODROME_BASE_CL_status.json
Normal file
44
florida/AERODROME_BASE_CL_status.json
Normal 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
|
||||
}
|
||||
]
|
||||
90
florida/GEMINI.md
Normal file
90
florida/GEMINI.md
Normal file
@ -0,0 +1,90 @@
|
||||
# Uniswap Auto CLP & Delta-Neutral Hedger
|
||||
|
||||
## Project Overview
|
||||
This project is an automated high-frequency trading system designed to provide **Concentrated Liquidity (CLP)** on Uniswap V3 (and forks) while simultaneously **hedging delta exposure** on Hyperliquid.
|
||||
|
||||
The goal is to capture trading fees from the Liquidity Pool (LP) while neutralizing the price risk of the underlying assets (Impermanent Loss protection). The system operates as a **Delta-Zero Scalper**, effectively farming yields with reduced market exposure.
|
||||
|
||||
## Architecture
|
||||
The system consists of three independent Python processes that coordinate via shared JSON state files.
|
||||
|
||||
### 1. Position Manager (`clp_manager.py`)
|
||||
* **Role:** Active Liquidity Provision.
|
||||
* **Functionality:**
|
||||
* Connects to EVM chains (Arbitrum, BNB Chain, Base) via Web3.
|
||||
* Monitors `{TARGET_DEX}_status.json` and on-chain pool state.
|
||||
* **Auto-Entry:** Detects when no position exists, calculates optimal tick ranges based on `RANGE_WIDTH_PCT`, and mints a new NFT position.
|
||||
* **Auto-Wrap:** Automatically wraps native tokens (ETH -> WETH) if balances are insufficient.
|
||||
* **Auto-Exit:** Detects out-of-range positions, removes liquidity, and collects fees (`CLOSE_POSITION_ENABLED`).
|
||||
* **State Sync:** Updates `{TARGET_DEX}_status.json` with entry price, amounts, and token IDs for the Hedger.
|
||||
|
||||
### 2. Delta Hedger (`clp_hedger.py`)
|
||||
* **Role:** Delta Neutralization (Risk Management).
|
||||
* **Functionality:**
|
||||
* Connects to Hyperliquid (Perp DEX).
|
||||
* Reads `{TARGET_DEX}_status.json` to understand the current LP position's delta profile.
|
||||
* **Dynamic Hedging:** Calculates the precise `Gamma` (rate of change of delta) and rebalances the short position to keep Net Delta close to zero.
|
||||
* **Scalping:** Uses "Fishing Orders" (Maker orders) and volatility-adjusted thresholds to profit from hedging rebalances rather than just paying taker fees.
|
||||
* **Safety:** Includes emergency closures, edge protection logic (to avoid hedging at max loss points), and disconnect protection.
|
||||
|
||||
### 3. Monitoring (`telegram_monitor.py`)
|
||||
* **Role:** Alerting.
|
||||
* **Functionality:**
|
||||
* Watches `{TARGET_DEX}_status.json` for state transitions (OPEN -> CLOSED).
|
||||
* Sends real-time notifications to Telegram with PnL summaries, duration, and fees collected.
|
||||
|
||||
## Key Files & Directories
|
||||
|
||||
| File/Dir | Description |
|
||||
| :--- | :--- |
|
||||
| `clp_manager.py` | Main logic for Uniswap V3 interaction (Mint/Burn/Collect). |
|
||||
| `clp_hedger.py` | Main logic for Hyperliquid hedging (Open/Close/Rebalance). |
|
||||
| `clp_config.py` | Configuration profiles (Chain IDs, Contract Addresses) and Strategy settings. |
|
||||
| `telegram_monitor.py` | Telegram bot for notifications. |
|
||||
| `{TARGET_DEX}_status.json` | **Critical:** Shared state file acting as the database between Manager and Hedger. |
|
||||
| `.env` | Stores secrets (Private Keys, RPCs). **Do not commit.** |
|
||||
| `tests/backtest/` | **New:** Professional Backtesting & Optimization Framework. |
|
||||
| `tools/` | Utility scripts, including the Git Agent for auto-backups. |
|
||||
| `logs/` | Detailed logs for all processes. |
|
||||
|
||||
## Backtesting Framework (Jan 2026 Update)
|
||||
A robust simulation engine has been implemented to validate strategies before capital commitment.
|
||||
|
||||
### Components
|
||||
* **`tests/backtest/backtester.py`**: Event-driven engine mocking Web3/Hyperliquid interactions.
|
||||
* **`tests/backtest/mocks.py`**: Stateful simulator handling balance tracking, V3 tick math, and fee accrual.
|
||||
* **`tests/backtest/grid_search.py`**: Optimization runner to test parameter combinations (Range Width, Hedging Threshold).
|
||||
* **`tests/backtest/analyze_results.py`**: Helper to interpret simulation CSV results.
|
||||
|
||||
### Progress Status (Jan 1, 2026)
|
||||
* **Completed:**
|
||||
* Simulation loop runs end-to-end (Mint -> Accrue Fees -> Close).
|
||||
* Fixed Mock Pricing logic (handling inverted T0/T1 pairs like USDT/WBNB).
|
||||
* Implemented realistic Fee Accrual based on Trade Volume + Market Share.
|
||||
* Verified "In Range" detection and position lifecycle.
|
||||
* **Pending / Next Steps:**
|
||||
* **Hedger PnL Verification:** Simulation showed 0.0 Hedging Fees because the price volatility in the 1-day sample was too low to trigger the 10% rebalance threshold. We are lowering thresholds to 1% to force activity and verify costs.
|
||||
* **NAV Calculation:** Refine "Total PnL" to include Unrealized PnL from both LP and Hedge to handle Impermanent Loss correctly.
|
||||
* **Final Optimization:** Run the `grid_search.py` with the corrected Market Share (0.02%) and lower thresholds to find the profitable "Sweet Spot".
|
||||
|
||||
## Logic Details
|
||||
|
||||
### Hedging Mathematics
|
||||
The hedger calculates the **Pool Delta** derived from the V3 liquidity math:
|
||||
$$ \Delta_{LP} = L \times (\frac{1}{\sqrt{P}} - \frac{1}{\sqrt{P_{upper}}}) $$
|
||||
(For Token0/Token1 where we hedge Token0).
|
||||
|
||||
It then maintains a Short Position ($S$) such that:
|
||||
$$ S \approx \Delta_{LP} $$
|
||||
|
||||
### Rebalancing
|
||||
Rebalancing is triggered when:
|
||||
1. **Delta Drift:** $|S - \Delta_{LP}| > Threshold$
|
||||
2. **Volatility:** Thresholds expand during high volatility to reduce churn (fees).
|
||||
3. **Edge Proximity:** Hedging logic changes when price approaches the range boundaries to prevent "buying high/selling low" behavior at the edges.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* **Logs:** Check `logs/clp_manager.log` and `logs/clp_hedger.log` first.
|
||||
* **Stuck Position:** If a position is closed on-chain but `{TARGET_DEX}_status.json` says `OPEN`, manually edit the JSON status to `CLOSED` or delete the entry (with caution).
|
||||
* **RPC Errors:** Ensure your RPC URLs in `.env` are active and have sufficient rate limits.
|
||||
682
florida/PANCAKESWAP_BNB_status.json
Normal file
682
florida/PANCAKESWAP_BNB_status.json
Normal file
@ -0,0 +1,682 @@
|
||||
[
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6146792,
|
||||
"status": "CLOSED",
|
||||
"target_value": 994.02,
|
||||
"entry_price": 842.2702,
|
||||
"amount0_initial": 494.0331,
|
||||
"amount1_initial": 0.5936,
|
||||
"liquidity": "3468400810004249810484",
|
||||
"range_upper": 850.7027,
|
||||
"range_lower": 834.0253,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1766872414,
|
||||
"initial_hedge_usdc": 1004.058551,
|
||||
"hedge_equity_usd": 0.0,
|
||||
"hedge_pnl_realized": -87.35,
|
||||
"hedge_fees_paid": 42.87,
|
||||
"target_value_end": 997.47,
|
||||
"timestamp_close": 1766919846
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6149306,
|
||||
"status": "CLOSED",
|
||||
"target_value": 997.66,
|
||||
"entry_price": 851.2687,
|
||||
"amount0_initial": 500.0087,
|
||||
"amount1_initial": 0.5846,
|
||||
"liquidity": "3462645216513961883832",
|
||||
"range_upper": 859.7676,
|
||||
"range_lower": 842.9125,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1766919922,
|
||||
"initial_hedge_usdc": 908.9771,
|
||||
"hedge_equity_usd": 904.232487,
|
||||
"hedge_pnl_realized": -2.07,
|
||||
"hedge_fees_paid": 0.95,
|
||||
"target_value_end": 1000.83,
|
||||
"timestamp_close": 1766937096
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6150494,
|
||||
"status": "CLOSED",
|
||||
"target_value": 991.84,
|
||||
"entry_price": 861.6614,
|
||||
"amount0_initial": 491.8656,
|
||||
"amount1_initial": 0.5802,
|
||||
"liquidity": "3421642374594297568197",
|
||||
"range_upper": 870.3205,
|
||||
"range_lower": 853.2585,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1766937178
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6151763,
|
||||
"status": "CLOSED",
|
||||
"target_value": 199.28,
|
||||
"entry_price": 857.718,
|
||||
"amount0_initial": 99.2854,
|
||||
"amount1_initial": 0.1166,
|
||||
"liquidity": "461487203975551730969",
|
||||
"range_upper": 870.5816,
|
||||
"range_lower": 845.1913,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1766960125
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6151784,
|
||||
"status": "CLOSED",
|
||||
"target_value": 997.07,
|
||||
"entry_price": 858.2809,
|
||||
"amount0_initial": 497.0759,
|
||||
"amount1_initial": 0.5826,
|
||||
"liquidity": "2308213453823846434158",
|
||||
"range_upper": 871.1041,
|
||||
"range_lower": 845.6986,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1766960550,
|
||||
"target_value_end": 1001.56,
|
||||
"timestamp_close": 1766982067
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6153292,
|
||||
"status": "CLOSED",
|
||||
"target_value": 993.31,
|
||||
"entry_price": 869.418,
|
||||
"amount0_initial": 500.0094,
|
||||
"amount1_initial": 0.5674,
|
||||
"liquidity": "2284728345715808667084",
|
||||
"range_upper": 882.4136,
|
||||
"range_lower": 856.6782,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1766982584,
|
||||
"target_value_end": 982.48,
|
||||
"timestamp_close": 1767000734
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6154897,
|
||||
"status": "CLOSED",
|
||||
"target_value": 998.49,
|
||||
"entry_price": 849.3437,
|
||||
"amount0_initial": 498.5177,
|
||||
"amount1_initial": 0.5887,
|
||||
"liquidity": "2323641520769318237803",
|
||||
"range_upper": 862.092,
|
||||
"range_lower": 836.9493,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767001797,
|
||||
"target_value_end": 1005.08,
|
||||
"timestamp_close": 1767102435
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6161247,
|
||||
"status": "CLOSED",
|
||||
"target_value": 995.73,
|
||||
"entry_price": 862.6115,
|
||||
"amount0_initial": 495.7523,
|
||||
"amount1_initial": 0.5796,
|
||||
"liquidity": "2299317483414760958984",
|
||||
"range_upper": 875.5579,
|
||||
"range_lower": 850.0224,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767102508,
|
||||
"hedge_TotPnL": 0.523778
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6162814,
|
||||
"status": "CLOSED",
|
||||
"target_value": 995.27,
|
||||
"entry_price": 860.0,
|
||||
"amount0_initial": 496.4754,
|
||||
"amount1_initial": 0.58,
|
||||
"liquidity": "8300226074094182294178",
|
||||
"range_upper": 863.616,
|
||||
"range_lower": 856.384,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767126772,
|
||||
"hedge_TotPnL": -3.412645,
|
||||
"hedge_fees_paid": 1.002278,
|
||||
"timestamp_close": 1767144919
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6163606,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1000.01,
|
||||
"entry_price": 862.0807,
|
||||
"amount0_initial": 500.0068,
|
||||
"amount1_initial": 0.58,
|
||||
"liquidity": "8283150435973737393211",
|
||||
"range_upper": 865.7014,
|
||||
"range_lower": 858.46,
|
||||
"timestamp_open": 1767145082,
|
||||
"timestamp_close": 1767151372
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6163987,
|
||||
"status": "CLOSED",
|
||||
"target_value": 995.26,
|
||||
"entry_price": 857.9836,
|
||||
"amount0_initial": 497.6305,
|
||||
"amount1_initial": 0.58,
|
||||
"liquidity": "8313185309628073121633",
|
||||
"range_upper": 861.5872,
|
||||
"range_lower": 854.3801,
|
||||
"timestamp_open": 1767152045,
|
||||
"timestamp_close": 1767158799
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6164411,
|
||||
"status": "CLOSED",
|
||||
"target_value": 991.83,
|
||||
"entry_price": 855.03,
|
||||
"amount0_initial": 495.9174,
|
||||
"amount1_initial": 0.58,
|
||||
"liquidity": "8280770348281556176465",
|
||||
"range_upper": 858.6211,
|
||||
"range_lower": 851.4389,
|
||||
"timestamp_open": 1767158967,
|
||||
"timestamp_close": 1767163852
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6164702,
|
||||
"status": "CLOSED",
|
||||
"target_value": 993.41,
|
||||
"entry_price": 866.3337,
|
||||
"amount0_initial": 490.942,
|
||||
"amount1_initial": 0.58,
|
||||
"liquidity": "8220443727732589279738",
|
||||
"range_upper": 869.8855,
|
||||
"range_lower": 862.782,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767164052,
|
||||
"hedge_TotPnL": -3.587319,
|
||||
"hedge_fees_paid": 0.723066,
|
||||
"clp_fees": 1.75,
|
||||
"clp_TotPnL": 0.31,
|
||||
"timestamp_close": 1767189814,
|
||||
"time_close": "31.12.25 15:03:34"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6166625,
|
||||
"status": "CLOSED",
|
||||
"target_value": 996.7,
|
||||
"entry_price": 873.896,
|
||||
"amount0_initial": 496.6816,
|
||||
"amount1_initial": 0.5722,
|
||||
"liquidity": "8653989263919246133281",
|
||||
"range_upper": 877.3107,
|
||||
"range_lower": 870.4946,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767190229,
|
||||
"time_open": "31.12.25 15:10:29",
|
||||
"hedge_TotPnL": 4.004047,
|
||||
"hedge_fees_paid": 0.807563,
|
||||
"clp_fees": 0.34,
|
||||
"clp_TotPnL": -3.96,
|
||||
"timestamp_close": 1767191809,
|
||||
"time_close": "31.12.25 15:36:49"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6166939,
|
||||
"status": "CLOSED",
|
||||
"target_value": 999.11,
|
||||
"entry_price": 866.9331,
|
||||
"amount0_initial": 500.0004,
|
||||
"amount1_initial": 0.5757,
|
||||
"liquidity": "8709690098157915483248",
|
||||
"range_upper": 870.3205,
|
||||
"range_lower": 863.5588,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767192966,
|
||||
"time_open": "31.12.25 15:56:06",
|
||||
"hedge_TotPnL": 1.064447,
|
||||
"hedge_fees_paid": 0.927408,
|
||||
"clp_fees": 0.3,
|
||||
"clp_TotPnL": -2.71,
|
||||
"timestamp_close": 1767193991,
|
||||
"time_close": "31.12.25 16:13:11"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6167093,
|
||||
"status": "CLOSED",
|
||||
"target_value": 996.69,
|
||||
"entry_price": 864.077,
|
||||
"amount0_initial": 500.0128,
|
||||
"amount1_initial": 0.5748,
|
||||
"liquidity": "8702875143941291654654",
|
||||
"range_upper": 867.4533,
|
||||
"range_lower": 860.7139,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767194522,
|
||||
"time_open": "31.12.25 16:22:02",
|
||||
"hedge_TotPnL": -3.013382,
|
||||
"hedge_fees_paid": 0.814047,
|
||||
"clp_fees": 0.95,
|
||||
"clp_TotPnL": -2.76,
|
||||
"timestamp_close": 1767199352,
|
||||
"time_close": "31.12.25 17:42:32"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6167590,
|
||||
"status": "CLOSED",
|
||||
"target_value": 998.37,
|
||||
"entry_price": 861.6611,
|
||||
"amount0_initial": 498.363,
|
||||
"amount1_initial": 0.5803,
|
||||
"liquidity": "8729751956580574272932",
|
||||
"range_upper": 865.028,
|
||||
"range_lower": 858.3074,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767200083,
|
||||
"time_open": "31.12.25 17:54:43",
|
||||
"hedge_TotPnL": -4.720271,
|
||||
"hedge_fees_paid": 1.311938,
|
||||
"clp_fees": 1.95,
|
||||
"clp_TotPnL": 2.92,
|
||||
"timestamp_close": 1767217535,
|
||||
"time_close": "31.12.25 22:45:35"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6168553,
|
||||
"status": "CLOSED",
|
||||
"target_value": 991.55,
|
||||
"entry_price": 865.4606,
|
||||
"amount0_initial": 491.5385,
|
||||
"amount1_initial": 0.5777,
|
||||
"liquidity": "8651067937842123260294",
|
||||
"range_upper": 868.8423,
|
||||
"range_lower": 862.092,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767217854,
|
||||
"time_open": "31.12.25 22:50:54",
|
||||
"hedge_TotPnL": -3.016562,
|
||||
"hedge_fees_paid": 0.460066,
|
||||
"clp_fees": 0.58,
|
||||
"clp_TotPnL": 1.55,
|
||||
"timestamp_close": 1767229894,
|
||||
"time_close": "01.01.26 02:11:34"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6169279,
|
||||
"status": "CLOSED",
|
||||
"target_value": 993.04,
|
||||
"entry_price": 869.1899,
|
||||
"amount0_initial": 493.031,
|
||||
"amount1_initial": 0.5753,
|
||||
"liquidity": "8645470844979366936741",
|
||||
"range_upper": 872.5862,
|
||||
"range_lower": 865.8068,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767230090,
|
||||
"time_open": "01.01.26 02:14:50",
|
||||
"hedge_TotPnL": -1.709208,
|
||||
"hedge_fees_paid": 0.300063,
|
||||
"clp_fees": 0.22,
|
||||
"clp_TotPnL": 1.19,
|
||||
"timestamp_close": 1767232654,
|
||||
"time_close": "01.01.26 02:57:34"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6169469,
|
||||
"status": "CLOSED",
|
||||
"target_value": 996.5,
|
||||
"entry_price": 873.4592,
|
||||
"amount0_initial": 496.4932,
|
||||
"amount1_initial": 0.5724,
|
||||
"liquidity": "8654359631059929427298",
|
||||
"range_upper": 876.8721,
|
||||
"range_lower": 870.0595,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767233101,
|
||||
"time_open": "01.01.26 03:05:01",
|
||||
"hedge_TotPnL": 0.379026,
|
||||
"hedge_fees_paid": 0.415621,
|
||||
"clp_fees": 0.4,
|
||||
"clp_TotPnL": -3.21,
|
||||
"timestamp_close": 1767238291,
|
||||
"time_close": "01.01.26 04:31:31"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6169789,
|
||||
"status": "CLOSED",
|
||||
"target_value": 996.05,
|
||||
"entry_price": 869.103,
|
||||
"amount0_initial": 500.0117,
|
||||
"amount1_initial": 0.5707,
|
||||
"liquidity": "8672126155624077647253",
|
||||
"range_upper": 872.4989,
|
||||
"range_lower": 865.7203,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767238369,
|
||||
"time_open": "01.01.26 04:32:49",
|
||||
"hedge_TotPnL": 3.954178,
|
||||
"hedge_fees_paid": 0.765854,
|
||||
"clp_fees": 0.21,
|
||||
"clp_TotPnL": -2.7,
|
||||
"timestamp_close": 1767242596,
|
||||
"time_close": "01.01.26 05:43:16"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6170135,
|
||||
"status": "CLOSED",
|
||||
"target_value": 998.15,
|
||||
"entry_price": 862.6094,
|
||||
"amount0_initial": 500.001,
|
||||
"amount1_initial": 0.5775,
|
||||
"liquidity": "8723056935772169247603",
|
||||
"range_upper": 865.98,
|
||||
"range_lower": 859.252,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767243101,
|
||||
"time_open": "01.01.26 05:51:41",
|
||||
"hedge_TotPnL": 2.409614,
|
||||
"hedge_fees_paid": 1.355821,
|
||||
"clp_fees": 0.64,
|
||||
"clp_TotPnL": -2.48,
|
||||
"timestamp_close": 1767254432,
|
||||
"time_close": "01.01.26 09:00:32"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6170841,
|
||||
"status": "CLOSED",
|
||||
"target_value": 998.62,
|
||||
"entry_price": 859.8536,
|
||||
"amount0_initial": 498.6144,
|
||||
"amount1_initial": 0.5815,
|
||||
"liquidity": "8741115554990437903852",
|
||||
"range_upper": 863.2134,
|
||||
"range_lower": 856.5069,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767254603,
|
||||
"time_open": "01.01.26 09:03:23",
|
||||
"hedge_TotPnL": -4.244326,
|
||||
"hedge_fees_paid": 1.827099,
|
||||
"clp_fees": 2.59,
|
||||
"clp_TotPnL": 3.56,
|
||||
"timestamp_close": 1767308203,
|
||||
"time_close": "01.01.26 23:56:43"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6175190,
|
||||
"status": "CLOSED",
|
||||
"target_value": 998.54,
|
||||
"entry_price": 863.8179,
|
||||
"amount0_initial": 498.5396,
|
||||
"amount1_initial": 0.5788,
|
||||
"liquidity": "8720378230633469405596",
|
||||
"range_upper": 867.1932,
|
||||
"range_lower": 860.4557,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767308440,
|
||||
"time_open": "02.01.26 00:00:40",
|
||||
"hedge_TotPnL": 2.712563,
|
||||
"hedge_fees_paid": 0.819224,
|
||||
"clp_fees": 0.62,
|
||||
"clp_TotPnL": -2.49,
|
||||
"timestamp_close": 1767320335,
|
||||
"time_close": "02.01.26 03:18:55"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6175868,
|
||||
"status": "CLOSED",
|
||||
"target_value": 991.53,
|
||||
"entry_price": 860.2836,
|
||||
"amount0_initial": 491.5252,
|
||||
"amount1_initial": 0.5812,
|
||||
"liquidity": "8676952736685102236300",
|
||||
"range_upper": 863.6451,
|
||||
"range_lower": 856.9352,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767320584,
|
||||
"time_open": "02.01.26 03:23:04",
|
||||
"hedge_TotPnL": -1.782405,
|
||||
"hedge_fees_paid": 0.312615,
|
||||
"clp_fees": 0.11,
|
||||
"clp_TotPnL": 1.09,
|
||||
"timestamp_close": 1767323453,
|
||||
"time_close": "02.01.26 04:10:53"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6176051,
|
||||
"status": "CLOSED",
|
||||
"target_value": 997.7,
|
||||
"entry_price": 863.7315,
|
||||
"amount0_initial": 497.694,
|
||||
"amount1_initial": 0.5789,
|
||||
"liquidity": "8713457799891424871655",
|
||||
"range_upper": 867.1064,
|
||||
"range_lower": 860.3697,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767324323,
|
||||
"time_open": "02.01.26 04:25:23",
|
||||
"hedge_TotPnL": -3.840822,
|
||||
"hedge_fees_paid": 0.892717,
|
||||
"clp_fees": 0.65,
|
||||
"clp_TotPnL": 1.63,
|
||||
"timestamp_close": 1767335965,
|
||||
"time_close": "02.01.26 07:39:25"
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6176727,
|
||||
"status": "CLOSED",
|
||||
"target_value": 990.64,
|
||||
"entry_price": 867.5401,
|
||||
"amount0_initial": 490.6325,
|
||||
"amount1_initial": 0.5764,
|
||||
"liquidity": "8632807640200638943476",
|
||||
"range_upper": 870.9299,
|
||||
"range_lower": 864.1634,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1767336634,
|
||||
"time_open": "02.01.26 07:50:34",
|
||||
"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
|
||||
}
|
||||
]
|
||||
905
florida/UNISWAP_V3_status.json
Normal file
905
florida/UNISWAP_V3_status.json
Normal file
@ -0,0 +1,905 @@
|
||||
[
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5173910,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1999.21,
|
||||
"entry_price": 2966.7,
|
||||
"amount0_initial": 0.3301,
|
||||
"amount1_initial": 1019.95,
|
||||
"range_upper": 2995.91,
|
||||
"range_lower": 2936.59,
|
||||
"timestamp_open": 1766488092,
|
||||
"initial_hedge_usdc": 1000.0,
|
||||
"hedge_equity_usd": 1012.848897,
|
||||
"hedge_pnl_realized": -0.89,
|
||||
"hedge_fees_paid": 1.56,
|
||||
"timestamp_close": 1766501540
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174242,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1989.63,
|
||||
"entry_price": 2910.43,
|
||||
"amount0_initial": 0.322,
|
||||
"amount1_initial": 1052.48,
|
||||
"range_upper": 2936.59,
|
||||
"range_lower": 2881.32,
|
||||
"timestamp_open": 1766502268,
|
||||
"initial_hedge_usdc": 1011.11115,
|
||||
"hedge_equity_usd": 1002.743444,
|
||||
"hedge_pnl_realized": -9.04,
|
||||
"hedge_fees_paid": 0.86,
|
||||
"timestamp_close": 1766506329
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174392,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1987.85,
|
||||
"entry_price": 2956.82,
|
||||
"amount0_initial": 0.3071,
|
||||
"amount1_initial": 1079.77,
|
||||
"range_upper": 2983.95,
|
||||
"range_lower": 2924.86,
|
||||
"timestamp_open": 1766507046,
|
||||
"initial_hedge_usdc": 1002.743444,
|
||||
"hedge_equity_usd": 1018.807922,
|
||||
"hedge_pnl_realized": -2.28,
|
||||
"hedge_fees_paid": 0.93,
|
||||
"timestamp_close": 1766513112
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174532,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1986.73,
|
||||
"entry_price": 2942.37,
|
||||
"amount0_initial": 0.3211,
|
||||
"amount1_initial": 1041.97,
|
||||
"range_upper": 2969.07,
|
||||
"range_lower": 2913.19,
|
||||
"timestamp_open": 1766513873,
|
||||
"initial_hedge_usdc": 1016.672049,
|
||||
"hedge_equity_usd": 1005.949738,
|
||||
"hedge_pnl_realized": -6.16,
|
||||
"hedge_fees_paid": 1.11,
|
||||
"timestamp_close": 1766522608
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174721,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1988.43,
|
||||
"entry_price": 2971.33,
|
||||
"amount0_initial": 0.3092,
|
||||
"amount1_initial": 1069.8,
|
||||
"range_upper": 2998.9,
|
||||
"range_lower": 2939.53,
|
||||
"timestamp_open": 1766523284,
|
||||
"initial_hedge_usdc": 1005.949738,
|
||||
"hedge_equity_usd": 1005.238693,
|
||||
"hedge_pnl_realized": -0.7,
|
||||
"hedge_fees_paid": 0.71
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174792,
|
||||
"status": "CLOSED",
|
||||
"target_value": 2651.4,
|
||||
"entry_price": 2973.46,
|
||||
"amount0_initial": 0.4246,
|
||||
"amount1_initial": 1388.81,
|
||||
"liquidity": "4874764439714363",
|
||||
"range_upper": 3001.9,
|
||||
"range_lower": 2942.47,
|
||||
"timestamp_open": 1766527489,
|
||||
"initial_hedge_usdc": 1002.312464,
|
||||
"hedge_equity_usd": 1002.312464,
|
||||
"hedge_pnl_realized": 0.0,
|
||||
"hedge_fees_paid": 0.0
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174797,
|
||||
"status": "CLOSED",
|
||||
"target_value": 199.26,
|
||||
"entry_price": 2960.88,
|
||||
"amount0_initial": 0.0329,
|
||||
"amount1_initial": 101.99,
|
||||
"liquidity": "367136758323064",
|
||||
"range_upper": 2989.92,
|
||||
"range_lower": 2930.72,
|
||||
"timestamp_open": 1766527967,
|
||||
"initial_hedge_usdc": 1000.965119,
|
||||
"hedge_equity_usd": 1000.864724,
|
||||
"hedge_pnl_realized": -0.05,
|
||||
"hedge_fees_paid": 0.05
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174808,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1994.89,
|
||||
"entry_price": 2954.93,
|
||||
"amount0_initial": 0.3299,
|
||||
"amount1_initial": 1019.94,
|
||||
"liquidity": "3679197389549125",
|
||||
"range_upper": 2983.95,
|
||||
"range_lower": 2924.86,
|
||||
"timestamp_open": 1766529348,
|
||||
"initial_hedge_usdc": 1001.01805,
|
||||
"hedge_equity_usd": 1008.884158,
|
||||
"hedge_pnl_realized": -5.85,
|
||||
"hedge_fees_paid": 2.21,
|
||||
"timestamp_close": 1766545502
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5175426,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1984.47,
|
||||
"entry_price": 2925.98,
|
||||
"amount0_initial": 0.3262,
|
||||
"amount1_initial": 1029.88,
|
||||
"liquidity": "3678045237670142",
|
||||
"range_upper": 2954.26,
|
||||
"range_lower": 2895.76,
|
||||
"timestamp_open": 1766566249,
|
||||
"initial_hedge_usdc": 1007.931072,
|
||||
"hedge_equity_usd": 983.149732,
|
||||
"hedge_pnl_realized": -26.81,
|
||||
"hedge_fees_paid": 2.57,
|
||||
"timestamp_close": 1766616464
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5176634,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1981.59,
|
||||
"entry_price": 2958.09,
|
||||
"amount0_initial": 0.3251,
|
||||
"amount1_initial": 1019.9,
|
||||
"liquidity": "3652722296614890",
|
||||
"range_upper": 2986.93,
|
||||
"range_lower": 2927.79,
|
||||
"timestamp_open": 1766617139,
|
||||
"initial_hedge_usdc": 983.149732,
|
||||
"hedge_equity_usd": 998.691177,
|
||||
"hedge_pnl_realized": -0.32,
|
||||
"hedge_fees_paid": 0.89,
|
||||
"timestamp_close": 1766651355
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5177261,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1995.91,
|
||||
"entry_price": 2925.51,
|
||||
"amount0_initial": 0.3336,
|
||||
"amount1_initial": 1019.94,
|
||||
"liquidity": "3699547812088951",
|
||||
"range_upper": 2954.26,
|
||||
"range_lower": 2895.76,
|
||||
"timestamp_open": 1766652125,
|
||||
"initial_hedge_usdc": 997.755868,
|
||||
"hedge_equity_usd": 987.99485,
|
||||
"hedge_pnl_realized": -8.17,
|
||||
"hedge_fees_paid": 0.74,
|
||||
"target_value_end": 0.0,
|
||||
"timestamp_close": 1766677241
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5178047,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1991.5,
|
||||
"entry_price": 2965.34,
|
||||
"amount0_initial": 0.3108,
|
||||
"amount1_initial": 1069.8,
|
||||
"liquidity": "3666528467508532",
|
||||
"range_upper": 2992.91,
|
||||
"range_lower": 2933.65,
|
||||
"timestamp_open": 1766677916,
|
||||
"initial_hedge_usdc": 987.99485,
|
||||
"hedge_equity_usd": 1003.143647,
|
||||
"hedge_pnl_realized": -0.16,
|
||||
"hedge_fees_paid": 0.69,
|
||||
"target_value_end": 0.0,
|
||||
"timestamp_close": 1766702691
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5178409,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1987.63,
|
||||
"entry_price": 2916.84,
|
||||
"amount0_initial": 0.3318,
|
||||
"amount1_initial": 1019.92,
|
||||
"liquidity": "3689670482290624",
|
||||
"range_upper": 2945.41,
|
||||
"range_lower": 2887.09,
|
||||
"timestamp_open": 1766703368,
|
||||
"initial_hedge_usdc": 1002.368481,
|
||||
"hedge_equity_usd": 990.301215,
|
||||
"hedge_pnl_realized": -3.91,
|
||||
"hedge_fees_paid": 0.98,
|
||||
"target_value_end": 0.0,
|
||||
"timestamp_close": 1766715440
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5178639,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1996.39,
|
||||
"entry_price": 2983.41,
|
||||
"amount0_initial": 0.3072,
|
||||
"amount1_initial": 1079.79,
|
||||
"liquidity": "3664399483786727",
|
||||
"range_upper": 3010.92,
|
||||
"range_lower": 2951.31,
|
||||
"timestamp_open": 1766716114,
|
||||
"initial_hedge_usdc": 990.301215,
|
||||
"hedge_equity_usd": 1018.672,
|
||||
"hedge_pnl_realized": -5.58,
|
||||
"hedge_fees_paid": 1.55,
|
||||
"target_value_end": 0.0,
|
||||
"timestamp_close": 1766760944
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5179293,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1986.34,
|
||||
"entry_price": 2910.18,
|
||||
"amount0_initial": 0.3245,
|
||||
"amount1_initial": 1041.97,
|
||||
"liquidity": "3885311775355906",
|
||||
"range_upper": 2936.59,
|
||||
"range_lower": 2881.32,
|
||||
"timestamp_open": 1766761703,
|
||||
"initial_hedge_usdc": 1019.619398,
|
||||
"hedge_equity_usd": 1001.73964,
|
||||
"hedge_pnl_realized": -20.55,
|
||||
"hedge_fees_paid": 1.66,
|
||||
"target_value_end": 2002.85,
|
||||
"timestamp_close": 1766874151
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5180968,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1986.71,
|
||||
"entry_price": 2932.98,
|
||||
"amount0_initial": 0.3126,
|
||||
"amount1_initial": 1069.79,
|
||||
"liquidity": "3677842511030595",
|
||||
"range_upper": 2960.17,
|
||||
"range_lower": 2901.56,
|
||||
"timestamp_open": 1766874875,
|
||||
"initial_hedge_usdc": 1002.337877,
|
||||
"hedge_equity_usd": 901.431859,
|
||||
"hedge_pnl_realized": -268.84,
|
||||
"hedge_fees_paid": 34.92,
|
||||
"target_value_end": 2000.45,
|
||||
"timestamp_close": 1766968239
|
||||
},
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5182179,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1993.84,
|
||||
"entry_price": 2969.9855,
|
||||
"amount0_initial": 0.3347,
|
||||
"amount1_initial": 999.8831,
|
||||
"liquidity": "755871440225782",
|
||||
"range_upper": 3118.1664,
|
||||
"range_lower": 2827.096,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 6,
|
||||
"timestamp_open": 1766968369,
|
||||
"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
82
florida/clp_abis.py
Normal 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"}
|
||||
]
|
||||
''')
|
||||
174
florida/clp_config.py
Normal file
174
florida/clp_config.py
Normal file
@ -0,0 +1,174 @@
|
||||
import os
|
||||
from decimal import Decimal
|
||||
|
||||
# --- GLOBAL SETTINGS ---
|
||||
# Use environment variables to switch profiles
|
||||
TARGET_DEX = os.environ.get("TARGET_DEX", "UNISWAP_V3")
|
||||
STATUS_FILE = os.environ.get("STATUS_FILE", f"{TARGET_DEX}_status.json")
|
||||
|
||||
# --- DEFAULT STRATEGY ---
|
||||
DEFAULT_STRATEGY = {
|
||||
"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
|
||||
|
||||
# Investment Settings
|
||||
"TARGET_INVESTMENT_AMOUNT": 2000, # Total USD value to deploy into the LP position
|
||||
"INITIAL_HEDGE_CAPITAL": 1000, # Capital reserved on Hyperliquid for hedging
|
||||
"VALUE_REFERENCE": "USD", # Base currency for all calculations
|
||||
|
||||
# Range Settings
|
||||
"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), "FIXED" (Initial Delta)
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.012"), # Minimum delta change (in coins) required to trigger a trade
|
||||
|
||||
# Unified Hedger Settings
|
||||
|
||||
"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
|
||||
"ZONE_CLOSE_END": Decimal("11.0"), # Distance (pct) from edge to finish closing logic
|
||||
"ZONE_TOP_HEDGE_START": Decimal("10.0"), # Distance (pct) from top edge to adjust hedging
|
||||
"PRICE_BUFFER_PCT": Decimal("0.0015"), # Buffer for limit order pricing (0.15%)
|
||||
"MIN_ORDER_VALUE_USD": Decimal("10.0"), # Minimum order size allowed by Hyperliquid
|
||||
"DYNAMIC_THRESHOLD_MULTIPLIER": Decimal("1.2"), # Expansion factor for thresholds
|
||||
"MIN_TIME_BETWEEN_TRADES": 60, # Cooldown (seconds) between rebalance trades
|
||||
"MAX_HEDGE_MULTIPLIER": Decimal("1.25"), # Max allowed hedge size relative to calculated target
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": Decimal("0.25"), # Base tolerance for delta drift (20%)
|
||||
"EDGE_PROXIMITY_PCT": Decimal("0.04"), # Distance to range edge where protection activates
|
||||
"VELOCITY_THRESHOLD_PCT": Decimal("0.0005"), # Minimum price velocity to trigger volatility logic
|
||||
"POSITION_OPEN_EDGE_PROXIMITY_PCT": Decimal("0.06"), # Safety margin when opening new positions
|
||||
"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.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)
|
||||
"FISHING_ORDER_SIZE_PCT": Decimal("0.10"), # Size of individual fishing orders
|
||||
"FISHING_TIMEOUT_FALLBACK": 30, # Seconds before converting fishing order to taker
|
||||
|
||||
# EAC (Enhanced Asymmetric Compensation)
|
||||
"EAC_NARROW_RANGE_THRESHOLD": Decimal("0.02"), # <2% = narrow
|
||||
"EAC_MEDIUM_RANGE_THRESHOLD": Decimal("0.05"), # <5% = medium
|
||||
"EAC_NARROW_BOOST": Decimal("0.15"), # 15% boost
|
||||
"EAC_MEDIUM_BOOST": Decimal("0.10"), # 10% boost
|
||||
"EAC_WIDE_BOOST": Decimal("0.075"), # 7.5% boost
|
||||
}
|
||||
|
||||
# --- CLP PROFILES ---
|
||||
CLP_PROFILES = {
|
||||
"UNISWAP_V3": {
|
||||
"NAME": "Uniswap V3 (Arbitrum) - ETH/USDC",
|
||||
"COIN_SYMBOL": "ETH",
|
||||
"RPC_ENV_VAR": "MAINNET_RPC_URL",
|
||||
"NPM_ADDRESS": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
||||
"ROUTER_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
|
||||
"TOKEN_A_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH
|
||||
"TOKEN_B_ADDRESS": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC
|
||||
"WRAPPED_NATIVE_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
||||
"POOL_FEE": 500,
|
||||
"TARGET_INVESTMENT_AMOUNT": 3000,
|
||||
"HEDGE_STRATEGY": "FIXED",
|
||||
"RANGE_WIDTH_PCT": Decimal("0.0075"),
|
||||
},
|
||||
"UNISWAP_wide": {
|
||||
"NAME": "Uniswap V3 (Arbitrum) - ETH/USDC Wide",
|
||||
"COIN_SYMBOL": "ETH",
|
||||
"RPC_ENV_VAR": "MAINNET_RPC_URL",
|
||||
"NPM_ADDRESS": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
|
||||
"ROUTER_ADDRESS": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
|
||||
"TOKEN_A_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH
|
||||
"TOKEN_B_ADDRESS": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC
|
||||
"WRAPPED_NATIVE_ADDRESS": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
||||
"POOL_FEE": 500,
|
||||
"RANGE_WIDTH_PCT": Decimal("0.05"),
|
||||
"TARGET_INVESTMENT_AMOUNT": 2000,
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.01"),
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": Decimal("0.15"),
|
||||
},
|
||||
"PANCAKESWAP_BNB": {
|
||||
"NAME": "PancakeSwap V3 (BNB Chain) - BNB/USDT",
|
||||
"COIN_SYMBOL": "BNB",
|
||||
"RPC_ENV_VAR": "BNB_RPC_URL",
|
||||
"NPM_ADDRESS": "0x46A15B0b27311cedF172AB29E4f4766fbE7F4364",
|
||||
"ROUTER_ADDRESS": "0x1b81D678ffb9C0263b24A97847620C99d213eB14",
|
||||
"TOKEN_A_ADDRESS": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", # WBNB
|
||||
"TOKEN_B_ADDRESS": "0x55d398326f99059fF775485246999027B3197955", # USDT
|
||||
"WRAPPED_NATIVE_ADDRESS": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
||||
"POOL_FEE": 100,
|
||||
"EDGE_CLEANUP_MARGIN_PCT": Decimal("0.1875"), # 0.1875 only for asymmetric shedge % of range width used for edge detection
|
||||
"RANGE_WIDTH_PCT": Decimal("0.004"),
|
||||
"TARGET_INVESTMENT_AMOUNT": 1000,
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.015"),
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": Decimal("0.10"),
|
||||
"EDGE_PROXIMITY_PCT": Decimal("0.015"),
|
||||
"DYNAMIC_THRESHOLD_MULTIPLIER": Decimal("1.1"),
|
||||
"MIN_TIME_BETWEEN_TRADES": 20,
|
||||
"ENABLE_FISHING": False,
|
||||
"FISHING_ORDER_SIZE_PCT": Decimal("0.05"),
|
||||
"MAKER_ORDER_TIMEOUT": 180,
|
||||
"FISHING_TIMEOUT_FALLBACK": 60,
|
||||
},
|
||||
"WETH_CBBTC_BASE": {
|
||||
"NAME": "Aerodrome/Uni (Base) - WETH/cbBTC",
|
||||
"COIN_SYMBOL": "ETH",
|
||||
"RPC_ENV_VAR": "BASE_RPC_URL",
|
||||
"NPM_ADDRESS": "0x0000000000000000000000000000000000000000", # Placeholder
|
||||
"ROUTER_ADDRESS": "0x0000000000000000000000000000000000000000", # Placeholder
|
||||
"TOKEN_A_ADDRESS": "0x4200000000000000000000000000000000000006", # WETH (Base)
|
||||
"TOKEN_B_ADDRESS": "0xcbB7C915AB58735a1391B9fE18541b4d8926D412", # cbBTC (Base)
|
||||
"WRAPPED_NATIVE_ADDRESS": "0x4200000000000000000000000000000000000006",
|
||||
"POOL_FEE": 3000,
|
||||
"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",
|
||||
}
|
||||
}
|
||||
|
||||
# --- HELPER TO GET ACTIVE CONFIG ---
|
||||
def get_current_config():
|
||||
profile = CLP_PROFILES.get(TARGET_DEX)
|
||||
if not profile:
|
||||
raise ValueError(f"Unknown CLP profile: {TARGET_DEX}")
|
||||
|
||||
# Merge Default Strategy with Profile (Profile wins)
|
||||
config = DEFAULT_STRATEGY.copy()
|
||||
config.update(profile)
|
||||
|
||||
return config
|
||||
1061
florida/clp_hedger.py
Normal file
1061
florida/clp_hedger.py
Normal file
File diff suppressed because it is too large
Load Diff
1199
florida/clp_manager.py
Normal file
1199
florida/clp_manager.py
Normal file
File diff suppressed because it is too large
Load Diff
103
florida/doc/AERODROME_CL_INTEGRATION.md
Normal file
103
florida/doc/AERODROME_CL_INTEGRATION.md
Normal 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` |
|
||||
66
florida/doc/CLP_DATA_INTERPRETATION.md
Normal file
66
florida/doc/CLP_DATA_INTERPRETATION.md
Normal 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.*
|
||||
230
florida/doc/CLP_OPTIMIZATION_PLAN.md
Normal file
230
florida/doc/CLP_OPTIMIZATION_PLAN.md
Normal file
@ -0,0 +1,230 @@
|
||||
# CLP Hedge Risk Mitigation & Zero-Delta Optimization Plan
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Based on analysis of -$2.23 test loss from position 6153292, this plan addresses critical formula errors, suboptimal configurations, and missing features preventing true zero-delta hedging. Expected outcome: 85-95% loss reduction (from $2.23 to $0.11-0.33).
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Critical Bug Fixes (Week 1)
|
||||
|
||||
### 1.1 Delta Calculation Formula Correction
|
||||
**Files**: `unified_hedger.py` (lines 187-198)
|
||||
**Issue**: Incorrect in-range delta formula causing hedge underestimation
|
||||
**Current Buggy Formula**:
|
||||
```python
|
||||
return self.L * ((Decimal("1")/sqrt_P) - (Decimal("1")/sqrt_Pb))
|
||||
```
|
||||
**Correct Formula**:
|
||||
```python
|
||||
return self.L * (sqrt_Pb - sqrt_P) / (current_price * sqrt_P * sqrt_Pb)
|
||||
```
|
||||
**Impact**: Fixes core hedge accuracy, 60-70% loss reduction expected
|
||||
|
||||
### 1.2 Liquidity Scaling Fix
|
||||
**Files**: `unified_hedger.py` (lines 156-158)
|
||||
**Issue**: Improper normalization causing position size errors
|
||||
**Current**:
|
||||
```python
|
||||
scale_exp = (d0 + d1) / 2
|
||||
liquidity_scale = Decimal("10") ** Decimal(str(-scale_exp))
|
||||
```
|
||||
**Correct**:
|
||||
```python
|
||||
liquidity_scale = Decimal("10") ** Decimal(str(-(d0 + d1) / 2))
|
||||
```
|
||||
**Impact**: Correct hedge position sizing
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Fishing Feature Implementation (Week 1-2)
|
||||
|
||||
### 2.1 Port Fishing Logic to Unified Hedger
|
||||
**Files**: `unified_hedger.py`
|
||||
**Task**: Move fishing implementation from `clp_hedger.py` to existing `shadow_orders` system
|
||||
|
||||
### 2.2 Conservative Fishing Configuration
|
||||
**Files**: `clp_config.py` (BNB profile)
|
||||
**Changes**:
|
||||
```python
|
||||
"ENABLE_FISHING": True, # Enable feature
|
||||
"FISHING_ORDER_SIZE_PCT": Decimal("0.05"), # Conservative 5%
|
||||
"MAKER_ORDER_TIMEOUT": 60, # 60s timeout (not 600s)
|
||||
"FISHING_TIMEOUT_FALLBACK": 30, # 30s to taker fallback
|
||||
```
|
||||
**Impact**: 30-40% fee reduction by converting taker to maker trades
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Configuration Optimization (Week 2-3)
|
||||
|
||||
### 3.1 BNB-Specific Parameter Tuning
|
||||
**Files**: `clp_config.py` (PANCAKESWAP_BNB profile)
|
||||
**Changes**:
|
||||
```python
|
||||
"MIN_HEDGE_THRESHOLD": Decimal("0.02"), # Down from 0.05 (2.5x responsive)
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": Decimal("0.15"), # Down from 0.25 (tighter)
|
||||
"EDGE_PROXIMITY_PCT": Decimal("0.02"), # Down from 0.04 (earlier)
|
||||
"DYNAMIC_THRESHOLD_MULTIPLIER": Decimal("1.1"), # Down from 1.2 (less padding)
|
||||
"MIN_TIME_BETWEEN_TRADES": 30, # Down from 60 (faster response)
|
||||
```
|
||||
|
||||
### 3.2 Add EAC Configuration Parameters
|
||||
**Files**: `clp_config.py` (add to DEFAULT_STRATEGY)
|
||||
**New Parameters**:
|
||||
```python
|
||||
"EAC_NARROW_RANGE_THRESHOLD": Decimal("0.02"), # <2% = narrow
|
||||
"EAC_MEDIUM_RANGE_THRESHOLD": Decimal("0.05"), # <5% = medium
|
||||
"EAC_NARROW_BOOST": Decimal("0.15"), # 15% boost
|
||||
"EAC_MEDIUM_BOOST": Decimal("0.10"), # 10% boost
|
||||
"EAC_WIDE_BOOST": Decimal("0.075"), # 7.5% boost
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Enhanced Asymmetric Compensation (Week 2-3)
|
||||
|
||||
### 4.1 Dynamic Compensation Logic
|
||||
**Files**: `unified_hedger.py` (replace lines 206-219)
|
||||
**Current Implementation**:
|
||||
```python
|
||||
max_boost = Decimal("0.075") # Static 7.5% for all ranges
|
||||
```
|
||||
|
||||
**Enhanced Implementation**:
|
||||
```python
|
||||
def get_compensation_boost(self):
|
||||
range_width_pct = (self.high_range - self.low_range) / self.low_range
|
||||
|
||||
if range_width_pct < Decimal("0.02"): # <2% range
|
||||
return Decimal("0.15") # Double protection for narrow ranges
|
||||
elif range_width_pct < Decimal("0.05"): # <5% range
|
||||
return Decimal("0.10") # Moderate for medium ranges
|
||||
else: # >=5% range
|
||||
return Decimal("0.075") # Standard for wide ranges
|
||||
|
||||
# Replace in calculate_rebalance:
|
||||
max_boost = self.get_compensation_boost()
|
||||
```
|
||||
|
||||
**Impact Analysis for Test Case**:
|
||||
- **Current 7.5%**: Would offset ~$0.59 of fees in -$2.23 loss
|
||||
- **Enhanced 15%**: Would offset ~$1.18 of fees (double improvement)
|
||||
- **Net Result**: Reduces $2.23 loss to ~$1.05 (36% additional improvement)
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: System Improvements (Week 3-4)
|
||||
|
||||
### 5.1 API Synchronization Enhancement
|
||||
**Files**: `unified_hedger.py` (line 788-790)
|
||||
**Change**: Increase sync time from 5 to 10 seconds
|
||||
|
||||
### 5.2 Position Validation
|
||||
**Files**: `unified_hedger.py` (after trade execution)
|
||||
**Add**: Post-trade position verification to ensure hedge accuracy
|
||||
|
||||
### 5.3 Enhanced Monitoring & Logging
|
||||
**Files**: New logging/metrics module
|
||||
**Add**:
|
||||
- Real-time delta tracking alerts
|
||||
- EAC effectiveness monitoring
|
||||
- Fishing success rate tracking
|
||||
- Fee vs PnL performance metrics
|
||||
|
||||
---
|
||||
|
||||
## Expected Results Timeline
|
||||
|
||||
| Week | Changes | Expected Loss Reduction | Fee Impact |
|
||||
|------|---------|----------------------|------------|
|
||||
| 1 | Formula fixes + fishing | 60-70% | -40% |
|
||||
| 2 | Config + EAC enhancement | 75-85% | -50% |
|
||||
| 3-4 | System improvements | 85-95% | -60% |
|
||||
| **Target**: Reduce $2.23 loss to **$0.11-0.33** |
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation & Rollback Criteria
|
||||
|
||||
### Stop Conditions:
|
||||
- Daily trades >15 per position
|
||||
- Net fees increase for 3 consecutive days
|
||||
- Hedge accuracy degrades >5%
|
||||
- Fishing success rate <25%
|
||||
- EAC directional bias >15% of total PnL
|
||||
|
||||
### Monitoring Requirements:
|
||||
- Daily PnL vs fee analysis
|
||||
- Transaction frequency tracking
|
||||
- Hedge coverage percentage
|
||||
- Fishing fill rate metrics
|
||||
- EAC effectiveness measurement
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
### Week 1 (Critical):
|
||||
1. ✅ Fix delta calculation formula
|
||||
2. ✅ Fix liquidity scaling
|
||||
3. ✅ Enable 5% fishing
|
||||
4. ✅ Basic monitoring setup
|
||||
|
||||
### Week 2 (Important):
|
||||
5. ✅ Implement Enhanced Asymmetric Compensation
|
||||
6. ✅ Optimize BNB configuration
|
||||
7. ✅ Add EAC configuration parameters
|
||||
8. ✅ Improve API sync timing
|
||||
|
||||
### Week 3-4 (Optimization):
|
||||
9. ✅ Advanced monitoring including EAC metrics
|
||||
10. ✅ Position validation
|
||||
11. ✅ EAC effectiveness fine-tuning
|
||||
|
||||
---
|
||||
|
||||
## Fee Management Strategy
|
||||
|
||||
### Conservative Approach to Transaction Costs:
|
||||
- **Start with 5% fishing** (not aggressive 20%)
|
||||
- **Monitor daily trade counts**: Stop if >15 trades/day per position
|
||||
- **Track fishing success rate**: Disable if <25% fill rate
|
||||
- **Phase thresholds gradually**: Only reduce if fishing proves effective
|
||||
|
||||
### Expected Fee Impact:
|
||||
- **Phase 1-2**: 30-40% fee reduction
|
||||
- **Phase 3-4**: Additional 20-30% reduction
|
||||
- **Total**: 50-70% fee reduction despite more trades
|
||||
|
||||
---
|
||||
|
||||
## Test Case Impact Analysis
|
||||
|
||||
### Current Loss Breakdown (Position 6153292):
|
||||
- **Total Loss**: -$2.23
|
||||
- **Delta Slippage**: -$0.20
|
||||
- **Trading Fees**: -$0.79
|
||||
- **Funding/Execution**: -$1.24
|
||||
|
||||
### Expected Impact with All Improvements:
|
||||
- **Formula Fixes**: -$0.89 (60% reduction)
|
||||
- **Fishing**: -$0.31 (40% fee reduction)
|
||||
- **EAC Enhancement**: -$0.80 (additional 36% improvement)
|
||||
- **Config Optimization**: -$0.23 (tighter thresholds)
|
||||
- **Final Expected Loss**: **-$0.11 to -$0.33**
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This comprehensive plan addresses the root causes of the -$2.23 test loss through:
|
||||
1. **Core mathematical fixes** (delta formula, liquidity scaling)
|
||||
2. **Strategic enhancements** (Enhanced Asymmetric Compensation, fishing)
|
||||
3. **Configuration optimization** (responsive thresholds)
|
||||
4. **Risk management** (monitoring, rollback triggers)
|
||||
|
||||
Expected outcome: **85-95% loss reduction** while maintaining control over transaction costs and system complexity.
|
||||
|
||||
*Plan created: 2025-12-29*
|
||||
*Based on analysis of position 6153292 and comprehensive code review*
|
||||
93
florida/doc/SECURITY_AND_OPTIMIZATION.md
Normal file
93
florida/doc/SECURITY_AND_OPTIMIZATION.md
Normal 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.
|
||||
143
florida/doc/UNIFIED_HEDGER_LOGIC.md
Normal file
143
florida/doc/UNIFIED_HEDGER_LOGIC.md
Normal file
@ -0,0 +1,143 @@
|
||||
# Unified Delta-Neutral Hedger Logic
|
||||
|
||||
## 1. Overview
|
||||
The **Unified Hedger** (`unified_hedger.py`) is the central risk management engine for the Auto CLP system. Unlike the previous architecture where each LP position required a separate process, this unified system manages **all** Liquidity Pool positions across different chains (Arbitrum, BNB Chain, Base) in a single, efficient process.
|
||||
|
||||
### Architecture Comparison
|
||||
|
||||
**Old Architecture (Multi-Process):**
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Manager Arbitrum] -->|Write| B(UNISWAP_V3_status.json)
|
||||
C[Manager BNB] -->|Write| D(PANCAKESWAP_BNB_status.json)
|
||||
|
||||
B -->|Read| E[Hedger Process 1]
|
||||
D -->|Read| F[Hedger Process 2]
|
||||
|
||||
E -->|API Call| G[Hyperliquid]
|
||||
F -->|API Call| G
|
||||
|
||||
style E fill:#f96,stroke:#333
|
||||
style F fill:#f96,stroke:#333
|
||||
```
|
||||
|
||||
**New Architecture (Unified):**
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Manager Arbitrum] -->|Write| B(UNISWAP_V3_status.json)
|
||||
C[Manager BNB] -->|Write| D(PANCAKESWAP_BNB_status.json)
|
||||
|
||||
B -->|Read| E[Unified Hedger (Master)]
|
||||
D -->|Read| E
|
||||
|
||||
E -->|Single API Connection| G[Hyperliquid]
|
||||
|
||||
style E fill:#9f9,stroke:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Core Logic Flow
|
||||
The hedger runs a continuous loop (approx. every 1 second) performing the following steps:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Loop as Main Loop
|
||||
participant Scanner as Strategy Scanner
|
||||
participant API as Hyperliquid API
|
||||
participant Math as Aggregation Engine
|
||||
participant Exec as Execution Logic
|
||||
|
||||
Loop->>Scanner: Scan *_status.json files
|
||||
Scanner-->>Loop: Update Active Strategies
|
||||
|
||||
Loop->>API: Fetch All Prices & Account State (Once)
|
||||
API-->>Loop: Market Data & Balances
|
||||
|
||||
loop For Each Strategy
|
||||
Loop->>Math: Calculate Ideal Short Size
|
||||
end
|
||||
|
||||
Math->>Math: Netting (Sum Targets by Coin)
|
||||
|
||||
Loop->>Exec: Compare Net Target vs. Actual Position
|
||||
|
||||
alt Diff > Threshold
|
||||
Exec->>API: Place Order (Buy/Sell)
|
||||
Exec->>Loop: Sleep 5s (API Lag Safety)
|
||||
else Diff < Threshold
|
||||
Exec->>Loop: Do Nothing (Idle)
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Detailed Mechanisms
|
||||
|
||||
### A. Strategy Scanning & Initialization
|
||||
* **Discovery:** The script uses `glob` to find all files matching `*_status.json` in the project directory.
|
||||
* **Parsing:** It loads the JSON and extracts active positions (`OPEN`, `PENDING_HEDGE`).
|
||||
* **Liquidity Scaling:**
|
||||
To support different chains with different decimals (e.g., BNB has 18 decimals, USDC has 6), it calculates a scaling factor:
|
||||
$$ Scale = 10^{-(d_0 + d_1)/2} $$
|
||||
This ensures the liquidity math ($L$) is normalized for the hedging calculations.
|
||||
|
||||
### B. Ideal Delta Calculation
|
||||
For each active LP position, the strategy calculates how much it *should* be short to be delta-neutral.
|
||||
Formula for Concentrated Liquidity Delta ($\Delta_{LP}$):
|
||||
|
||||
$$ \Delta_{LP} = L \times \left( \frac{1}{\sqrt{P_{current}}} - \frac{1}{\sqrt{P_{upper}}} \right) $$
|
||||
|
||||
* If $P_{current} < P_{lower}$: Delta is max (full range).
|
||||
* If $P_{current} > P_{upper}$: Delta is 0.
|
||||
|
||||
### C. Portfolio Netting (The "Alpha")
|
||||
This is the key efficiency gain. Instead of trading for every position, the system sums up the requirements.
|
||||
|
||||
| Strategy | Chain | Coin | Ideal Short |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| Strat A | Arbitrum | ETH | -1.5 ETH |
|
||||
| Strat B | Base | ETH | -0.5 ETH |
|
||||
| Strat C | Arbitrum | ETH | +0.2 ETH (Long/Closing) |
|
||||
| **NET TOTAL** | **ALL** | **ETH** | **-1.8 ETH** |
|
||||
|
||||
The hedger only checks the Hyperliquid account once:
|
||||
* **Reality:** Account has `-1.6 ETH` short.
|
||||
* **Action:** Sell `0.2 ETH` to reach `-1.8`.
|
||||
* *Result:* Strat C's "Buy" was internally netted against Strat A's "Sell". **Zero fees paid for that portion.**
|
||||
|
||||
### D. Execution & Thresholds
|
||||
|
||||
The decision to trade is based on a dynamic threshold system to avoid churn (over-trading).
|
||||
|
||||
1. **Volatility Adjustment:**
|
||||
It calculates the standard deviation of the last 30 price points.
|
||||
$$ Threshold_{Dynamic} = BaseThreshold \times \min(3.0, \frac{Vol_{Current}}{Vol_{Base}}) $$
|
||||
*High Volatility = Wider Thresholds (Trade less).*
|
||||
|
||||
2. **Edge Protection:**
|
||||
If the price is very close to the range boundary (e.g., within 2%), the math becomes unstable (Gamma spike).
|
||||
* **Action:** Force the threshold to the minimum (`MIN_HEDGE_THRESHOLD`) to ensure precise hedging at the dangerous "exit" zones.
|
||||
|
||||
3. **Safety Checks:**
|
||||
* **Large Hedge Multiplier (5.0x):** If the required trade size is huge (>5x normal threshold), it assumes a "Catch-up" is needed but treats it carefully.
|
||||
* **API Lag Sleep:** After *any* trade, the loop **sleeps for 5 seconds**. This prevents the "Double Hedge" bug where the bot trades again because the API hasn't updated the position size yet.
|
||||
|
||||
### E. Shadow Orders
|
||||
Since the bot often uses Taker orders (Market/IOC) during urgent rebalances, it simulates "What if I had used a Maker order?".
|
||||
* It creates a virtual "Shadow Order" in memory.
|
||||
* It tracks the order book. If the price crosses the shadow price, it marks it as "Filled".
|
||||
* *Purpose:* To gather data on whether a purely Maker-based strategy would be viable in the future.
|
||||
|
||||
---
|
||||
|
||||
## 4. Configuration Reference
|
||||
|
||||
All settings are tunable in `unified_hedger.py` or via `clp_config.py`.
|
||||
|
||||
| Parameter | Default | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `LARGE_HEDGE_MULTIPLIER` | `5.0` | Multiplier for "Urgent" trade flag. Higher = Less Panic. |
|
||||
| `MIN_TIME_BETWEEN_TRADES` | `60s` | Cooldown for standard rebalances. |
|
||||
| `BASE_REBALANCE_THRESHOLD_PCT` | `0.09` | 9% deviation allowed before rebalancing (scaled by Vol). |
|
||||
| `ENABLE_EDGE_CLEANUP` | `True` | Forces tight hedging at range edges. |
|
||||
57
florida/doc/UNIFIED_HEDGER_LOGS.md
Normal file
57
florida/doc/UNIFIED_HEDGER_LOGS.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Unified Hedger Logs Explained
|
||||
|
||||
This document explains how to read and interpret the `[IDLE]` logs generated by the `unified_hedger.py` system.
|
||||
|
||||
## Log Structure
|
||||
|
||||
A standard log entry looks like this:
|
||||
|
||||
```text
|
||||
[IDLE] BNB | Px: 861.66 | M: 862.1 | B: 863.4 / S: 860.9 | delta: -0.5818(+0.0332) | Adj: +0.01%, Vol: 1.00, Thr: 0.0873 | PnL: -0.04 | TotPnL: -0.04
|
||||
```
|
||||
|
||||
### Fields Breakdown
|
||||
|
||||
| Field | Full Name | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| **Px** | Price | The current mid-price of the asset on Hyperliquid. |
|
||||
| **M** | Mid-Hedge Price | The theoretical price where your Hedge Difference (`Diff`) would be exactly 0 (Perfect Equilibrium). |
|
||||
| **B** | Buy Trigger | The price at which the bot estimates it will need to **BUY** to rebalance (hedge is too heavy). |
|
||||
| **S** | Sell Trigger | The price at which the bot estimates it will need to **SELL** to rebalance (hedge is too light). |
|
||||
| **delta** | Net Target Delta | The total size (in coins) the bot *wants* to be Short. Value is negative for Short. <br> **Format:** `Target (Difference)` <br> *Example:* `-0.5818(+0.0332)` means target is -0.5818, and you are currently +0.0332 away from it (under-hedged, need to sell). |
|
||||
| **Adj** | Adjustment % | The **Asymmetric Compensation** boost applied to the delta. Positive means the bot is "boosting" the short (anticipating downside), negative means it is reducing it. |
|
||||
| **Vol** | Volatility Multiplier | A factor (1.0 to 3.0) that widens the threshold during high volatility to prevent chop. |
|
||||
| **Thr** | Threshold | The dynamic trigger distance. If `Diff` > `Thr`, a trade occurs. |
|
||||
| **PnL** | Unrealized PnL | The current PnL of your open position on Hyperliquid. |
|
||||
| **TotPnL** | Total PnL | The sum of Realized PnL (from closed rebalancing trades) + Unrealized PnL. |
|
||||
|
||||
---
|
||||
|
||||
## Detailed Examples
|
||||
|
||||
### Example 1: BNB (Stable)
|
||||
```text
|
||||
[IDLE] BNB | Px: 861.66 | M: 862.1 | B: 863.4 / S: 860.9 | delta: -0.5818(+0.0332) | Adj: +0.01%, Vol: 1.00, Thr: 0.0873 | PnL: -0.04 | TotPnL: -0.04
|
||||
```
|
||||
* **Status:** The bot wants to be Short `0.5818` BNB.
|
||||
* **Reality:** It is currently off by `+0.0332` (meaning it has slightly *less* short than it wants).
|
||||
* **Action:** The difference (`0.0332`) is smaller than the Threshold (`0.0873`), so it does nothing.
|
||||
* **Triggers:** If price rises to `863.4` (B), it will Buy. If it drops to `860.9` (S), it will Sell.
|
||||
|
||||
### Example 2: ETH (Asymmetric Boost)
|
||||
```text
|
||||
[IDLE] ETH | Px: 2934.35 | M: 2935.3 | B: 2939.1 / S: 2931.5 | delta: -0.2957(+0.0111) | Adj: -0.35%, Vol: 1.00, Thr: 0.0444 | PnL: 0.05 | TotPnL: -303.71
|
||||
```
|
||||
* **Status:** The bot has applied a `-0.35%` adjustment (Adj) because price is likely near a range edge or moving favorably.
|
||||
* **Delta:** This adjustment reduced the target delta slightly.
|
||||
* **Safety:** The Volatility is low (`1.00`), keeping the Threshold tight (`0.0444`).
|
||||
|
||||
## How Calculations Work
|
||||
|
||||
1. **Net Delta:** Sum of all Uniswap V3 position deltas + `Adj` factor.
|
||||
2. **Diff:** `Target Delta` - `Current Hyperliquid Position`.
|
||||
3. **Triggers (B/S):** Calculated using the **Gamma** (rate of change of delta).
|
||||
* `Gamma` tells us "How much does Delta change for a $1 price move?"
|
||||
* `B = Price + (Threshold + Diff) / Gamma`
|
||||
* `S = Price - (Threshold - Diff) / Gamma`
|
||||
* *Note:* These are estimates. In tight ranges, Gamma increases rapidly, so actual trades may happen before these prices are hit.
|
||||
12
florida/requirements.txt
Normal file
12
florida/requirements.txt
Normal file
@ -0,0 +1,12 @@
|
||||
# Core Web3 and Blockchain interaction
|
||||
web3>=7.0.0
|
||||
eth-account>=0.13.0
|
||||
|
||||
# Hyperliquid SDK for hedging
|
||||
hyperliquid-python-sdk>=0.6.0
|
||||
|
||||
# Environment and Configuration
|
||||
python-dotenv>=1.0.0
|
||||
|
||||
# Utility
|
||||
requests>=2.31.0
|
||||
BIN
florida/summaries/5178639.PNG
Normal file
BIN
florida/summaries/5178639.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
@ -0,0 +1,814 @@
|
||||
1766982585168, 2025-12-29 05:29:45,168 - [STRAT] Init 6153292 (BNB) | Range: 856.6782-882.4136
|
||||
1766982585988, 2025-12-29 05:29:45,988 - [WARN] LARGE HEDGE: 0.5795 > 0.0869 (x5.0)
|
||||
1766982586249, 2025-12-29 05:29:46,249 - [TRIG] Net BNB: SELL 0.5795 | Tgt: -0.5795 / Cur: 0.0000 | Thresh: 0.0869
|
||||
1766982586250, 2025-12-29 05:29:46,250 - [ORDER] IOC BNB SELL 0.579 @ 868.4
|
||||
1766982588546, 2025-12-29 05:29:48,546 - Order filled immediately.
|
||||
1766982588547, 2025-12-29 05:29:48,547 - [SHADOW] Created Maker SELL @ 869.28
|
||||
1766982588548, 2025-12-29 05:29:48,548 - Sleeping 5s to allow position update...
|
||||
1766982600941, 2025-12-29 05:30:00,941 - [IDLE] ETH | Px: 3043.85 | M: 3036.1 | B: 3046.7 / S: 3025.6 | delta: -0.1580(-0.0174) | Adj: -3.81%, Vol: 2.51, Thr: 0.0237 | PnL: -10.62 | TotPnL: -10.62
|
||||
1766982600943, 2025-12-29 05:30:00,943 - [IDLE] BNB | Px: 869.28 | M: 869.3 | B: 871.2 / S: 867.3 | delta: -0.5793(-0.0003) | Adj: +0.08%, Vol: 1.33, Thr: 0.0869 | PnL: 0.01 | TotPnL: 0.01
|
||||
1766982630906, 2025-12-29 05:30:30,906 - [IDLE] ETH | Px: 3044.25 | M: 3036.9 | B: 3047.4 / S: 3026.4 | delta: -0.1571(-0.0165) | Adj: -3.83%, Vol: 2.31, Thr: 0.0236 | PnL: -10.60 | TotPnL: -10.60
|
||||
1766982630909, 2025-12-29 05:30:30,909 - [IDLE] BNB | Px: 869.36 | M: 869.4 | B: 871.4 / S: 867.5 | delta: -0.5752(+0.0038) | Adj: +0.03%, Vol: 1.28, Thr: 0.0863 | PnL: -0.09 | TotPnL: -0.09
|
||||
1766982690463, 2025-12-29 05:31:30,463 - [IDLE] ETH | Px: 3041.65 | M: 3031.7 | B: 3042.6 / S: 3020.9 | delta: -0.1630(-0.0224) | Adj: -3.69%, Vol: 1.77, Thr: 0.0244 | PnL: -10.32 | TotPnL: -10.32
|
||||
1766982690465, 2025-12-29 05:31:30,465 - [IDLE] BNB | Px: 868.84 | M: 868.3 | B: 870.4 / S: 866.3 | delta: -0.6007(-0.0217) | Adj: +0.34%, Vol: 1.00, Thr: 0.0901 | PnL: 0.23 | TotPnL: 0.23
|
||||
1766982720857, 2025-12-29 05:32:00,857 - [IDLE] ETH | Px: 3041.85 | M: 3032.1 | B: 3042.9 / S: 3021.3 | delta: -0.1625(-0.0219) | Adj: -3.70%, Vol: 1.41, Thr: 0.0244 | PnL: -10.36 | TotPnL: -10.36
|
||||
1766982720859, 2025-12-29 05:32:00,859 - [IDLE] BNB | Px: 868.84 | M: 868.4 | B: 870.4 / S: 866.4 | delta: -0.6002(-0.0212) | Adj: +0.33%, Vol: 1.00, Thr: 0.0900 | PnL: 0.19 | TotPnL: 0.19
|
||||
1766982810334, 2025-12-29 05:33:30,334 - [IDLE] ETH | Px: 3042.05 | M: 3032.5 | B: 3043.3 / S: 3021.7 | delta: -0.1621(-0.0215) | Adj: -3.71%, Vol: 1.00, Thr: 0.0243 | PnL: -10.33 | TotPnL: -10.33
|
||||
1766982810337, 2025-12-29 05:33:30,337 - [IDLE] BNB | Px: 868.98 | M: 868.6 | B: 870.6 / S: 866.6 | delta: -0.5940(-0.0150) | Adj: +0.26%, Vol: 1.00, Thr: 0.0891 | PnL: 0.17 | TotPnL: 0.17
|
||||
1766982870958, 2025-12-29 05:34:30,958 - [IDLE] ETH | Px: 3045.55 | M: 3039.5 | B: 3049.8 / S: 3029.2 | delta: -0.1542(-0.0136) | Adj: -3.89%, Vol: 1.00, Thr: 0.0231 | PnL: -10.84 | TotPnL: -10.84
|
||||
1766982870961, 2025-12-29 05:34:30,961 - [IDLE] BNB | Px: 869.40 | M: 869.5 | B: 871.5 / S: 867.6 | delta: -0.5736(+0.0054) | Adj: +0.01%, Vol: 1.00, Thr: 0.0860 | PnL: -0.08 | TotPnL: -0.08
|
||||
1766982900857, 2025-12-29 05:35:00,857 - [IDLE] ETH | Px: 3046.55 | M: 3041.5 | B: 3051.6 / S: 3031.4 | delta: -0.1519(-0.0113) | Adj: -3.95%, Vol: 1.00, Thr: 0.0228 | PnL: -10.99 | TotPnL: -10.99
|
||||
1766982900859, 2025-12-29 05:35:00,859 - [IDLE] BNB | Px: 869.72 | M: 870.2 | B: 872.1 / S: 868.3 | delta: -0.5580(+0.0210) | Adj: -0.18%, Vol: 1.00, Thr: 0.0837 | PnL: -0.24 | TotPnL: -0.24
|
||||
1766982960405, 2025-12-29 05:36:00,405 - [IDLE] ETH | Px: 3047.65 | M: 3043.7 | B: 3053.7 / S: 3033.7 | delta: -0.1495(-0.0089) | Adj: -4.00%, Vol: 1.12, Thr: 0.0224 | PnL: -11.11 | TotPnL: -11.11
|
||||
1766982960407, 2025-12-29 05:36:00,407 - [IDLE] BNB | Px: 870.04 | M: 870.8 | B: 872.7 / S: 869.0 | delta: -0.5433(+0.0357) | Adj: -0.36%, Vol: 1.00, Thr: 0.0815 | PnL: -0.45 | TotPnL: -0.45
|
||||
1766983020846, 2025-12-29 05:37:00,846 - [IDLE] ETH | Px: 3049.15 | M: 3046.7 | B: 3056.5 / S: 3036.9 | delta: -0.1461(-0.0055) | Adj: -4.08%, Vol: 1.53, Thr: 0.0219 | PnL: -11.32 | TotPnL: -11.32
|
||||
1766983020848, 2025-12-29 05:37:00,848 - [IDLE] BNB | Px: 870.31 | M: 871.4 | B: 873.2 / S: 869.6 | delta: -0.5302(+0.0488) | Adj: -0.52%, Vol: 1.04, Thr: 0.0795 | PnL: -0.55 | TotPnL: -0.55
|
||||
1766983050330, 2025-12-29 05:37:30,330 - [IDLE] ETH | Px: 3049.85 | M: 3048.1 | B: 3057.7 / S: 3038.4 | delta: -0.1446(-0.0040) | Adj: -4.12%, Vol: 1.74, Thr: 0.0217 | PnL: -11.46 | TotPnL: -11.46
|
||||
1766983050333, 2025-12-29 05:37:30,333 - [IDLE] BNB | Px: 870.26 | M: 871.3 | B: 873.1 / S: 869.5 | delta: -0.5328(+0.0462) | Adj: -0.49%, Vol: 1.14, Thr: 0.0799 | PnL: -0.61 | TotPnL: -0.61
|
||||
1766983110106, 2025-12-29 05:38:30,106 - [IDLE] ETH | Px: 3051.45 | M: 3051.3 | B: 3060.7 / S: 3041.8 | delta: -0.1410(-0.0004) | Adj: -4.20%, Vol: 2.05, Thr: 0.0211 | PnL: -11.64 | TotPnL: -11.64
|
||||
1766983110109, 2025-12-29 05:38:30,109 - [IDLE] BNB | Px: 870.32 | M: 871.4 | B: 873.2 / S: 869.6 | delta: -0.5300(+0.0490) | Adj: -0.52%, Vol: 1.24, Thr: 0.0795 | PnL: -0.63 | TotPnL: -0.63
|
||||
1766983140082, 2025-12-29 05:39:00,082 - [IDLE] ETH | Px: 3051.45 | M: 3051.3 | B: 3060.7 / S: 3041.8 | delta: -0.1410(-0.0004) | Adj: -4.20%, Vol: 2.24, Thr: 0.0211 | PnL: -11.61 | TotPnL: -11.61
|
||||
1766983140084, 2025-12-29 05:39:00,084 - [IDLE] BNB | Px: 870.22 | M: 871.2 | B: 873.0 / S: 869.4 | delta: -0.5345(+0.0445) | Adj: -0.47%, Vol: 1.28, Thr: 0.0802 | PnL: -0.58 | TotPnL: -0.58
|
||||
1766983170916, 2025-12-29 05:39:30,916 - [IDLE] ETH | Px: 3049.05 | M: 3046.5 | B: 3056.3 / S: 3036.7 | delta: -0.1463(-0.0057) | Adj: -4.07%, Vol: 2.27, Thr: 0.0220 | PnL: -11.33 | TotPnL: -11.33
|
||||
1766983170918, 2025-12-29 05:39:30,918 - [IDLE] BNB | Px: 869.91 | M: 870.6 | B: 872.4 / S: 868.7 | delta: -0.5492(+0.0298) | Adj: -0.29%, Vol: 1.27, Thr: 0.0824 | PnL: -0.38 | TotPnL: -0.38
|
||||
1766983200818, 2025-12-29 05:40:00,818 - [IDLE] ETH | Px: 3049.05 | M: 3046.5 | B: 3056.3 / S: 3036.7 | delta: -0.1463(-0.0057) | Adj: -4.07%, Vol: 2.29, Thr: 0.0220 | PnL: -11.33 | TotPnL: -11.33
|
||||
1766983200821, 2025-12-29 05:40:00,821 - [IDLE] BNB | Px: 869.98 | M: 870.7 | B: 872.6 / S: 868.9 | delta: -0.5459(+0.0331) | Adj: -0.33%, Vol: 1.28, Thr: 0.0819 | PnL: -0.39 | TotPnL: -0.39
|
||||
1766983260084, 2025-12-29 05:41:00,084 - [IDLE] ETH | Px: 3049.25 | M: 3046.9 | B: 3056.6 / S: 3037.1 | delta: -0.1459(-0.0053) | Adj: -4.08%, Vol: 2.22, Thr: 0.0219 | PnL: -11.36 | TotPnL: -11.36
|
||||
1766983260086, 2025-12-29 05:41:00,086 - [IDLE] BNB | Px: 870.06 | M: 870.9 | B: 872.7 / S: 869.1 | delta: -0.5423(+0.0367) | Adj: -0.37%, Vol: 1.23, Thr: 0.0813 | PnL: -0.46 | TotPnL: -0.46
|
||||
1766983290675, 2025-12-29 05:41:30,675 - [IDLE] ETH | Px: 3046.55 | M: 3041.5 | B: 3051.6 / S: 3031.4 | delta: -0.1519(-0.0113) | Adj: -3.95%, Vol: 2.07, Thr: 0.0228 | PnL: -11.01 | TotPnL: -11.01
|
||||
1766983290678, 2025-12-29 05:41:30,678 - [IDLE] BNB | Px: 869.51 | M: 869.8 | B: 871.7 / S: 867.8 | delta: -0.5683(+0.0107) | Adj: -0.05%, Vol: 1.14, Thr: 0.0852 | PnL: -0.25 | TotPnL: -0.25
|
||||
1766983320684, 2025-12-29 05:42:00,684 - [IDLE] ETH | Px: 3045.85 | M: 3040.1 | B: 3050.4 / S: 3029.9 | delta: -0.1535(-0.0129) | Adj: -3.91%, Vol: 1.90, Thr: 0.0230 | PnL: -10.90 | TotPnL: -10.90
|
||||
1766983320686, 2025-12-29 05:42:00,686 - [IDLE] BNB | Px: 869.32 | M: 869.4 | B: 871.3 / S: 867.4 | delta: -0.5772(+0.0018) | Adj: +0.05%, Vol: 1.05, Thr: 0.0866 | PnL: -0.06 | TotPnL: -0.06
|
||||
1766983410347, 2025-12-29 05:43:30,347 - [IDLE] ETH | Px: 3047.35 | M: 3043.1 | B: 3053.1 / S: 3033.1 | delta: -0.1501(-0.0095) | Adj: -3.99%, Vol: 1.20, Thr: 0.0225 | PnL: -11.11 | TotPnL: -11.11
|
||||
1766983410350, 2025-12-29 05:43:30,350 - [IDLE] BNB | Px: 869.52 | M: 869.8 | B: 871.7 / S: 867.9 | delta: -0.5676(+0.0114) | Adj: -0.06%, Vol: 1.00, Thr: 0.0851 | PnL: -0.17 | TotPnL: -0.17
|
||||
1766983440872, 2025-12-29 05:44:00,872 - [IDLE] ETH | Px: 3047.15 | M: 3042.7 | B: 3052.8 / S: 3032.6 | delta: -0.1506(-0.0100) | Adj: -3.98%, Vol: 1.06, Thr: 0.0226 | PnL: -11.07 | TotPnL: -11.07
|
||||
1766983440874, 2025-12-29 05:44:00,874 - [IDLE] BNB | Px: 869.34 | M: 869.4 | B: 871.3 / S: 867.4 | delta: -0.5767(+0.0023) | Adj: +0.05%, Vol: 1.00, Thr: 0.0865 | PnL: -0.10 | TotPnL: -0.10
|
||||
1766983500399, 2025-12-29 05:45:00,399 - [IDLE] ETH | Px: 3045.85 | M: 3040.1 | B: 3050.4 / S: 3029.9 | delta: -0.1535(-0.0129) | Adj: -3.91%, Vol: 1.07, Thr: 0.0230 | PnL: -10.90 | TotPnL: -10.90
|
||||
1766983500402, 2025-12-29 05:45:00,402 - [IDLE] BNB | Px: 869.44 | M: 869.6 | B: 871.5 / S: 867.7 | delta: -0.5714(+0.0076) | Adj: -0.02%, Vol: 1.00, Thr: 0.0857 | PnL: -0.13 | TotPnL: -0.13
|
||||
1766983530852, 2025-12-29 05:45:30,852 - [IDLE] ETH | Px: 3045.05 | M: 3038.5 | B: 3048.9 / S: 3028.2 | delta: -0.1553(-0.0147) | Adj: -3.87%, Vol: 1.13, Thr: 0.0233 | PnL: -10.73 | TotPnL: -10.73
|
||||
1766983530854, 2025-12-29 05:45:30,854 - [IDLE] BNB | Px: 869.32 | M: 869.3 | B: 871.3 / S: 867.4 | delta: -0.5776(+0.0014) | Adj: +0.06%, Vol: 1.00, Thr: 0.0866 | PnL: -0.05 | TotPnL: -0.05
|
||||
1766983650920, 2025-12-29 05:47:30,920 - [IDLE] ETH | Px: 3044.15 | M: 3036.7 | B: 3047.2 / S: 3026.2 | delta: -0.1573(-0.0167) | Adj: -3.82%, Vol: 1.40, Thr: 0.0236 | PnL: -10.66 | TotPnL: -10.66
|
||||
1766983650922, 2025-12-29 05:47:30,922 - [IDLE] BNB | Px: 869.22 | M: 869.1 | B: 871.1 / S: 867.2 | delta: -0.5822(-0.0032) | Adj: +0.12%, Vol: 1.00, Thr: 0.0873 | PnL: 0.01 | TotPnL: 0.01
|
||||
1766983680725, 2025-12-29 05:48:00,725 - [IDLE] ETH | Px: 3044.25 | M: 3036.9 | B: 3047.4 / S: 3026.4 | delta: -0.1571(-0.0165) | Adj: -3.83%, Vol: 1.35, Thr: 0.0236 | PnL: -10.64 | TotPnL: -10.64
|
||||
1766983680728, 2025-12-29 05:48:00,728 - [IDLE] BNB | Px: 869.28 | M: 869.3 | B: 871.2 / S: 867.3 | delta: -0.5791(-0.0001) | Adj: +0.08%, Vol: 1.00, Thr: 0.0869 | PnL: -0.04 | TotPnL: -0.04
|
||||
1766983710673, 2025-12-29 05:48:30,673 - [IDLE] ETH | Px: 3044.15 | M: 3036.7 | B: 3047.2 / S: 3026.2 | delta: -0.1573(-0.0167) | Adj: -3.82%, Vol: 1.20, Thr: 0.0236 | PnL: -10.63 | TotPnL: -10.63
|
||||
1766983710676, 2025-12-29 05:48:30,676 - [IDLE] BNB | Px: 869.13 | M: 869.0 | B: 870.9 / S: 867.0 | delta: -0.5865(-0.0075) | Adj: +0.17%, Vol: 1.00, Thr: 0.0880 | PnL: 0.04 | TotPnL: 0.04
|
||||
1766983830715, 2025-12-29 05:50:30,715 - [IDLE] ETH | Px: 3043.35 | M: 3035.1 | B: 3045.7 / S: 3024.5 | delta: -0.1591(-0.0185) | Adj: -3.78%, Vol: 1.08, Thr: 0.0239 | PnL: -10.53 | TotPnL: -10.53
|
||||
1766983830718, 2025-12-29 05:50:30,718 - [IDLE] BNB | Px: 868.78 | M: 868.2 | B: 870.3 / S: 866.2 | delta: -0.6031(-0.0241) | Adj: +0.37%, Vol: 1.00, Thr: 0.0905 | PnL: 0.30 | TotPnL: 0.30
|
||||
1766983860864, 2025-12-29 05:51:00,864 - [IDLE] ETH | Px: 3043.65 | M: 3035.7 | B: 3046.3 / S: 3025.2 | delta: -0.1584(-0.0178) | Adj: -3.80%, Vol: 1.00, Thr: 0.0238 | PnL: -10.59 | TotPnL: -10.59
|
||||
1766983860867, 2025-12-29 05:51:00,867 - [IDLE] BNB | Px: 868.94 | M: 868.6 | B: 870.6 / S: 866.6 | delta: -0.5959(-0.0169) | Adj: +0.28%, Vol: 1.00, Thr: 0.0894 | PnL: 0.20 | TotPnL: 0.20
|
||||
1766983920930, 2025-12-29 05:52:00,930 - [IDLE] ETH | Px: 3041.15 | M: 3030.7 | B: 3041.7 / S: 3019.8 | delta: -0.1641(-0.0235) | Adj: -3.67%, Vol: 1.11, Thr: 0.0246 | PnL: -10.22 | TotPnL: -10.22
|
||||
1766983920933, 2025-12-29 05:52:00,933 - [IDLE] BNB | Px: 868.50 | M: 867.7 | B: 869.7 / S: 865.6 | delta: -0.6167(-0.0377) | Adj: +0.53%, Vol: 1.00, Thr: 0.0925 | PnL: 0.47 | TotPnL: 0.47
|
||||
1766983924859, 2025-12-29 05:52:04,859 - [TRIG] Net ETH: SELL 0.0248 | Tgt: -0.1654 / Cur: -0.1406 | Thresh: 0.0248
|
||||
1766983924860, 2025-12-29 05:52:04,860 - [ORDER] ALO ETH SELL 0.0248 @ 3040.6
|
||||
1766983926618, 2025-12-29 05:52:06,618 - Sleeping 5s to allow position update...
|
||||
1766983950997, 2025-12-29 05:52:30,997 - [IDLE] ETH | Px: 3040.25 | M: 3039.9 | B: 3051.0 / S: 3028.9 | delta: -0.1661(-0.0007) | Adj: -3.62%, Vol: 1.16, Thr: 0.0249 | PnL: -10.12 | TotPnL: -10.12
|
||||
1766984010082, 2025-12-29 05:53:30,082 - [IDLE] ETH | Px: 3039.05 | M: 3037.5 | B: 3048.8 / S: 3026.3 | delta: -0.1688(-0.0034) | Adj: -3.56%, Vol: 1.33, Thr: 0.0253 | PnL: -9.89 | TotPnL: -9.89
|
||||
1766984010085, 2025-12-29 05:53:30,085 - [IDLE] BNB | Px: 868.06 | M: 866.7 | B: 868.9 / S: 864.6 | delta: -0.6383(-0.0593) | Adj: +0.79%, Vol: 1.00, Thr: 0.0957 | PnL: 0.65 | TotPnL: 0.65
|
||||
1766984040268, 2025-12-29 05:54:00,268 - [IDLE] ETH | Px: 3039.35 | M: 3038.1 | B: 3049.3 / S: 3026.9 | delta: -0.1682(-0.0028) | Adj: -3.57%, Vol: 1.40, Thr: 0.0252 | PnL: -9.95 | TotPnL: -9.95
|
||||
1766984040271, 2025-12-29 05:54:00,271 - [IDLE] BNB | Px: 868.13 | M: 866.9 | B: 869.0 / S: 864.7 | delta: -0.6349(-0.0559) | Adj: +0.75%, Vol: 1.05, Thr: 0.0952 | PnL: 0.65 | TotPnL: 0.65
|
||||
1766984070708, 2025-12-29 05:54:30,708 - [IDLE] ETH | Px: 3039.35 | M: 3038.1 | B: 3049.3 / S: 3026.9 | delta: -0.1682(-0.0028) | Adj: -3.57%, Vol: 1.45, Thr: 0.0252 | PnL: -9.94 | TotPnL: -9.94
|
||||
1766984070711, 2025-12-29 05:54:30,711 - [IDLE] BNB | Px: 868.10 | M: 866.8 | B: 869.0 / S: 864.7 | delta: -0.6363(-0.0573) | Adj: +0.77%, Vol: 1.09, Thr: 0.0955 | PnL: 0.66 | TotPnL: 0.66
|
||||
1766984100726, 2025-12-29 05:55:00,726 - [IDLE] ETH | Px: 3040.15 | M: 3039.7 | B: 3050.8 / S: 3028.7 | delta: -0.1663(-0.0009) | Adj: -3.62%, Vol: 1.45, Thr: 0.0250 | PnL: -10.09 | TotPnL: -10.09
|
||||
1766984100729, 2025-12-29 05:55:00,729 - [IDLE] BNB | Px: 868.14 | M: 866.9 | B: 869.0 / S: 864.8 | delta: -0.6342(-0.0552) | Adj: +0.74%, Vol: 1.08, Thr: 0.0951 | PnL: 0.65 | TotPnL: 0.65
|
||||
1766984130556, 2025-12-29 05:55:30,556 - [IDLE] ETH | Px: 3042.05 | M: 3043.5 | B: 3054.3 / S: 3032.7 | delta: -0.1621(+0.0033) | Adj: -3.71%, Vol: 1.38, Thr: 0.0243 | PnL: -10.35 | TotPnL: -10.35
|
||||
1766984130558, 2025-12-29 05:55:30,558 - [IDLE] BNB | Px: 868.56 | M: 867.8 | B: 869.8 / S: 865.7 | delta: -0.6138(-0.0348) | Adj: +0.50%, Vol: 1.04, Thr: 0.0921 | PnL: 0.42 | TotPnL: 0.42
|
||||
1766984160838, 2025-12-29 05:56:00,838 - [IDLE] ETH | Px: 3042.45 | M: 3044.3 | B: 3055.1 / S: 3033.6 | delta: -0.1611(+0.0043) | Adj: -3.73%, Vol: 1.32, Thr: 0.0242 | PnL: -10.45 | TotPnL: -10.45
|
||||
1766984160841, 2025-12-29 05:56:00,841 - [IDLE] BNB | Px: 868.54 | M: 867.7 | B: 869.8 / S: 865.7 | delta: -0.6150(-0.0360) | Adj: +0.51%, Vol: 1.00, Thr: 0.0922 | PnL: 0.42 | TotPnL: 0.42
|
||||
1766984190576, 2025-12-29 05:56:30,576 - [IDLE] ETH | Px: 3041.05 | M: 3041.5 | B: 3052.5 / S: 3030.6 | delta: -0.1643(+0.0011) | Adj: -3.66%, Vol: 1.27, Thr: 0.0246 | PnL: -10.22 | TotPnL: -10.22
|
||||
1766984190579, 2025-12-29 05:56:30,579 - [IDLE] BNB | Px: 868.38 | M: 867.4 | B: 869.5 / S: 865.3 | delta: -0.6227(-0.0437) | Adj: +0.61%, Vol: 1.00, Thr: 0.0934 | PnL: 0.50 | TotPnL: 0.50
|
||||
1766984220961, 2025-12-29 05:57:00,961 - [IDLE] ETH | Px: 3040.55 | M: 3040.5 | B: 3051.5 / S: 3029.5 | delta: -0.1654(-0.0000) | Adj: -3.64%, Vol: 1.22, Thr: 0.0248 | PnL: -10.15 | TotPnL: -10.15
|
||||
1766984220964, 2025-12-29 05:57:00,964 - [IDLE] BNB | Px: 868.49 | M: 867.6 | B: 869.7 / S: 865.6 | delta: -0.6174(-0.0384) | Adj: +0.54%, Vol: 1.00, Thr: 0.0926 | PnL: 0.43 | TotPnL: 0.43
|
||||
1766984400215, 2025-12-29 06:00:00,215 - [IDLE] ETH | Px: 3038.65 | M: 3036.7 | B: 3048.0 / S: 3025.4 | delta: -0.1697(-0.0043) | Adj: -3.54%, Vol: 1.00, Thr: 0.0255 | PnL: -9.80 | TotPnL: -9.80
|
||||
1766984400218, 2025-12-29 06:00:00,218 - [IDLE] BNB | Px: 868.38 | M: 867.4 | B: 869.5 / S: 865.3 | delta: -0.6230(-0.0440) | Adj: +0.61%, Vol: 1.00, Thr: 0.0934 | PnL: 0.53 | TotPnL: 0.53
|
||||
1766984460070, 2025-12-29 06:01:00,070 - [IDLE] ETH | Px: 3039.85 | M: 3039.1 | B: 3050.2 / S: 3028.0 | delta: -0.1670(-0.0016) | Adj: -3.60%, Vol: 1.00, Thr: 0.0251 | PnL: -10.04 | TotPnL: -10.04
|
||||
1766984460073, 2025-12-29 06:01:00,073 - [IDLE] BNB | Px: 868.32 | M: 867.3 | B: 869.4 / S: 865.2 | delta: -0.6259(-0.0469) | Adj: +0.64%, Vol: 1.00, Thr: 0.0939 | PnL: 0.56 | TotPnL: 0.56
|
||||
1766984490373, 2025-12-29 06:01:30,373 - [IDLE] ETH | Px: 3039.85 | M: 3039.1 | B: 3050.2 / S: 3028.0 | delta: -0.1670(-0.0016) | Adj: -3.60%, Vol: 1.00, Thr: 0.0251 | PnL: -10.02 | TotPnL: -10.02
|
||||
1766984490375, 2025-12-29 06:01:30,375 - [IDLE] BNB | Px: 868.32 | M: 867.3 | B: 869.4 / S: 865.2 | delta: -0.6259(-0.0469) | Adj: +0.64%, Vol: 1.00, Thr: 0.0939 | PnL: 0.55 | TotPnL: 0.55
|
||||
1766984520649, 2025-12-29 06:02:00,649 - [IDLE] ETH | Px: 3039.85 | M: 3039.1 | B: 3050.2 / S: 3028.0 | delta: -0.1670(-0.0016) | Adj: -3.60%, Vol: 1.00, Thr: 0.0251 | PnL: -10.02 | TotPnL: -10.02
|
||||
1766984520651, 2025-12-29 06:02:00,651 - [IDLE] BNB | Px: 868.30 | M: 867.2 | B: 869.3 / S: 865.1 | delta: -0.6269(-0.0479) | Adj: +0.65%, Vol: 1.00, Thr: 0.0940 | PnL: 0.55 | TotPnL: 0.55
|
||||
1766984610641, 2025-12-29 06:03:30,641 - [IDLE] ETH | Px: 3035.45 | M: 3030.3 | B: 3042.1 / S: 3018.6 | delta: -0.1770(-0.0116) | Adj: -3.37%, Vol: 1.00, Thr: 0.0266 | PnL: -9.51 | TotPnL: -9.51
|
||||
1766984610643, 2025-12-29 06:03:30,643 - [IDLE] BNB | Px: 868.38 | M: 867.4 | B: 869.5 / S: 865.3 | delta: -0.6225(-0.0435) | Adj: +0.60%, Vol: 1.00, Thr: 0.0934 | PnL: 0.52 | TotPnL: 0.52
|
||||
1766984640266, 2025-12-29 06:04:00,266 - [IDLE] ETH | Px: 3033.45 | M: 3026.3 | B: 3038.3 / S: 3014.3 | delta: -0.1816(-0.0162) | Adj: -3.27%, Vol: 1.21, Thr: 0.0272 | PnL: -8.99 | TotPnL: -8.99
|
||||
1766984640269, 2025-12-29 06:04:00,269 - [IDLE] BNB | Px: 868.18 | M: 867.0 | B: 869.1 / S: 864.9 | delta: -0.6322(-0.0532) | Adj: +0.72%, Vol: 1.00, Thr: 0.0948 | PnL: 0.65 | TotPnL: 0.65
|
||||
1766984670519, 2025-12-29 06:04:30,519 - [IDLE] ETH | Px: 3034.35 | M: 3028.1 | B: 3040.0 / S: 3016.2 | delta: -0.1795(-0.0141) | Adj: -3.32%, Vol: 1.45, Thr: 0.0269 | PnL: -9.06 | TotPnL: -9.06
|
||||
1766984670522, 2025-12-29 06:04:30,522 - [IDLE] BNB | Px: 868.22 | M: 867.1 | B: 869.2 / S: 864.9 | delta: -0.6305(-0.0515) | Adj: +0.70%, Vol: 1.00, Thr: 0.0946 | PnL: 0.63 | TotPnL: 0.63
|
||||
1766984730700, 2025-12-29 06:05:30,700 - [IDLE] ETH | Px: 3034.55 | M: 3028.5 | B: 3040.4 / S: 3016.6 | delta: -0.1791(-0.0137) | Adj: -3.33%, Vol: 1.71, Thr: 0.0269 | PnL: -9.16 | TotPnL: -9.16
|
||||
1766984730703, 2025-12-29 06:05:30,703 - [IDLE] BNB | Px: 868.53 | M: 867.7 | B: 869.8 / S: 865.6 | delta: -0.6155(-0.0365) | Adj: +0.52%, Vol: 1.00, Thr: 0.0923 | PnL: 0.46 | TotPnL: 0.46
|
||||
1766984760287, 2025-12-29 06:06:00,287 - [IDLE] ETH | Px: 3035.35 | M: 3030.1 | B: 3041.9 / S: 3018.3 | delta: -0.1772(-0.0118) | Adj: -3.37%, Vol: 1.68, Thr: 0.0266 | PnL: -9.29 | TotPnL: -9.29
|
||||
1766984760288, 2025-12-29 06:06:00,288 - [IDLE] BNB | Px: 868.44 | M: 867.5 | B: 869.6 / S: 865.5 | delta: -0.6196(-0.0406) | Adj: +0.57%, Vol: 1.00, Thr: 0.0929 | PnL: 0.47 | TotPnL: 0.47
|
||||
1766984850199, 2025-12-29 06:07:30,199 - [IDLE] ETH | Px: 3034.25 | M: 3027.9 | B: 3039.8 / S: 3016.0 | delta: -0.1797(-0.0143) | Adj: -3.31%, Vol: 1.66, Thr: 0.0270 | PnL: -9.16 | TotPnL: -9.16
|
||||
1766984850201, 2025-12-29 06:07:30,201 - [IDLE] BNB | Px: 868.26 | M: 867.1 | B: 869.3 / S: 865.0 | delta: -0.6288(-0.0498) | Adj: +0.68%, Vol: 1.00, Thr: 0.0943 | PnL: 0.53 | TotPnL: 0.53
|
||||
1766984880382, 2025-12-29 06:08:00,382 - [IDLE] ETH | Px: 3034.55 | M: 3028.5 | B: 3040.4 / S: 3016.6 | delta: -0.1791(-0.0137) | Adj: -3.33%, Vol: 1.64, Thr: 0.0269 | PnL: -9.16 | TotPnL: -9.16
|
||||
1766984880384, 2025-12-29 06:08:00,384 - [IDLE] BNB | Px: 867.64 | M: 865.9 | B: 868.1 / S: 863.7 | delta: -0.6586(-0.0796) | Adj: +1.03%, Vol: 1.00, Thr: 0.0988 | PnL: 0.94 | TotPnL: 0.94
|
||||
1766984940003, 2025-12-29 06:09:00,003 - [IDLE] ETH | Px: 3038.15 | M: 3035.7 | B: 3047.1 / S: 3024.4 | delta: -0.1709(-0.0055) | Adj: -3.51%, Vol: 1.50, Thr: 0.0256 | PnL: -9.70 | TotPnL: -9.70
|
||||
1766984940006, 2025-12-29 06:09:00,006 - [IDLE] BNB | Px: 868.10 | M: 866.8 | B: 869.0 / S: 864.7 | delta: -0.6363(-0.0573) | Adj: +0.77%, Vol: 1.00, Thr: 0.0955 | PnL: 0.68 | TotPnL: 0.68
|
||||
1766985000707, 2025-12-29 06:10:00,707 - [IDLE] ETH | Px: 3034.45 | M: 3028.3 | B: 3040.2 / S: 3016.4 | delta: -0.1793(-0.0139) | Adj: -3.32%, Vol: 1.43, Thr: 0.0269 | PnL: -9.13 | TotPnL: -9.13
|
||||
1766985000710, 2025-12-29 06:10:00,710 - [IDLE] BNB | Px: 867.20 | M: 864.9 | B: 867.2 / S: 862.7 | delta: -0.6802(-0.1012) | Adj: +1.29%, Vol: 1.00, Thr: 0.1020 | PnL: 1.23 | TotPnL: 1.23
|
||||
1766985002853, 2025-12-29 06:10:02,853 - [TRIG] Net BNB: SELL 0.1085 | Tgt: -0.6875 / Cur: -0.5790 | Thresh: 0.1031
|
||||
1766985002853, 2025-12-29 06:10:02,853 - [ORDER] ALO BNB SELL 0.108 @ 867.1
|
||||
1766985004972, 2025-12-29 06:10:04,972 - Sleeping 5s to allow position update...
|
||||
1766985030719, 2025-12-29 06:10:30,719 - [IDLE] ETH | Px: 3035.65 | M: 3030.7 | B: 3042.4 / S: 3019.0 | delta: -0.1766(-0.0112) | Adj: -3.38%, Vol: 1.36, Thr: 0.0265 | PnL: -9.27 | TotPnL: -9.27
|
||||
1766985030721, 2025-12-29 06:10:30,721 - [WAIT] BNB Pending SELL Order 281210439159 @ 867.1 (Dist: 0.001%)
|
||||
1766985034615, 2025-12-29 06:10:34,615 - Cancelling idle order 281210439159 (A @ 867.1)
|
||||
1766985060669, 2025-12-29 06:11:00,669 - [IDLE] ETH | Px: 3036.75 | M: 3032.9 | B: 3044.5 / S: 3021.4 | delta: -0.1741(-0.0087) | Adj: -3.44%, Vol: 1.24, Thr: 0.0261 | PnL: -9.51 | TotPnL: -9.51
|
||||
1766985060672, 2025-12-29 06:11:00,672 - [IDLE] BNB | Px: 867.28 | M: 866.4 | B: 868.7 / S: 864.2 | delta: -0.6767(-0.0377) | Adj: +1.25%, Vol: 1.00, Thr: 0.1015 | PnL: 1.10 | TotPnL: 1.10
|
||||
1766985090464, 2025-12-29 06:11:30,464 - [IDLE] ETH | Px: 3037.95 | M: 3035.3 | B: 3046.7 / S: 3023.9 | delta: -0.1713(-0.0059) | Adj: -3.50%, Vol: 1.12, Thr: 0.0257 | PnL: -9.72 | TotPnL: -9.72
|
||||
1766985090467, 2025-12-29 06:11:30,467 - [IDLE] BNB | Px: 867.34 | M: 866.6 | B: 868.8 / S: 864.3 | delta: -0.6733(-0.0343) | Adj: +1.21%, Vol: 1.03, Thr: 0.1010 | PnL: 1.03 | TotPnL: 1.03
|
||||
1766985150181, 2025-12-29 06:12:30,181 - [IDLE] ETH | Px: 3038.75 | M: 3036.9 | B: 3048.2 / S: 3025.7 | delta: -0.1695(-0.0041) | Adj: -3.54%, Vol: 1.19, Thr: 0.0254 | PnL: -9.85 | TotPnL: -9.85
|
||||
1766985150184, 2025-12-29 06:12:30,184 - [IDLE] BNB | Px: 867.56 | M: 867.0 | B: 869.2 / S: 864.8 | delta: -0.6630(-0.0240) | Adj: +1.09%, Vol: 1.04, Thr: 0.0994 | PnL: 0.94 | TotPnL: 0.94
|
||||
1766985330338, 2025-12-29 06:15:30,338 - [IDLE] ETH | Px: 3040.25 | M: 3039.9 | B: 3051.0 / S: 3028.9 | delta: -0.1661(-0.0007) | Adj: -3.62%, Vol: 1.79, Thr: 0.0249 | PnL: -10.09 | TotPnL: -10.09
|
||||
1766985330341, 2025-12-29 06:15:30,341 - [IDLE] BNB | Px: 867.44 | M: 866.8 | B: 869.0 / S: 864.5 | delta: -0.6684(-0.0294) | Adj: +1.15%, Vol: 1.00, Thr: 0.1003 | PnL: 1.03 | TotPnL: 1.03
|
||||
1766985390060, 2025-12-29 06:16:30,060 - [IDLE] ETH | Px: 3039.55 | M: 3038.5 | B: 3049.7 / S: 3027.4 | delta: -0.1677(-0.0023) | Adj: -3.58%, Vol: 1.74, Thr: 0.0252 | PnL: -9.97 | TotPnL: -9.97
|
||||
1766985390062, 2025-12-29 06:16:30,062 - [IDLE] BNB | Px: 867.56 | M: 867.0 | B: 869.3 / S: 864.8 | delta: -0.6625(-0.0235) | Adj: +1.08%, Vol: 1.00, Thr: 0.0994 | PnL: 0.99 | TotPnL: 0.99
|
||||
1766985420317, 2025-12-29 06:17:00,317 - [IDLE] ETH | Px: 3039.75 | M: 3038.9 | B: 3050.1 / S: 3027.8 | delta: -0.1672(-0.0018) | Adj: -3.60%, Vol: 1.66, Thr: 0.0251 | PnL: -9.99 | TotPnL: -9.99
|
||||
1766985420320, 2025-12-29 06:17:00,320 - [IDLE] BNB | Px: 867.68 | M: 867.3 | B: 869.5 / S: 865.1 | delta: -0.6571(-0.0181) | Adj: +1.02%, Vol: 1.00, Thr: 0.0986 | PnL: 0.92 | TotPnL: 0.92
|
||||
1766985480870, 2025-12-29 06:18:00,870 - [IDLE] ETH | Px: 3040.45 | M: 3040.3 | B: 3051.4 / S: 3029.3 | delta: -0.1657(-0.0003) | Adj: -3.63%, Vol: 1.48, Thr: 0.0248 | PnL: -10.12 | TotPnL: -10.12
|
||||
1766985480873, 2025-12-29 06:18:00,873 - [IDLE] BNB | Px: 867.34 | M: 866.6 | B: 868.8 / S: 864.3 | delta: -0.6733(-0.0343) | Adj: +1.21%, Vol: 1.00, Thr: 0.1010 | PnL: 1.06 | TotPnL: 1.06
|
||||
1766985510732, 2025-12-29 06:18:30,732 - [IDLE] ETH | Px: 3038.25 | M: 3035.9 | B: 3047.3 / S: 3024.6 | delta: -0.1706(-0.0052) | Adj: -3.52%, Vol: 1.45, Thr: 0.0256 | PnL: -9.72 | TotPnL: -9.72
|
||||
1766985510735, 2025-12-29 06:18:30,735 - [IDLE] BNB | Px: 867.01 | M: 865.9 | B: 868.2 / S: 863.6 | delta: -0.6898(-0.0508) | Adj: +1.40%, Vol: 1.00, Thr: 0.1035 | PnL: 1.29 | TotPnL: 1.29
|
||||
1766985570705, 2025-12-29 06:19:30,705 - [IDLE] ETH | Px: 3037.25 | M: 3033.9 | B: 3045.4 / S: 3022.4 | delta: -0.1729(-0.0075) | Adj: -3.47%, Vol: 1.26, Thr: 0.0259 | PnL: -9.56 | TotPnL: -9.56
|
||||
1766985570707, 2025-12-29 06:19:30,707 - [IDLE] BNB | Px: 866.88 | M: 865.6 | B: 867.9 / S: 863.3 | delta: -0.6959(-0.0569) | Adj: +1.48%, Vol: 1.00, Thr: 0.1044 | PnL: 1.40 | TotPnL: 1.40
|
||||
1766985690574, 2025-12-29 06:21:30,574 - [IDLE] ETH | Px: 3036.75 | M: 3032.9 | B: 3044.5 / S: 3021.4 | delta: -0.1741(-0.0087) | Adj: -3.44%, Vol: 1.08, Thr: 0.0261 | PnL: -9.51 | TotPnL: -9.51
|
||||
1766985690577, 2025-12-29 06:21:30,577 - [IDLE] BNB | Px: 866.73 | M: 865.3 | B: 867.6 / S: 862.9 | delta: -0.7036(-0.0646) | Adj: +1.57%, Vol: 1.00, Thr: 0.1055 | PnL: 1.47 | TotPnL: 1.47
|
||||
1766985720479, 2025-12-29 06:22:00,479 - [IDLE] ETH | Px: 3036.55 | M: 3032.5 | B: 3044.1 / S: 3020.9 | delta: -0.1745(-0.0091) | Adj: -3.43%, Vol: 1.15, Thr: 0.0262 | PnL: -9.49 | TotPnL: -9.49
|
||||
1766985720482, 2025-12-29 06:22:00,482 - [IDLE] BNB | Px: 866.42 | M: 864.6 | B: 867.0 / S: 862.2 | delta: -0.7192(-0.0802) | Adj: +1.75%, Vol: 1.08, Thr: 0.1079 | PnL: 1.72 | TotPnL: 1.72
|
||||
1766985750172, 2025-12-29 06:22:30,172 - [IDLE] ETH | Px: 3037.25 | M: 3033.9 | B: 3045.4 / S: 3022.4 | delta: -0.1729(-0.0075) | Adj: -3.47%, Vol: 1.18, Thr: 0.0259 | PnL: -9.59 | TotPnL: -9.59
|
||||
1766985750175, 2025-12-29 06:22:30,175 - [IDLE] BNB | Px: 866.34 | M: 864.5 | B: 866.9 / S: 862.0 | delta: -0.7230(-0.0840) | Adj: +1.79%, Vol: 1.17, Thr: 0.1084 | PnL: 1.72 | TotPnL: 1.72
|
||||
1766985780899, 2025-12-29 06:23:00,899 - [IDLE] ETH | Px: 3037.05 | M: 3033.5 | B: 3045.0 / S: 3022.0 | delta: -0.1734(-0.0080) | Adj: -3.46%, Vol: 1.18, Thr: 0.0260 | PnL: -9.57 | TotPnL: -9.57
|
||||
1766985780902, 2025-12-29 06:23:00,902 - [IDLE] BNB | Px: 866.26 | M: 864.3 | B: 866.7 / S: 861.9 | delta: -0.7272(-0.0882) | Adj: +1.84%, Vol: 1.24, Thr: 0.1091 | PnL: 1.80 | TotPnL: 1.80
|
||||
1766985870557, 2025-12-29 06:24:30,557 - [IDLE] ETH | Px: 3038.55 | M: 3036.5 | B: 3047.8 / S: 3025.2 | delta: -0.1700(-0.0046) | Adj: -3.53%, Vol: 1.00, Thr: 0.0255 | PnL: -9.82 | TotPnL: -9.82
|
||||
1766985870559, 2025-12-29 06:24:30,559 - [IDLE] BNB | Px: 866.59 | M: 865.0 | B: 867.4 / S: 862.6 | delta: -0.7105(-0.0715) | Adj: +1.65%, Vol: 1.08, Thr: 0.1066 | PnL: 1.54 | TotPnL: 1.54
|
||||
1766985930277, 2025-12-29 06:25:30,277 - [IDLE] ETH | Px: 3036.75 | M: 3032.9 | B: 3044.5 / S: 3021.4 | delta: -0.1741(-0.0087) | Adj: -3.44%, Vol: 1.00, Thr: 0.0261 | PnL: -9.52 | TotPnL: -9.52
|
||||
1766985930280, 2025-12-29 06:25:30,280 - [IDLE] BNB | Px: 866.54 | M: 864.9 | B: 867.3 / S: 862.5 | delta: -0.7133(-0.0743) | Adj: +1.68%, Vol: 1.00, Thr: 0.1070 | PnL: 1.61 | TotPnL: 1.61
|
||||
1766985990861, 2025-12-29 06:26:30,861 - [IDLE] ETH | Px: 3036.65 | M: 3032.7 | B: 3044.3 / S: 3021.1 | delta: -0.1743(-0.0089) | Adj: -3.44%, Vol: 1.00, Thr: 0.0261 | PnL: -9.52 | TotPnL: -9.52
|
||||
1766985990864, 2025-12-29 06:26:30,864 - [IDLE] BNB | Px: 866.52 | M: 864.8 | B: 867.2 / S: 862.4 | delta: -0.7143(-0.0753) | Adj: +1.69%, Vol: 1.00, Thr: 0.1071 | PnL: 1.67 | TotPnL: 1.67
|
||||
1766986110034, 2025-12-29 06:28:30,034 - [IDLE] ETH | Px: 3035.85 | M: 3031.1 | B: 3042.8 / S: 3019.4 | delta: -0.1761(-0.0107) | Adj: -3.39%, Vol: 1.00, Thr: 0.0264 | PnL: -9.37 | TotPnL: -9.37
|
||||
1766986110037, 2025-12-29 06:28:30,037 - [IDLE] BNB | Px: 866.20 | M: 864.2 | B: 866.6 / S: 861.7 | delta: -0.7302(-0.0912) | Adj: +1.88%, Vol: 1.00, Thr: 0.1095 | PnL: 1.83 | TotPnL: 1.83
|
||||
1766986170521, 2025-12-29 06:29:30,521 - [IDLE] ETH | Px: 3035.45 | M: 3030.3 | B: 3042.1 / S: 3018.6 | delta: -0.1770(-0.0116) | Adj: -3.37%, Vol: 1.00, Thr: 0.0266 | PnL: -9.31 | TotPnL: -9.31
|
||||
1766986170524, 2025-12-29 06:29:30,524 - [IDLE] BNB | Px: 866.20 | M: 864.2 | B: 866.6 / S: 861.7 | delta: -0.7302(-0.0912) | Adj: +1.88%, Vol: 1.00, Thr: 0.1095 | PnL: 1.80 | TotPnL: 1.80
|
||||
1766986200655, 2025-12-29 06:30:00,655 - [IDLE] ETH | Px: 3035.25 | M: 3029.9 | B: 3041.7 / S: 3018.1 | delta: -0.1775(-0.0121) | Adj: -3.36%, Vol: 1.00, Thr: 0.0266 | PnL: -9.29 | TotPnL: -9.29
|
||||
1766986200657, 2025-12-29 06:30:00,657 - [IDLE] BNB | Px: 866.15 | M: 864.1 | B: 866.5 / S: 861.6 | delta: -0.7324(-0.0934) | Adj: +1.90%, Vol: 1.00, Thr: 0.1099 | PnL: 1.83 | TotPnL: 1.83
|
||||
1766986350574, 2025-12-29 06:32:30,574 - [IDLE] ETH | Px: 3034.95 | M: 3029.3 | B: 3041.1 / S: 3017.5 | delta: -0.1782(-0.0128) | Adj: -3.35%, Vol: 1.00, Thr: 0.0267 | PnL: -9.23 | TotPnL: -9.23
|
||||
1766986350576, 2025-12-29 06:32:30,576 - [IDLE] BNB | Px: 866.14 | M: 864.0 | B: 866.5 / S: 861.6 | delta: -0.7332(-0.0942) | Adj: +1.91%, Vol: 1.00, Thr: 0.1100 | PnL: 1.87 | TotPnL: 1.87
|
||||
1766986380986, 2025-12-29 06:33:00,986 - [IDLE] ETH | Px: 3034.65 | M: 3028.7 | B: 3040.6 / S: 3016.8 | delta: -0.1788(-0.0134) | Adj: -3.33%, Vol: 1.00, Thr: 0.0268 | PnL: -9.14 | TotPnL: -9.14
|
||||
1766986380989, 2025-12-29 06:33:00,989 - [IDLE] BNB | Px: 866.14 | M: 864.0 | B: 866.5 / S: 861.6 | delta: -0.7332(-0.0942) | Adj: +1.91%, Vol: 1.00, Thr: 0.1100 | PnL: 1.87 | TotPnL: 1.87
|
||||
1766986440933, 2025-12-29 06:34:00,933 - [IDLE] ETH | Px: 3035.65 | M: 3030.7 | B: 3042.4 / S: 3019.0 | delta: -0.1766(-0.0112) | Adj: -3.38%, Vol: 1.00, Thr: 0.0265 | PnL: -9.27 | TotPnL: -9.27
|
||||
1766986440935, 2025-12-29 06:34:00,935 - [IDLE] BNB | Px: 866.24 | M: 864.2 | B: 866.7 / S: 861.8 | delta: -0.7282(-0.0892) | Adj: +1.86%, Vol: 1.00, Thr: 0.1092 | PnL: 1.92 | TotPnL: 1.92
|
||||
1766986500326, 2025-12-29 06:35:00,326 - [IDLE] ETH | Px: 3035.45 | M: 3030.3 | B: 3042.1 / S: 3018.6 | delta: -0.1770(-0.0116) | Adj: -3.37%, Vol: 1.00, Thr: 0.0266 | PnL: -9.24 | TotPnL: -9.24
|
||||
1766986500329, 2025-12-29 06:35:00,329 - [IDLE] BNB | Px: 865.90 | M: 863.5 | B: 866.0 / S: 861.0 | delta: -0.7452(-0.1062) | Adj: +2.05%, Vol: 1.00, Thr: 0.1118 | PnL: 2.00 | TotPnL: 2.00
|
||||
1766986530765, 2025-12-29 06:35:30,765 - [IDLE] ETH | Px: 3035.65 | M: 3030.7 | B: 3042.4 / S: 3019.0 | delta: -0.1766(-0.0112) | Adj: -3.38%, Vol: 1.00, Thr: 0.0265 | PnL: -9.31 | TotPnL: -9.31
|
||||
1766986530768, 2025-12-29 06:35:30,768 - [IDLE] BNB | Px: 866.04 | M: 863.8 | B: 866.3 / S: 861.4 | delta: -0.7379(-0.0989) | Adj: +1.97%, Vol: 1.00, Thr: 0.1107 | PnL: 1.93 | TotPnL: 1.93
|
||||
1766986560795, 2025-12-29 06:36:00,795 - [IDLE] ETH | Px: 3036.05 | M: 3031.5 | B: 3043.2 / S: 3019.9 | delta: -0.1756(-0.0102) | Adj: -3.40%, Vol: 1.00, Thr: 0.0263 | PnL: -9.39 | TotPnL: -9.39
|
||||
1766986560798, 2025-12-29 06:36:00,798 - [IDLE] BNB | Px: 866.36 | M: 864.5 | B: 866.9 / S: 862.1 | delta: -0.7222(-0.0832) | Adj: +1.79%, Vol: 1.00, Thr: 0.1083 | PnL: 1.74 | TotPnL: 1.74
|
||||
1766986710529, 2025-12-29 06:38:30,529 - [IDLE] ETH | Px: 3039.45 | M: 3038.3 | B: 3049.5 / S: 3027.2 | delta: -0.1679(-0.0025) | Adj: -3.58%, Vol: 1.52, Thr: 0.0252 | PnL: -9.95 | TotPnL: -9.95
|
||||
1766986710532, 2025-12-29 06:38:30,532 - [IDLE] BNB | Px: 866.78 | M: 865.4 | B: 867.7 / S: 863.0 | delta: -0.7014(-0.0624) | Adj: +1.54%, Vol: 1.00, Thr: 0.1052 | PnL: 1.46 | TotPnL: 1.46
|
||||
1766986740525, 2025-12-29 06:39:00,525 - [IDLE] ETH | Px: 3038.75 | M: 3036.9 | B: 3048.2 / S: 3025.7 | delta: -0.1695(-0.0041) | Adj: -3.54%, Vol: 1.55, Thr: 0.0254 | PnL: -9.82 | TotPnL: -9.82
|
||||
1766986740528, 2025-12-29 06:39:00,528 - [IDLE] BNB | Px: 866.32 | M: 864.4 | B: 866.9 / S: 862.0 | delta: -0.7237(-0.0847) | Adj: +1.80%, Vol: 1.00, Thr: 0.1086 | PnL: 1.74 | TotPnL: 1.74
|
||||
1766986770504, 2025-12-29 06:39:30,504 - [IDLE] ETH | Px: 3038.95 | M: 3037.3 | B: 3048.6 / S: 3026.1 | delta: -0.1691(-0.0037) | Adj: -3.55%, Vol: 1.57, Thr: 0.0254 | PnL: -9.85 | TotPnL: -9.85
|
||||
1766986770507, 2025-12-29 06:39:30,507 - [IDLE] BNB | Px: 866.23 | M: 864.2 | B: 866.7 / S: 861.8 | delta: -0.7284(-0.0894) | Adj: +1.86%, Vol: 1.00, Thr: 0.1093 | PnL: 1.83 | TotPnL: 1.83
|
||||
1766986800351, 2025-12-29 06:40:00,351 - [IDLE] ETH | Px: 3038.35 | M: 3036.1 | B: 3047.5 / S: 3024.8 | delta: -0.1704(-0.0050) | Adj: -3.52%, Vol: 1.57, Thr: 0.0256 | PnL: -9.74 | TotPnL: -9.74
|
||||
1766986800353, 2025-12-29 06:40:00,353 - [IDLE] BNB | Px: 865.98 | M: 863.7 | B: 866.2 / S: 861.2 | delta: -0.7409(-0.1019) | Adj: +2.00%, Vol: 1.00, Thr: 0.1111 | PnL: 1.93 | TotPnL: 1.93
|
||||
1766986830846, 2025-12-29 06:40:30,846 - [IDLE] ETH | Px: 3039.25 | M: 3037.9 | B: 3049.1 / S: 3026.7 | delta: -0.1684(-0.0030) | Adj: -3.57%, Vol: 1.56, Thr: 0.0253 | PnL: -9.89 | TotPnL: -9.89
|
||||
1766986830848, 2025-12-29 06:40:30,848 - [IDLE] BNB | Px: 866.15 | M: 864.1 | B: 866.5 / S: 861.6 | delta: -0.7324(-0.0934) | Adj: +1.90%, Vol: 1.00, Thr: 0.1099 | PnL: 1.84 | TotPnL: 1.84
|
||||
1766986920410, 2025-12-29 06:42:00,410 - [IDLE] ETH | Px: 3040.15 | M: 3039.7 | B: 3050.8 / S: 3028.7 | delta: -0.1663(-0.0009) | Adj: -3.62%, Vol: 1.48, Thr: 0.0250 | PnL: -10.07 | TotPnL: -10.07
|
||||
1766986920412, 2025-12-29 06:42:00,412 - [IDLE] BNB | Px: 866.36 | M: 864.5 | B: 866.9 / S: 862.1 | delta: -0.7222(-0.0832) | Adj: +1.79%, Vol: 1.00, Thr: 0.1083 | PnL: 1.75 | TotPnL: 1.75
|
||||
1766986980523, 2025-12-29 06:43:00,523 - [IDLE] ETH | Px: 3041.55 | M: 3042.5 | B: 3053.4 / S: 3031.7 | delta: -0.1632(+0.0022) | Adj: -3.69%, Vol: 1.33, Thr: 0.0245 | PnL: -10.30 | TotPnL: -10.30
|
||||
1766986980526, 2025-12-29 06:43:00,526 - [IDLE] BNB | Px: 866.46 | M: 864.7 | B: 867.1 / S: 862.3 | delta: -0.7170(-0.0780) | Adj: +1.72%, Vol: 1.00, Thr: 0.1075 | PnL: 1.68 | TotPnL: 1.68
|
||||
1766987070017, 2025-12-29 06:44:30,017 - [IDLE] ETH | Px: 3043.15 | M: 3045.7 | B: 3056.4 / S: 3035.1 | delta: -0.1596(+0.0058) | Adj: -3.77%, Vol: 1.27, Thr: 0.0239 | PnL: -10.53 | TotPnL: -10.53
|
||||
1766987070020, 2025-12-29 06:44:30,020 - [IDLE] BNB | Px: 866.82 | M: 865.5 | B: 867.8 / S: 863.1 | delta: -0.6989(-0.0599) | Adj: +1.51%, Vol: 1.00, Thr: 0.1048 | PnL: 1.49 | TotPnL: 1.49
|
||||
1766987100862, 2025-12-29 06:45:00,862 - [IDLE] ETH | Px: 3043.05 | M: 3045.5 | B: 3056.2 / S: 3034.9 | delta: -0.1598(+0.0056) | Adj: -3.77%, Vol: 1.20, Thr: 0.0240 | PnL: -10.55 | TotPnL: -10.55
|
||||
1766987100864, 2025-12-29 06:45:00,864 - [IDLE] BNB | Px: 866.80 | M: 865.4 | B: 867.8 / S: 863.1 | delta: -0.7004(-0.0614) | Adj: +1.53%, Vol: 1.00, Thr: 0.1051 | PnL: 1.44 | TotPnL: 1.44
|
||||
1766987190441, 2025-12-29 06:46:30,441 - [IDLE] ETH | Px: 3042.85 | M: 3045.1 | B: 3055.8 / S: 3034.5 | delta: -0.1602(+0.0052) | Adj: -3.75%, Vol: 1.19, Thr: 0.0240 | PnL: -10.63 | TotPnL: -10.63
|
||||
1766987190444, 2025-12-29 06:46:30,444 - [IDLE] BNB | Px: 866.29 | M: 864.4 | B: 866.8 / S: 861.9 | delta: -0.7254(-0.0864) | Adj: +1.82%, Vol: 1.00, Thr: 0.1088 | PnL: 1.48 | TotPnL: 1.48
|
||||
1766987229532, 2025-12-29 06:47:09,532 - [TRIG] Net BNB: SELL 0.1227 | Tgt: -0.7617 / Cur: -0.6390 | Thresh: 0.1143
|
||||
1766987229533, 2025-12-29 06:47:09,533 - [ORDER] ALO BNB SELL 0.122 @ 865.56
|
||||
1766987231641, 2025-12-29 06:47:11,641 - Sleeping 5s to allow position update...
|
||||
1766987240303, 2025-12-29 06:47:20,303 - [WAIT] BNB Pending SELL Order 281227381064 @ 865.56 (Dist: 0.004%)
|
||||
1766987250145, 2025-12-29 06:47:30,145 - [IDLE] ETH | Px: 3041.75 | M: 3042.9 | B: 3053.8 / S: 3032.1 | delta: -0.1627(+0.0027) | Adj: -3.70%, Vol: 1.22, Thr: 0.0244 | PnL: -10.33 | TotPnL: -10.33
|
||||
1766987250147, 2025-12-29 06:47:30,147 - [WAIT] BNB Pending SELL Order 281227381064 @ 865.56 (Dist: 0.064%)
|
||||
1766987270926, 2025-12-29 06:47:50,926 - [WAIT] BNB Pending SELL Order 281227381064 @ 865.56 (Dist: 0.064%)
|
||||
1766987280432, 2025-12-29 06:48:00,432 - [IDLE] ETH | Px: 3040.45 | M: 3040.3 | B: 3051.4 / S: 3029.3 | delta: -0.1657(-0.0003) | Adj: -3.63%, Vol: 1.21, Thr: 0.0248 | PnL: -10.09 | TotPnL: -10.09
|
||||
1766987280435, 2025-12-29 06:48:00,435 - [WAIT] BNB Pending SELL Order 281227381064 @ 865.56 (Dist: 0.137%)
|
||||
1766987310562, 2025-12-29 06:48:30,562 - [IDLE] ETH | Px: 3039.95 | M: 3039.3 | B: 3050.4 / S: 3028.2 | delta: -0.1668(-0.0014) | Adj: -3.61%, Vol: 1.18, Thr: 0.0250 | PnL: -10.02 | TotPnL: -10.02
|
||||
1766987310564, 2025-12-29 06:48:30,564 - [WAIT] BNB Pending SELL Order 281227381064 @ 865.56 (Dist: 0.128%)
|
||||
1766987320120, 2025-12-29 06:48:40,120 - [WAIT] BNB Pending SELL Order 281227381064 @ 865.56 (Dist: 0.096%)
|
||||
1766987327807, 2025-12-29 06:48:47,807 - Cancelling stale order 281227381064 (A @ 865.56)
|
||||
1766987328681, 2025-12-29 06:48:48,681 - [TRIG] Net BNB: SELL 0.2005 | Tgt: -0.8395 / Cur: -0.6390 | Thresh: 0.1259
|
||||
1766987328682, 2025-12-29 06:48:48,682 - [ORDER] ALO BNB SELL 0.2 @ 864.0
|
||||
1766987329515, 2025-12-29 06:48:49,515 - Sleeping 5s to allow position update...
|
||||
1766987340194, 2025-12-29 06:49:00,194 - [IDLE] ETH | Px: 3039.55 | M: 3038.5 | B: 3049.7 / S: 3027.4 | delta: -0.1677(-0.0023) | Adj: -3.58%, Vol: 1.15, Thr: 0.0252 | PnL: -10.00 | TotPnL: -10.00
|
||||
1766987340196, 2025-12-29 06:49:00,196 - [WAIT] BNB Pending SELL Order 281228204718 @ 864.0 (Dist: 0.001%)
|
||||
1766987370735, 2025-12-29 06:49:30,735 - [IDLE] ETH | Px: 3040.75 | M: 3040.9 | B: 3051.9 / S: 3030.0 | delta: -0.1650(+0.0004) | Adj: -3.65%, Vol: 1.12, Thr: 0.0247 | PnL: -10.09 | TotPnL: -10.09
|
||||
1766987370737, 2025-12-29 06:49:30,737 - [WAIT] BNB Pending SELL Order 281228204718 @ 864.0 (Dist: 0.025%)
|
||||
1766987380456, 2025-12-29 06:49:40,456 - [WAIT] BNB Pending SELL Order 281228204718 @ 864.0 (Dist: 0.018%)
|
||||
1766987390360, 2025-12-29 06:49:50,360 - [WAIT] BNB Pending SELL Order 281228204718 @ 864.0 (Dist: 0.009%)
|
||||
1766987400223, 2025-12-29 06:50:00,223 - [IDLE] ETH | Px: 3041.35 | M: 3042.1 | B: 3053.0 / S: 3031.2 | delta: -0.1636(+0.0018) | Adj: -3.68%, Vol: 1.05, Thr: 0.0245 | PnL: -10.27 | TotPnL: -10.27
|
||||
1766987400225, 2025-12-29 06:50:00,225 - [WAIT] BNB Pending SELL Order 281228204718 @ 864.0 (Dist: 0.011%)
|
||||
1766987415377, 2025-12-29 06:50:15,377 - Cancelling idle order 281228204718 (A @ 864.0)
|
||||
1766987460331, 2025-12-29 06:51:00,331 - [IDLE] ETH | Px: 3039.85 | M: 3039.1 | B: 3050.2 / S: 3028.0 | delta: -0.1670(-0.0016) | Adj: -3.60%, Vol: 1.00, Thr: 0.0251 | PnL: -10.04 | TotPnL: -10.04
|
||||
1766987460334, 2025-12-29 06:51:00,334 - [IDLE] BNB | Px: 863.92 | M: 862.8 | B: 865.6 / S: 860.0 | delta: -0.8448(-0.0518) | Adj: +3.20%, Vol: 2.62, Thr: 0.1267 | PnL: 3.21 | TotPnL: 3.21
|
||||
1766987640865, 2025-12-29 06:54:00,865 - [IDLE] ETH | Px: 3036.65 | M: 3032.7 | B: 3044.3 / S: 3021.1 | delta: -0.1743(-0.0089) | Adj: -3.44%, Vol: 1.46, Thr: 0.0261 | PnL: -9.47 | TotPnL: -9.47
|
||||
1766987640868, 2025-12-29 06:54:00,868 - [IDLE] BNB | Px: 864.01 | M: 863.0 | B: 865.8 / S: 860.2 | delta: -0.8405(-0.0475) | Adj: +3.15%, Vol: 2.85, Thr: 0.1261 | PnL: 3.25 | TotPnL: 3.25
|
||||
1766987760209, 2025-12-29 06:56:00,209 - [IDLE] ETH | Px: 3038.05 | M: 3035.5 | B: 3046.9 / S: 3024.2 | delta: -0.1711(-0.0057) | Adj: -3.51%, Vol: 1.47, Thr: 0.0257 | PnL: -9.70 | TotPnL: -9.70
|
||||
1766987760212, 2025-12-29 06:56:00,212 - [IDLE] BNB | Px: 864.32 | M: 863.6 | B: 866.4 / S: 860.9 | delta: -0.8249(-0.0319) | Adj: +2.97%, Vol: 1.83, Thr: 0.1237 | PnL: 2.99 | TotPnL: 2.99
|
||||
1766987790271, 2025-12-29 06:56:30,271 - [IDLE] ETH | Px: 3038.05 | M: 3035.5 | B: 3046.9 / S: 3024.2 | delta: -0.1711(-0.0057) | Adj: -3.51%, Vol: 1.34, Thr: 0.0257 | PnL: -9.72 | TotPnL: -9.72
|
||||
1766987790274, 2025-12-29 06:56:30,274 - [IDLE] BNB | Px: 864.35 | M: 863.7 | B: 866.4 / S: 860.9 | delta: -0.8231(-0.0301) | Adj: +2.95%, Vol: 1.34, Thr: 0.1235 | PnL: 2.96 | TotPnL: 2.96
|
||||
1766987820384, 2025-12-29 06:57:00,384 - [IDLE] ETH | Px: 3037.55 | M: 3034.5 | B: 3046.0 / S: 3023.1 | delta: -0.1722(-0.0068) | Adj: -3.48%, Vol: 1.22, Thr: 0.0258 | PnL: -9.66 | TotPnL: -9.66
|
||||
1766987820386, 2025-12-29 06:57:00,386 - [IDLE] BNB | Px: 864.30 | M: 863.6 | B: 866.3 / S: 860.8 | delta: -0.8257(-0.0327) | Adj: +2.98%, Vol: 1.00, Thr: 0.1239 | PnL: 2.96 | TotPnL: 2.96
|
||||
1766987970057, 2025-12-29 06:59:30,057 - [IDLE] ETH | Px: 3036.95 | M: 3033.3 | B: 3044.9 / S: 3021.8 | delta: -0.1736(-0.0082) | Adj: -3.45%, Vol: 1.00, Thr: 0.0260 | PnL: -9.51 | TotPnL: -9.51
|
||||
1766987970060, 2025-12-29 06:59:30,060 - [IDLE] BNB | Px: 864.11 | M: 863.2 | B: 866.0 / S: 860.4 | delta: -0.8354(-0.0424) | Adj: +3.09%, Vol: 1.00, Thr: 0.1253 | PnL: 3.13 | TotPnL: 3.13
|
||||
1766988030397, 2025-12-29 07:00:30,397 - [IDLE] ETH | Px: 3036.35 | M: 3032.1 | B: 3043.7 / S: 3020.5 | delta: -0.1750(-0.0096) | Adj: -3.42%, Vol: 1.00, Thr: 0.0262 | PnL: -9.47 | TotPnL: -9.47
|
||||
1766988030400, 2025-12-29 07:00:30,400 - [IDLE] BNB | Px: 864.56 | M: 864.1 | B: 866.8 / S: 861.4 | delta: -0.8122(-0.0192) | Adj: +2.83%, Vol: 1.00, Thr: 0.1218 | PnL: 2.80 | TotPnL: 2.80
|
||||
1766988060802, 2025-12-29 07:01:00,802 - [IDLE] ETH | Px: 3036.25 | M: 3031.9 | B: 3043.5 / S: 3020.3 | delta: -0.1752(-0.0098) | Adj: -3.41%, Vol: 1.00, Thr: 0.0263 | PnL: -9.41 | TotPnL: -9.41
|
||||
1766988060805, 2025-12-29 07:01:00,805 - [IDLE] BNB | Px: 864.54 | M: 864.1 | B: 866.8 / S: 861.4 | delta: -0.8137(-0.0207) | Adj: +2.85%, Vol: 1.00, Thr: 0.1221 | PnL: 2.81 | TotPnL: 2.81
|
||||
1766988090913, 2025-12-29 07:01:30,913 - [IDLE] ETH | Px: 3032.75 | M: 3024.9 | B: 3037.0 / S: 3012.8 | delta: -0.1832(-0.0178) | Adj: -3.23%, Vol: 1.00, Thr: 0.0275 | PnL: -8.84 | TotPnL: -8.84
|
||||
1766988090916, 2025-12-29 07:01:30,916 - [IDLE] BNB | Px: 864.08 | M: 863.1 | B: 865.9 / S: 860.3 | delta: -0.8369(-0.0439) | Adj: +3.11%, Vol: 1.00, Thr: 0.1255 | PnL: 3.17 | TotPnL: 3.17
|
||||
1766988120633, 2025-12-29 07:02:00,633 - [IDLE] ETH | Px: 3033.65 | M: 3026.7 | B: 3038.7 / S: 3014.7 | delta: -0.1811(-0.0157) | Adj: -3.28%, Vol: 1.00, Thr: 0.0272 | PnL: -8.99 | TotPnL: -8.99
|
||||
1766988120636, 2025-12-29 07:02:00,636 - [IDLE] BNB | Px: 864.07 | M: 863.1 | B: 865.9 / S: 860.3 | delta: -0.8374(-0.0444) | Adj: +3.12%, Vol: 1.00, Thr: 0.1256 | PnL: 3.19 | TotPnL: 3.19
|
||||
1766988180920, 2025-12-29 07:03:00,920 - [IDLE] ETH | Px: 3033.05 | M: 3025.5 | B: 3037.6 / S: 3013.4 | delta: -0.1825(-0.0171) | Adj: -3.25%, Vol: 1.04, Thr: 0.0274 | PnL: -8.89 | TotPnL: -8.89
|
||||
1766988180923, 2025-12-29 07:03:00,923 - [IDLE] BNB | Px: 864.32 | M: 863.6 | B: 866.4 / S: 860.9 | delta: -0.8249(-0.0319) | Adj: +2.97%, Vol: 1.00, Thr: 0.1237 | PnL: 2.98 | TotPnL: 2.98
|
||||
1766988210031, 2025-12-29 07:03:30,031 - [IDLE] ETH | Px: 3033.35 | M: 3026.1 | B: 3038.2 / S: 3014.0 | delta: -0.1818(-0.0164) | Adj: -3.27%, Vol: 1.11, Thr: 0.0273 | PnL: -8.94 | TotPnL: -8.94
|
||||
1766988210034, 2025-12-29 07:03:30,034 - [IDLE] BNB | Px: 864.46 | M: 863.9 | B: 866.7 / S: 861.2 | delta: -0.8173(-0.0243) | Adj: +2.89%, Vol: 1.00, Thr: 0.1226 | PnL: 2.88 | TotPnL: 2.88
|
||||
1766988240616, 2025-12-29 07:04:00,616 - [IDLE] ETH | Px: 3033.75 | M: 3026.9 | B: 3038.9 / S: 3014.9 | delta: -0.1809(-0.0155) | Adj: -3.29%, Vol: 1.18, Thr: 0.0271 | PnL: -9.03 | TotPnL: -9.03
|
||||
1766988240619, 2025-12-29 07:04:00,619 - [IDLE] BNB | Px: 864.66 | M: 864.4 | B: 867.0 / S: 861.7 | delta: -0.8071(-0.0141) | Adj: +2.77%, Vol: 1.00, Thr: 0.1211 | PnL: 2.73 | TotPnL: 2.73
|
||||
1766988270677, 2025-12-29 07:04:30,677 - [IDLE] ETH | Px: 3032.55 | M: 3024.5 | B: 3036.7 / S: 3012.3 | delta: -0.1836(-0.0182) | Adj: -3.22%, Vol: 1.22, Thr: 0.0275 | PnL: -8.93 | TotPnL: -8.93
|
||||
1766988270680, 2025-12-29 07:04:30,680 - [IDLE] BNB | Px: 864.66 | M: 864.3 | B: 867.0 / S: 861.6 | delta: -0.8076(-0.0146) | Adj: +2.78%, Vol: 1.00, Thr: 0.1211 | PnL: 2.65 | TotPnL: 2.65
|
||||
1766988300436, 2025-12-29 07:05:00,436 - [IDLE] ETH | Px: 3032.05 | M: 3023.5 | B: 3035.7 / S: 3011.2 | delta: -0.1848(-0.0194) | Adj: -3.20%, Vol: 1.31, Thr: 0.0277 | PnL: -8.78 | TotPnL: -8.78
|
||||
1766988300438, 2025-12-29 07:05:00,438 - [IDLE] BNB | Px: 864.50 | M: 864.0 | B: 866.7 / S: 861.3 | delta: -0.8153(-0.0223) | Adj: +2.86%, Vol: 1.00, Thr: 0.1223 | PnL: 2.82 | TotPnL: 2.82
|
||||
1766988330030, 2025-12-29 07:05:30,030 - [IDLE] ETH | Px: 3033.95 | M: 3027.3 | B: 3039.3 / S: 3015.3 | delta: -0.1804(-0.0150) | Adj: -3.30%, Vol: 1.33, Thr: 0.0271 | PnL: -9.06 | TotPnL: -9.06
|
||||
1766988330032, 2025-12-29 07:05:30,032 - [IDLE] BNB | Px: 864.92 | M: 864.9 | B: 867.6 / S: 862.3 | delta: -0.7939(-0.0009) | Adj: +2.62%, Vol: 1.00, Thr: 0.1191 | PnL: 2.56 | TotPnL: 2.56
|
||||
1766988360157, 2025-12-29 07:06:00,157 - [IDLE] ETH | Px: 3033.75 | M: 3026.9 | B: 3038.9 / S: 3014.9 | delta: -0.1809(-0.0155) | Adj: -3.29%, Vol: 1.29, Thr: 0.0271 | PnL: -9.01 | TotPnL: -9.01
|
||||
1766988360160, 2025-12-29 07:06:00,160 - [IDLE] BNB | Px: 864.64 | M: 864.3 | B: 867.0 / S: 861.6 | delta: -0.8081(-0.0151) | Adj: +2.78%, Vol: 1.00, Thr: 0.1212 | PnL: 2.66 | TotPnL: 2.66
|
||||
1766988480694, 2025-12-29 07:08:00,694 - [IDLE] ETH | Px: 3037.35 | M: 3034.1 | B: 3045.6 / S: 3022.6 | delta: -0.1727(-0.0073) | Adj: -3.47%, Vol: 1.10, Thr: 0.0259 | PnL: -9.61 | TotPnL: -9.61
|
||||
1766988480697, 2025-12-29 07:08:00,697 - [IDLE] BNB | Px: 864.88 | M: 864.8 | B: 867.5 / S: 862.1 | delta: -0.7962(-0.0032) | Adj: +2.64%, Vol: 1.00, Thr: 0.1194 | PnL: 2.58 | TotPnL: 2.58
|
||||
1766988540794, 2025-12-29 07:09:00,794 - [IDLE] ETH | Px: 3036.35 | M: 3032.1 | B: 3043.7 / S: 3020.5 | delta: -0.1750(-0.0096) | Adj: -3.42%, Vol: 1.00, Thr: 0.0262 | PnL: -9.44 | TotPnL: -9.44
|
||||
1766988540797, 2025-12-29 07:09:00,797 - [IDLE] BNB | Px: 864.58 | M: 864.2 | B: 866.9 / S: 861.4 | delta: -0.8117(-0.0187) | Adj: +2.82%, Vol: 1.00, Thr: 0.1218 | PnL: 2.79 | TotPnL: 2.79
|
||||
1766988570132, 2025-12-29 07:09:30,132 - [IDLE] ETH | Px: 3036.35 | M: 3032.1 | B: 3043.7 / S: 3020.5 | delta: -0.1750(-0.0096) | Adj: -3.42%, Vol: 1.00, Thr: 0.0262 | PnL: -9.46 | TotPnL: -9.46
|
||||
1766988570135, 2025-12-29 07:09:30,135 - [IDLE] BNB | Px: 864.52 | M: 864.0 | B: 866.8 / S: 861.3 | delta: -0.8145(-0.0215) | Adj: +2.85%, Vol: 1.00, Thr: 0.1222 | PnL: 2.82 | TotPnL: 2.82
|
||||
1766988660076, 2025-12-29 07:11:00,076 - [IDLE] ETH | Px: 3039.35 | M: 3038.1 | B: 3049.3 / S: 3026.9 | delta: -0.1682(-0.0028) | Adj: -3.57%, Vol: 1.18, Thr: 0.0252 | PnL: -9.94 | TotPnL: -9.94
|
||||
1766988660078, 2025-12-29 07:11:00,078 - [IDLE] BNB | Px: 864.88 | M: 864.8 | B: 867.5 / S: 862.1 | delta: -0.7965(-0.0035) | Adj: +2.65%, Vol: 1.00, Thr: 0.1195 | PnL: 2.59 | TotPnL: 2.59
|
||||
1766988690660, 2025-12-29 07:11:30,660 - [IDLE] ETH | Px: 3039.35 | M: 3038.1 | B: 3049.3 / S: 3026.9 | delta: -0.1682(-0.0028) | Adj: -3.57%, Vol: 1.33, Thr: 0.0252 | PnL: -9.95 | TotPnL: -9.95
|
||||
1766988690662, 2025-12-29 07:11:30,662 - [IDLE] BNB | Px: 865.02 | M: 865.1 | B: 867.7 / S: 862.5 | delta: -0.7894(+0.0036) | Adj: +2.57%, Vol: 1.00, Thr: 0.1184 | PnL: 2.48 | TotPnL: 2.48
|
||||
1766988810922, 2025-12-29 07:13:30,922 - [IDLE] ETH | Px: 3039.95 | M: 3039.3 | B: 3050.4 / S: 3028.2 | delta: -0.1668(-0.0014) | Adj: -3.61%, Vol: 1.54, Thr: 0.0250 | PnL: -10.05 | TotPnL: -10.05
|
||||
1766988810924, 2025-12-29 07:13:30,924 - [IDLE] BNB | Px: 864.98 | M: 865.0 | B: 867.7 / S: 862.4 | delta: -0.7912(+0.0018) | Adj: +2.59%, Vol: 1.00, Thr: 0.1187 | PnL: 2.49 | TotPnL: 2.49
|
||||
1766988840774, 2025-12-29 07:14:00,774 - [IDLE] ETH | Px: 3038.85 | M: 3037.1 | B: 3048.4 / S: 3025.9 | delta: -0.1693(-0.0039) | Adj: -3.55%, Vol: 1.54, Thr: 0.0254 | PnL: -9.85 | TotPnL: -9.85
|
||||
1766988840777, 2025-12-29 07:14:00,777 - [IDLE] BNB | Px: 864.96 | M: 865.0 | B: 867.6 / S: 862.3 | delta: -0.7924(+0.0006) | Adj: +2.60%, Vol: 1.00, Thr: 0.1189 | PnL: 2.51 | TotPnL: 2.51
|
||||
1766988960307, 2025-12-29 07:16:00,307 - [IDLE] ETH | Px: 3039.95 | M: 3039.3 | B: 3050.4 / S: 3028.2 | delta: -0.1668(-0.0014) | Adj: -3.61%, Vol: 1.06, Thr: 0.0250 | PnL: -10.05 | TotPnL: -10.05
|
||||
1766988960310, 2025-12-29 07:16:00,310 - [IDLE] BNB | Px: 864.76 | M: 864.5 | B: 867.2 / S: 861.9 | delta: -0.8026(-0.0096) | Adj: +2.72%, Vol: 1.00, Thr: 0.1204 | PnL: 2.66 | TotPnL: 2.66
|
||||
1766988990637, 2025-12-29 07:16:30,637 - [IDLE] ETH | Px: 3041.85 | M: 3043.1 | B: 3054.0 / S: 3032.3 | delta: -0.1625(+0.0029) | Adj: -3.70%, Vol: 1.00, Thr: 0.0244 | PnL: -10.37 | TotPnL: -10.37
|
||||
1766988990640, 2025-12-29 07:16:30,640 - [IDLE] BNB | Px: 865.10 | M: 865.3 | B: 867.9 / S: 862.7 | delta: -0.7849(+0.0081) | Adj: +2.51%, Vol: 1.00, Thr: 0.1177 | PnL: 2.37 | TotPnL: 2.37
|
||||
1766989020851, 2025-12-29 07:17:00,851 - [IDLE] ETH | Px: 3041.25 | M: 3041.9 | B: 3052.8 / S: 3031.0 | delta: -0.1639(+0.0015) | Adj: -3.67%, Vol: 1.00, Thr: 0.0246 | PnL: -10.25 | TotPnL: -10.25
|
||||
1766989020853, 2025-12-29 07:17:00,853 - [IDLE] BNB | Px: 865.10 | M: 865.3 | B: 867.9 / S: 862.6 | delta: -0.7854(+0.0076) | Adj: +2.52%, Vol: 1.00, Thr: 0.1178 | PnL: 2.35 | TotPnL: 2.35
|
||||
1766989200023, 2025-12-29 07:20:00,023 - [IDLE] ETH | Px: 3042.85 | M: 3045.1 | B: 3055.8 / S: 3034.5 | delta: -0.1602(+0.0052) | Adj: -3.75%, Vol: 1.00, Thr: 0.0240 | PnL: -10.53 | TotPnL: -10.53
|
||||
1766989200026, 2025-12-29 07:20:00,026 - [IDLE] BNB | Px: 865.15 | M: 865.4 | B: 868.0 / S: 862.8 | delta: -0.7826(+0.0104) | Adj: +2.49%, Vol: 1.00, Thr: 0.1174 | PnL: 2.33 | TotPnL: 2.33
|
||||
1766989230644, 2025-12-29 07:20:30,644 - [IDLE] ETH | Px: 3042.85 | M: 3045.1 | B: 3055.8 / S: 3034.5 | delta: -0.1602(+0.0052) | Adj: -3.75%, Vol: 1.00, Thr: 0.0240 | PnL: -10.52 | TotPnL: -10.52
|
||||
1766989230647, 2025-12-29 07:20:30,647 - [IDLE] BNB | Px: 865.26 | M: 865.6 | B: 868.2 / S: 863.0 | delta: -0.7768(+0.0162) | Adj: +2.42%, Vol: 1.00, Thr: 0.1165 | PnL: 2.28 | TotPnL: 2.28
|
||||
1766989320150, 2025-12-29 07:22:00,150 - [IDLE] ETH | Px: 3042.35 | M: 3044.1 | B: 3054.9 / S: 3033.4 | delta: -0.1614(+0.0040) | Adj: -3.73%, Vol: 1.00, Thr: 0.0242 | PnL: -10.45 | TotPnL: -10.45
|
||||
1766989320152, 2025-12-29 07:22:00,152 - [IDLE] BNB | Px: 865.28 | M: 865.7 | B: 868.3 / S: 863.1 | delta: -0.7758(+0.0172) | Adj: +2.41%, Vol: 1.00, Thr: 0.1164 | PnL: 2.23 | TotPnL: 2.23
|
||||
1766989350916, 2025-12-29 07:22:30,916 - [IDLE] ETH | Px: 3042.95 | M: 3045.3 | B: 3056.0 / S: 3034.7 | delta: -0.1600(+0.0054) | Adj: -3.76%, Vol: 1.00, Thr: 0.0240 | PnL: -10.55 | TotPnL: -10.55
|
||||
1766989350918, 2025-12-29 07:22:30,918 - [IDLE] BNB | Px: 865.30 | M: 865.7 | B: 868.3 / S: 863.1 | delta: -0.7753(+0.0177) | Adj: +2.40%, Vol: 1.00, Thr: 0.1163 | PnL: 2.21 | TotPnL: 2.21
|
||||
1766989410383, 2025-12-29 07:23:30,383 - [IDLE] ETH | Px: 3042.55 | M: 3044.5 | B: 3055.3 / S: 3033.8 | delta: -0.1609(+0.0045) | Adj: -3.74%, Vol: 1.00, Thr: 0.0241 | PnL: -10.48 | TotPnL: -10.48
|
||||
1766989410385, 2025-12-29 07:23:30,385 - [IDLE] BNB | Px: 864.89 | M: 864.8 | B: 867.5 / S: 862.2 | delta: -0.7957(-0.0027) | Adj: +2.64%, Vol: 1.00, Thr: 0.1194 | PnL: 2.46 | TotPnL: 2.46
|
||||
1766989500382, 2025-12-29 07:25:00,382 - [IDLE] ETH | Px: 3040.55 | M: 3040.5 | B: 3051.5 / S: 3029.5 | delta: -0.1654(-0.0000) | Adj: -3.64%, Vol: 1.00, Thr: 0.0248 | PnL: -10.12 | TotPnL: -10.12
|
||||
1766989500384, 2025-12-29 07:25:00,384 - [IDLE] BNB | Px: 864.82 | M: 864.7 | B: 867.3 / S: 862.0 | delta: -0.7993(-0.0063) | Adj: +2.68%, Vol: 1.00, Thr: 0.1199 | PnL: 2.60 | TotPnL: 2.60
|
||||
1766989530657, 2025-12-29 07:25:30,657 - [IDLE] ETH | Px: 3040.55 | M: 3040.5 | B: 3051.5 / S: 3029.5 | delta: -0.1654(-0.0000) | Adj: -3.64%, Vol: 1.00, Thr: 0.0248 | PnL: -10.13 | TotPnL: -10.13
|
||||
1766989530660, 2025-12-29 07:25:30,660 - [IDLE] BNB | Px: 864.78 | M: 864.6 | B: 867.3 / S: 861.9 | delta: -0.8010(-0.0080) | Adj: +2.70%, Vol: 1.00, Thr: 0.1202 | PnL: 2.62 | TotPnL: 2.62
|
||||
1766989650215, 2025-12-29 07:27:30,215 - [IDLE] ETH | Px: 3037.05 | M: 3033.5 | B: 3045.0 / S: 3022.0 | delta: -0.1734(-0.0080) | Adj: -3.46%, Vol: 1.00, Thr: 0.0260 | PnL: -9.51 | TotPnL: -9.51
|
||||
1766989650217, 2025-12-29 07:27:30,217 - [IDLE] BNB | Px: 864.08 | M: 863.1 | B: 865.9 / S: 860.3 | delta: -0.8369(-0.0439) | Adj: +3.11%, Vol: 1.00, Thr: 0.1255 | PnL: 3.12 | TotPnL: 3.12
|
||||
1766989680284, 2025-12-29 07:28:00,284 - [IDLE] ETH | Px: 3036.75 | M: 3032.9 | B: 3044.5 / S: 3021.4 | delta: -0.1741(-0.0087) | Adj: -3.44%, Vol: 1.18, Thr: 0.0261 | PnL: -9.52 | TotPnL: -9.52
|
||||
1766989680286, 2025-12-29 07:28:00,286 - [IDLE] BNB | Px: 864.08 | M: 863.1 | B: 865.9 / S: 860.3 | delta: -0.8369(-0.0439) | Adj: +3.11%, Vol: 1.00, Thr: 0.1255 | PnL: 3.16 | TotPnL: 3.16
|
||||
1766989710548, 2025-12-29 07:28:30,548 - [IDLE] ETH | Px: 3036.55 | M: 3032.5 | B: 3044.1 / S: 3020.9 | delta: -0.1745(-0.0091) | Adj: -3.43%, Vol: 1.34, Thr: 0.0262 | PnL: -9.44 | TotPnL: -9.44
|
||||
1766989710551, 2025-12-29 07:28:30,551 - [IDLE] BNB | Px: 864.17 | M: 863.3 | B: 866.1 / S: 860.5 | delta: -0.8323(-0.0393) | Adj: +3.06%, Vol: 1.00, Thr: 0.1248 | PnL: 3.12 | TotPnL: 3.12
|
||||
1766989740968, 2025-12-29 07:29:00,968 - [IDLE] ETH | Px: 3036.45 | M: 3032.3 | B: 3043.9 / S: 3020.7 | delta: -0.1747(-0.0093) | Adj: -3.43%, Vol: 1.49, Thr: 0.0262 | PnL: -9.46 | TotPnL: -9.46
|
||||
1766989740971, 2025-12-29 07:29:00,971 - [IDLE] BNB | Px: 864.28 | M: 863.5 | B: 866.3 / S: 860.8 | delta: -0.8270(-0.0340) | Adj: +3.00%, Vol: 1.00, Thr: 0.1240 | PnL: 2.98 | TotPnL: 2.98
|
||||
1766989770670, 2025-12-29 07:29:30,670 - [IDLE] ETH | Px: 3036.15 | M: 3031.7 | B: 3043.4 / S: 3020.1 | delta: -0.1754(-0.0100) | Adj: -3.41%, Vol: 1.58, Thr: 0.0263 | PnL: -9.42 | TotPnL: -9.42
|
||||
1766989770673, 2025-12-29 07:29:30,673 - [IDLE] BNB | Px: 864.18 | M: 863.3 | B: 866.1 / S: 860.5 | delta: -0.8321(-0.0391) | Adj: +3.06%, Vol: 1.00, Thr: 0.1248 | PnL: 3.00 | TotPnL: 3.00
|
||||
1766989800981, 2025-12-29 07:30:00,981 - [IDLE] ETH | Px: 3036.35 | M: 3032.1 | B: 3043.7 / S: 3020.5 | delta: -0.1750(-0.0096) | Adj: -3.42%, Vol: 1.63, Thr: 0.0262 | PnL: -9.44 | TotPnL: -9.44
|
||||
1766989800984, 2025-12-29 07:30:00,984 - [IDLE] BNB | Px: 864.24 | M: 863.5 | B: 866.2 / S: 860.7 | delta: -0.8285(-0.0355) | Adj: +3.02%, Vol: 1.00, Thr: 0.1243 | PnL: 3.01 | TotPnL: 3.01
|
||||
1766989830860, 2025-12-29 07:30:30,860 - [IDLE] ETH | Px: 3036.35 | M: 3032.1 | B: 3043.7 / S: 3020.5 | delta: -0.1750(-0.0096) | Adj: -3.42%, Vol: 1.64, Thr: 0.0262 | PnL: -9.44 | TotPnL: -9.44
|
||||
1766989830863, 2025-12-29 07:30:30,863 - [IDLE] BNB | Px: 864.22 | M: 863.4 | B: 866.2 / S: 860.6 | delta: -0.8300(-0.0370) | Adj: +3.03%, Vol: 1.00, Thr: 0.1245 | PnL: 3.05 | TotPnL: 3.05
|
||||
1766989890645, 2025-12-29 07:31:30,645 - [IDLE] ETH | Px: 3036.05 | M: 3031.5 | B: 3043.2 / S: 3019.9 | delta: -0.1756(-0.0102) | Adj: -3.40%, Vol: 1.63, Thr: 0.0263 | PnL: -9.41 | TotPnL: -9.41
|
||||
1766989890648, 2025-12-29 07:31:30,648 - [IDLE] BNB | Px: 864.30 | M: 863.6 | B: 866.3 / S: 860.8 | delta: -0.8257(-0.0327) | Adj: +2.98%, Vol: 1.00, Thr: 0.1239 | PnL: 3.02 | TotPnL: 3.02
|
||||
1766989920692, 2025-12-29 07:32:00,692 - [IDLE] ETH | Px: 3036.45 | M: 3032.3 | B: 3043.9 / S: 3020.7 | delta: -0.1747(-0.0093) | Adj: -3.43%, Vol: 1.58, Thr: 0.0262 | PnL: -9.46 | TotPnL: -9.46
|
||||
1766989920694, 2025-12-29 07:32:00,694 - [IDLE] BNB | Px: 864.28 | M: 863.5 | B: 866.3 / S: 860.8 | delta: -0.8267(-0.0337) | Adj: +2.99%, Vol: 1.00, Thr: 0.1240 | PnL: 3.02 | TotPnL: 3.02
|
||||
1766989950036, 2025-12-29 07:32:30,036 - [IDLE] ETH | Px: 3037.35 | M: 3034.1 | B: 3045.6 / S: 3022.6 | delta: -0.1727(-0.0073) | Adj: -3.47%, Vol: 1.45, Thr: 0.0259 | PnL: -9.61 | TotPnL: -9.61
|
||||
1766989950038, 2025-12-29 07:32:30,038 - [IDLE] BNB | Px: 864.30 | M: 863.6 | B: 866.3 / S: 860.8 | delta: -0.8259(-0.0329) | Adj: +2.99%, Vol: 1.00, Thr: 0.1239 | PnL: 3.04 | TotPnL: 3.04
|
||||
1766989980867, 2025-12-29 07:33:00,867 - [IDLE] ETH | Px: 3038.15 | M: 3035.7 | B: 3047.1 / S: 3024.4 | delta: -0.1709(-0.0055) | Adj: -3.51%, Vol: 1.30, Thr: 0.0256 | PnL: -9.70 | TotPnL: -9.70
|
||||
1766989980870, 2025-12-29 07:33:00,870 - [IDLE] BNB | Px: 864.54 | M: 864.1 | B: 866.8 / S: 861.4 | delta: -0.8137(-0.0207) | Adj: +2.85%, Vol: 1.00, Thr: 0.1221 | PnL: 2.88 | TotPnL: 2.88
|
||||
1766990010938, 2025-12-29 07:33:30,938 - [IDLE] ETH | Px: 3035.85 | M: 3031.1 | B: 3042.8 / S: 3019.4 | delta: -0.1761(-0.0107) | Adj: -3.39%, Vol: 1.19, Thr: 0.0264 | PnL: -9.37 | TotPnL: -9.37
|
||||
1766990010940, 2025-12-29 07:33:30,940 - [IDLE] BNB | Px: 864.24 | M: 863.4 | B: 866.2 / S: 860.7 | delta: -0.8290(-0.0360) | Adj: +3.02%, Vol: 1.00, Thr: 0.1244 | PnL: 3.03 | TotPnL: 3.03
|
||||
1766990070594, 2025-12-29 07:34:30,594 - [IDLE] ETH | Px: 3033.95 | M: 3027.3 | B: 3039.3 / S: 3015.3 | delta: -0.1804(-0.0150) | Adj: -3.30%, Vol: 1.17, Thr: 0.0271 | PnL: -8.98 | TotPnL: -8.98
|
||||
1766990070597, 2025-12-29 07:34:30,597 - [IDLE] BNB | Px: 864.24 | M: 863.4 | B: 866.2 / S: 860.7 | delta: -0.8290(-0.0360) | Adj: +3.02%, Vol: 1.00, Thr: 0.1244 | PnL: 3.12 | TotPnL: 3.12
|
||||
1766990130034, 2025-12-29 07:35:30,034 - [IDLE] ETH | Px: 3034.65 | M: 3028.7 | B: 3040.6 / S: 3016.8 | delta: -0.1788(-0.0134) | Adj: -3.33%, Vol: 1.02, Thr: 0.0268 | PnL: -9.18 | TotPnL: -9.18
|
||||
1766990130037, 2025-12-29 07:35:30,037 - [IDLE] BNB | Px: 864.20 | M: 863.4 | B: 866.1 / S: 860.6 | delta: -0.8305(-0.0375) | Adj: +3.04%, Vol: 1.00, Thr: 0.1246 | PnL: 3.05 | TotPnL: 3.05
|
||||
1766990160669, 2025-12-29 07:36:00,669 - [IDLE] ETH | Px: 3037.75 | M: 3034.9 | B: 3046.3 / S: 3023.5 | delta: -0.1718(-0.0064) | Adj: -3.49%, Vol: 1.00, Thr: 0.0258 | PnL: -9.66 | TotPnL: -9.66
|
||||
1766990160672, 2025-12-29 07:36:00,672 - [IDLE] BNB | Px: 864.56 | M: 864.1 | B: 866.8 / S: 861.4 | delta: -0.8122(-0.0192) | Adj: +2.83%, Vol: 1.00, Thr: 0.1218 | PnL: 2.78 | TotPnL: 2.78
|
||||
1766990190407, 2025-12-29 07:36:30,407 - [IDLE] ETH | Px: 3038.25 | M: 3035.9 | B: 3047.3 / S: 3024.6 | delta: -0.1706(-0.0052) | Adj: -3.52%, Vol: 1.00, Thr: 0.0256 | PnL: -9.74 | TotPnL: -9.74
|
||||
1766990190409, 2025-12-29 07:36:30,409 - [IDLE] BNB | Px: 864.68 | M: 864.4 | B: 867.1 / S: 861.7 | delta: -0.8061(-0.0131) | Adj: +2.76%, Vol: 1.00, Thr: 0.1209 | PnL: 2.69 | TotPnL: 2.69
|
||||
1766990220571, 2025-12-29 07:37:00,571 - [IDLE] ETH | Px: 3037.25 | M: 3033.9 | B: 3045.4 / S: 3022.4 | delta: -0.1729(-0.0075) | Adj: -3.47%, Vol: 1.00, Thr: 0.0259 | PnL: -9.59 | TotPnL: -9.59
|
||||
1766990220574, 2025-12-29 07:37:00,574 - [IDLE] BNB | Px: 864.52 | M: 864.1 | B: 866.8 / S: 861.3 | delta: -0.8142(-0.0212) | Adj: +2.85%, Vol: 1.00, Thr: 0.1221 | PnL: 2.79 | TotPnL: 2.79
|
||||
1766990310206, 2025-12-29 07:38:30,206 - [IDLE] ETH | Px: 3036.05 | M: 3031.5 | B: 3043.2 / S: 3019.9 | delta: -0.1756(-0.0102) | Adj: -3.40%, Vol: 1.00, Thr: 0.0263 | PnL: -9.41 | TotPnL: -9.41
|
||||
1766990310209, 2025-12-29 07:38:30,209 - [IDLE] BNB | Px: 864.28 | M: 863.5 | B: 866.3 / S: 860.8 | delta: -0.8267(-0.0337) | Adj: +2.99%, Vol: 1.00, Thr: 0.1240 | PnL: 3.04 | TotPnL: 3.04
|
||||
1766990340505, 2025-12-29 07:39:00,505 - [IDLE] ETH | Px: 3035.05 | M: 3029.5 | B: 3041.3 / S: 3017.7 | delta: -0.1779(-0.0125) | Adj: -3.35%, Vol: 1.00, Thr: 0.0267 | PnL: -9.19 | TotPnL: -9.19
|
||||
1766990340508, 2025-12-29 07:39:00,508 - [IDLE] BNB | Px: 864.27 | M: 863.5 | B: 866.3 / S: 860.7 | delta: -0.8272(-0.0342) | Adj: +3.00%, Vol: 1.00, Thr: 0.1241 | PnL: 3.04 | TotPnL: 3.04
|
||||
1766990370368, 2025-12-29 07:39:30,368 - [IDLE] ETH | Px: 3034.45 | M: 3028.3 | B: 3040.2 / S: 3016.4 | delta: -0.1793(-0.0139) | Adj: -3.32%, Vol: 1.00, Thr: 0.0269 | PnL: -9.14 | TotPnL: -9.14
|
||||
1766990370371, 2025-12-29 07:39:30,371 - [IDLE] BNB | Px: 864.14 | M: 863.2 | B: 866.0 / S: 860.4 | delta: -0.8341(-0.0411) | Adj: +3.08%, Vol: 1.00, Thr: 0.1251 | PnL: 3.12 | TotPnL: 3.12
|
||||
1766990400908, 2025-12-29 07:40:00,908 - [IDLE] ETH | Px: 3034.85 | M: 3029.1 | B: 3040.9 / S: 3017.3 | delta: -0.1784(-0.0130) | Adj: -3.34%, Vol: 1.00, Thr: 0.0268 | PnL: -9.23 | TotPnL: -9.23
|
||||
1766990400911, 2025-12-29 07:40:00,911 - [IDLE] BNB | Px: 864.16 | M: 863.3 | B: 866.1 / S: 860.5 | delta: -0.8326(-0.0396) | Adj: +3.06%, Vol: 1.00, Thr: 0.1249 | PnL: 3.10 | TotPnL: 3.10
|
||||
1766990460319, 2025-12-29 07:41:00,319 - [IDLE] ETH | Px: 3035.15 | M: 3029.7 | B: 3041.5 / S: 3017.9 | delta: -0.1777(-0.0123) | Adj: -3.36%, Vol: 1.00, Thr: 0.0267 | PnL: -9.26 | TotPnL: -9.26
|
||||
1766990460321, 2025-12-29 07:41:00,321 - [IDLE] BNB | Px: 864.45 | M: 863.9 | B: 866.6 / S: 861.2 | delta: -0.8181(-0.0251) | Adj: +2.90%, Vol: 1.00, Thr: 0.1227 | PnL: 2.87 | TotPnL: 2.87
|
||||
1766990520542, 2025-12-29 07:42:00,542 - [IDLE] ETH | Px: 3035.95 | M: 3031.3 | B: 3043.0 / S: 3019.6 | delta: -0.1759(-0.0105) | Adj: -3.40%, Vol: 1.00, Thr: 0.0264 | PnL: -9.36 | TotPnL: -9.36
|
||||
1766990520544, 2025-12-29 07:42:00,544 - [IDLE] BNB | Px: 864.49 | M: 864.0 | B: 866.7 / S: 861.3 | delta: -0.8160(-0.0230) | Adj: +2.87%, Vol: 1.00, Thr: 0.1224 | PnL: 2.80 | TotPnL: 2.80
|
||||
1766990550983, 2025-12-29 07:42:30,983 - [IDLE] ETH | Px: 3035.75 | M: 3030.9 | B: 3042.6 / S: 3019.2 | delta: -0.1763(-0.0109) | Adj: -3.39%, Vol: 1.00, Thr: 0.0264 | PnL: -9.36 | TotPnL: -9.36
|
||||
1766990550986, 2025-12-29 07:42:30,986 - [IDLE] BNB | Px: 864.52 | M: 864.0 | B: 866.8 / S: 861.3 | delta: -0.8145(-0.0215) | Adj: +2.85%, Vol: 1.00, Thr: 0.1222 | PnL: 2.80 | TotPnL: 2.80
|
||||
1766990610378, 2025-12-29 07:43:30,378 - [IDLE] ETH | Px: 3036.15 | M: 3031.7 | B: 3043.4 / S: 3020.1 | delta: -0.1754(-0.0100) | Adj: -3.41%, Vol: 1.00, Thr: 0.0263 | PnL: -9.42 | TotPnL: -9.42
|
||||
1766990610381, 2025-12-29 07:43:30,381 - [IDLE] BNB | Px: 864.56 | M: 864.1 | B: 866.8 / S: 861.4 | delta: -0.8125(-0.0195) | Adj: +2.83%, Vol: 1.00, Thr: 0.1219 | PnL: 2.80 | TotPnL: 2.80
|
||||
1766990670318, 2025-12-29 07:44:30,318 - [IDLE] ETH | Px: 3035.85 | M: 3031.1 | B: 3042.8 / S: 3019.4 | delta: -0.1761(-0.0107) | Adj: -3.39%, Vol: 1.00, Thr: 0.0264 | PnL: -9.36 | TotPnL: -9.36
|
||||
1766990670320, 2025-12-29 07:44:30,320 - [IDLE] BNB | Px: 864.48 | M: 864.0 | B: 866.7 / S: 861.2 | delta: -0.8165(-0.0235) | Adj: +2.88%, Vol: 1.00, Thr: 0.1225 | PnL: 2.83 | TotPnL: 2.83
|
||||
1766990700519, 2025-12-29 07:45:00,519 - [IDLE] ETH | Px: 3035.60 | M: 3030.6 | B: 3042.3 / S: 3018.9 | delta: -0.1767(-0.0113) | Adj: -3.38%, Vol: 1.00, Thr: 0.0265 | PnL: -9.32 | TotPnL: -9.32
|
||||
1766990700522, 2025-12-29 07:45:00,522 - [IDLE] BNB | Px: 864.36 | M: 863.7 | B: 866.4 / S: 861.0 | delta: -0.8226(-0.0296) | Adj: +2.95%, Vol: 1.00, Thr: 0.1234 | PnL: 2.84 | TotPnL: 2.84
|
||||
1766990790862, 2025-12-29 07:46:30,862 - [IDLE] ETH | Px: 3034.85 | M: 3029.1 | B: 3040.9 / S: 3017.3 | delta: -0.1784(-0.0130) | Adj: -3.34%, Vol: 1.00, Thr: 0.0268 | PnL: -9.18 | TotPnL: -9.18
|
||||
1766990790865, 2025-12-29 07:46:30,865 - [IDLE] BNB | Px: 864.22 | M: 863.4 | B: 866.2 / S: 860.6 | delta: -0.8295(-0.0365) | Adj: +3.03%, Vol: 1.00, Thr: 0.1244 | PnL: 2.98 | TotPnL: 2.98
|
||||
1766990850321, 2025-12-29 07:47:30,321 - [IDLE] ETH | Px: 3034.75 | M: 3028.9 | B: 3040.8 / S: 3017.1 | delta: -0.1786(-0.0132) | Adj: -3.34%, Vol: 1.00, Thr: 0.0268 | PnL: -9.08 | TotPnL: -9.08
|
||||
1766990850324, 2025-12-29 07:47:30,324 - [IDLE] BNB | Px: 863.96 | M: 862.9 | B: 865.7 / S: 860.0 | delta: -0.8428(-0.0498) | Adj: +3.18%, Vol: 1.00, Thr: 0.1264 | PnL: 3.24 | TotPnL: 3.24
|
||||
1766990910821, 2025-12-29 07:48:30,821 - [IDLE] ETH | Px: 3032.65 | M: 3024.7 | B: 3036.9 / S: 3012.5 | delta: -0.1834(-0.0180) | Adj: -3.23%, Vol: 1.00, Thr: 0.0275 | PnL: -8.84 | TotPnL: -8.84
|
||||
1766990910823, 2025-12-29 07:48:30,823 - [IDLE] BNB | Px: 863.66 | M: 862.2 | B: 865.1 / S: 859.4 | delta: -0.8582(-0.0652) | Adj: +3.35%, Vol: 1.00, Thr: 0.1287 | PnL: 3.59 | TotPnL: 3.59
|
||||
1766990940824, 2025-12-29 07:49:00,824 - [IDLE] ETH | Px: 3033.85 | M: 3027.1 | B: 3039.1 / S: 3015.1 | delta: -0.1807(-0.0153) | Adj: -3.29%, Vol: 1.00, Thr: 0.0271 | PnL: -9.04 | TotPnL: -9.04
|
||||
1766990940827, 2025-12-29 07:49:00,827 - [IDLE] BNB | Px: 864.00 | M: 862.9 | B: 865.7 / S: 860.1 | delta: -0.8408(-0.0478) | Adj: +3.15%, Vol: 1.00, Thr: 0.1261 | PnL: 3.28 | TotPnL: 3.28
|
||||
1766991030032, 2025-12-29 07:50:30,032 - [IDLE] ETH | Px: 3034.45 | M: 3028.3 | B: 3040.2 / S: 3016.4 | delta: -0.1793(-0.0139) | Adj: -3.32%, Vol: 1.00, Thr: 0.0269 | PnL: -9.14 | TotPnL: -9.14
|
||||
1766991030035, 2025-12-29 07:50:30,035 - [IDLE] BNB | Px: 864.04 | M: 863.0 | B: 865.8 / S: 860.2 | delta: -0.8392(-0.0462) | Adj: +3.14%, Vol: 1.00, Thr: 0.1259 | PnL: 3.19 | TotPnL: 3.19
|
||||
1766991060640, 2025-12-29 07:51:00,640 - [IDLE] ETH | Px: 3033.15 | M: 3025.7 | B: 3037.8 / S: 3013.6 | delta: -0.1823(-0.0169) | Adj: -3.26%, Vol: 1.00, Thr: 0.0273 | PnL: -9.08 | TotPnL: -9.08
|
||||
1766991060643, 2025-12-29 07:51:00,643 - [IDLE] BNB | Px: 863.66 | M: 862.2 | B: 865.1 / S: 859.4 | delta: -0.8582(-0.0652) | Adj: +3.35%, Vol: 1.00, Thr: 0.1287 | PnL: 3.36 | TotPnL: 3.36
|
||||
1766991150586, 2025-12-29 07:52:30,586 - [IDLE] ETH | Px: 3032.55 | M: 3024.5 | B: 3036.7 / S: 3012.3 | delta: -0.1836(-0.0182) | Adj: -3.22%, Vol: 1.00, Thr: 0.0275 | PnL: -8.91 | TotPnL: -8.91
|
||||
1766991150589, 2025-12-29 07:52:30,589 - [IDLE] BNB | Px: 863.23 | M: 861.3 | B: 864.2 / S: 858.4 | delta: -0.8806(-0.0876) | Adj: +3.61%, Vol: 1.00, Thr: 0.1321 | PnL: 3.72 | TotPnL: 3.72
|
||||
1766991210673, 2025-12-29 07:53:30,673 - [IDLE] ETH | Px: 3032.95 | M: 3025.3 | B: 3037.4 / S: 3013.2 | delta: -0.1827(-0.0173) | Adj: -3.24%, Vol: 1.00, Thr: 0.0274 | PnL: -8.89 | TotPnL: -8.89
|
||||
1766991210675, 2025-12-29 07:53:30,675 - [IDLE] BNB | Px: 863.41 | M: 861.7 | B: 864.6 / S: 858.8 | delta: -0.8713(-0.0783) | Adj: +3.50%, Vol: 1.00, Thr: 0.1307 | PnL: 3.67 | TotPnL: 3.67
|
||||
1766991330629, 2025-12-29 07:55:30,629 - [IDLE] ETH | Px: 3032.25 | M: 3023.9 | B: 3036.1 / S: 3011.7 | delta: -0.1843(-0.0189) | Adj: -3.21%, Vol: 1.00, Thr: 0.0276 | PnL: -8.76 | TotPnL: -8.76
|
||||
1766991330631, 2025-12-29 07:55:30,631 - [IDLE] BNB | Px: 863.11 | M: 861.0 | B: 864.0 / S: 858.1 | delta: -0.8868(-0.0938) | Adj: +3.68%, Vol: 1.00, Thr: 0.1330 | PnL: 3.91 | TotPnL: 3.91
|
||||
1766991360786, 2025-12-29 07:56:00,786 - [IDLE] ETH | Px: 3031.15 | M: 3021.7 | B: 3034.1 / S: 3009.3 | delta: -0.1868(-0.0214) | Adj: -3.15%, Vol: 1.00, Thr: 0.0280 | PnL: -8.60 | TotPnL: -8.60
|
||||
1766991360789, 2025-12-29 07:56:00,789 - [IDLE] BNB | Px: 863.12 | M: 861.0 | B: 864.0 / S: 858.1 | delta: -0.8863(-0.0933) | Adj: +3.67%, Vol: 1.00, Thr: 0.1329 | PnL: 3.93 | TotPnL: 3.93
|
||||
1766991420935, 2025-12-29 07:57:00,935 - [IDLE] ETH | Px: 3032.45 | M: 3024.3 | B: 3036.5 / S: 3012.1 | delta: -0.1839(-0.0185) | Adj: -3.22%, Vol: 1.00, Thr: 0.0276 | PnL: -8.81 | TotPnL: -8.81
|
||||
1766991420938, 2025-12-29 07:57:00,938 - [IDLE] BNB | Px: 863.32 | M: 861.5 | B: 864.4 / S: 858.5 | delta: -0.8762(-0.0832) | Adj: +3.56%, Vol: 1.00, Thr: 0.1314 | PnL: 3.71 | TotPnL: 3.71
|
||||
1766991510065, 2025-12-29 07:58:30,065 - [IDLE] ETH | Px: 3033.15 | M: 3025.7 | B: 3037.8 / S: 3013.6 | delta: -0.1823(-0.0169) | Adj: -3.26%, Vol: 1.00, Thr: 0.0273 | PnL: -8.93 | TotPnL: -8.93
|
||||
1766991510068, 2025-12-29 07:58:30,068 - [IDLE] BNB | Px: 863.28 | M: 861.4 | B: 864.3 / S: 858.5 | delta: -0.8783(-0.0853) | Adj: +3.58%, Vol: 1.00, Thr: 0.1317 | PnL: 3.81 | TotPnL: 3.81
|
||||
1766991540505, 2025-12-29 07:59:00,505 - [IDLE] ETH | Px: 3033.95 | M: 3027.3 | B: 3039.3 / S: 3015.3 | delta: -0.1804(-0.0150) | Adj: -3.30%, Vol: 1.00, Thr: 0.0271 | PnL: -9.06 | TotPnL: -9.06
|
||||
1766991540508, 2025-12-29 07:59:00,508 - [IDLE] BNB | Px: 863.26 | M: 861.4 | B: 864.3 / S: 858.4 | delta: -0.8788(-0.0858) | Adj: +3.59%, Vol: 1.00, Thr: 0.1318 | PnL: 3.81 | TotPnL: 3.81
|
||||
1766991570905, 2025-12-29 07:59:30,905 - [IDLE] ETH | Px: 3034.15 | M: 3027.7 | B: 3039.6 / S: 3015.8 | delta: -0.1800(-0.0146) | Adj: -3.31%, Vol: 1.00, Thr: 0.0270 | PnL: -9.09 | TotPnL: -9.09
|
||||
1766991570908, 2025-12-29 07:59:30,908 - [IDLE] BNB | Px: 863.31 | M: 861.5 | B: 864.4 / S: 858.5 | delta: -0.8764(-0.0834) | Adj: +3.56%, Vol: 1.00, Thr: 0.1315 | PnL: 3.76 | TotPnL: 3.76
|
||||
1766991630147, 2025-12-29 08:00:30,147 - [IDLE] ETH | Px: 3033.35 | M: 3026.1 | B: 3038.2 / S: 3014.0 | delta: -0.1818(-0.0164) | Adj: -3.27%, Vol: 1.00, Thr: 0.0273 | PnL: -8.96 | TotPnL: -8.96
|
||||
1766991630149, 2025-12-29 08:00:30,149 - [IDLE] BNB | Px: 863.22 | M: 861.3 | B: 864.2 / S: 858.3 | delta: -0.8811(-0.0881) | Adj: +3.61%, Vol: 1.00, Thr: 0.1322 | PnL: 3.81 | TotPnL: 3.81
|
||||
1766991690565, 2025-12-29 08:01:30,565 - [IDLE] ETH | Px: 3036.75 | M: 3032.9 | B: 3044.5 / S: 3021.4 | delta: -0.1741(-0.0087) | Adj: -3.44%, Vol: 1.00, Thr: 0.0261 | PnL: -9.51 | TotPnL: -9.51
|
||||
1766991690568, 2025-12-29 08:01:30,568 - [IDLE] BNB | Px: 863.54 | M: 861.9 | B: 864.8 / S: 859.1 | delta: -0.8649(-0.0719) | Adj: +3.43%, Vol: 1.00, Thr: 0.1297 | PnL: 3.60 | TotPnL: 3.60
|
||||
1766991720737, 2025-12-29 08:02:00,737 - [IDLE] ETH | Px: 3034.95 | M: 3029.3 | B: 3041.1 / S: 3017.5 | delta: -0.1782(-0.0128) | Adj: -3.35%, Vol: 1.00, Thr: 0.0267 | PnL: -9.21 | TotPnL: -9.21
|
||||
1766991720739, 2025-12-29 08:02:00,739 - [IDLE] BNB | Px: 863.14 | M: 861.1 | B: 864.0 / S: 858.1 | delta: -0.8855(-0.0925) | Adj: +3.66%, Vol: 1.00, Thr: 0.1328 | PnL: 3.86 | TotPnL: 3.86
|
||||
1766991750160, 2025-12-29 08:02:30,160 - [IDLE] ETH | Px: 3035.45 | M: 3030.3 | B: 3042.1 / S: 3018.6 | delta: -0.1770(-0.0116) | Adj: -3.37%, Vol: 1.00, Thr: 0.0266 | PnL: -9.29 | TotPnL: -9.29
|
||||
1766991750163, 2025-12-29 08:02:30,163 - [IDLE] BNB | Px: 863.18 | M: 861.2 | B: 864.1 / S: 858.2 | delta: -0.8834(-0.0904) | Adj: +3.64%, Vol: 1.00, Thr: 0.1325 | PnL: 3.90 | TotPnL: 3.90
|
||||
1766991780704, 2025-12-29 08:03:00,704 - [IDLE] ETH | Px: 3036.95 | M: 3033.3 | B: 3044.9 / S: 3021.8 | delta: -0.1736(-0.0082) | Adj: -3.45%, Vol: 1.08, Thr: 0.0260 | PnL: -9.56 | TotPnL: -9.56
|
||||
1766991780707, 2025-12-29 08:03:00,707 - [IDLE] BNB | Px: 863.20 | M: 861.2 | B: 864.2 / S: 858.3 | delta: -0.8819(-0.0889) | Adj: +3.62%, Vol: 1.00, Thr: 0.1323 | PnL: 3.84 | TotPnL: 3.84
|
||||
1766991810504, 2025-12-29 08:03:30,504 - [IDLE] ETH | Px: 3033.85 | M: 3027.1 | B: 3039.1 / S: 3015.1 | delta: -0.1807(-0.0153) | Adj: -3.29%, Vol: 1.14, Thr: 0.0271 | PnL: -9.01 | TotPnL: -9.01
|
||||
1766991810507, 2025-12-29 08:03:30,507 - [IDLE] BNB | Px: 862.72 | M: 860.2 | B: 863.2 / S: 857.2 | delta: -0.9070(-0.1140) | Adj: +3.90%, Vol: 1.00, Thr: 0.1360 | PnL: 4.22 | TotPnL: 4.22
|
||||
1766991900098, 2025-12-29 08:05:00,098 - [IDLE] ETH | Px: 3032.25 | M: 3023.9 | B: 3036.1 / S: 3011.7 | delta: -0.1843(-0.0189) | Adj: -3.21%, Vol: 1.13, Thr: 0.0276 | PnL: -8.78 | TotPnL: -8.78
|
||||
1766991900100, 2025-12-29 08:05:00,100 - [IDLE] BNB | Px: 862.28 | M: 859.2 | B: 862.3 / S: 856.1 | delta: -0.9301(-0.1371) | Adj: +4.16%, Vol: 1.00, Thr: 0.1395 | PnL: 4.61 | TotPnL: 4.61
|
||||
1766991930796, 2025-12-29 08:05:30,796 - [IDLE] ETH | Px: 3032.95 | M: 3025.3 | B: 3037.4 / S: 3013.2 | delta: -0.1827(-0.0173) | Adj: -3.24%, Vol: 1.11, Thr: 0.0274 | PnL: -8.88 | TotPnL: -8.88
|
||||
1766991930799, 2025-12-29 08:05:30,799 - [IDLE] BNB | Px: 862.44 | M: 859.6 | B: 862.6 / S: 856.5 | delta: -0.9218(-0.1288) | Adj: +4.07%, Vol: 1.00, Thr: 0.1383 | PnL: 4.47 | TotPnL: 4.47
|
||||
1766991990347, 2025-12-29 08:06:30,347 - [IDLE] ETH | Px: 3031.45 | M: 3022.3 | B: 3034.6 / S: 3010.0 | delta: -0.1862(-0.0208) | Adj: -3.17%, Vol: 1.05, Thr: 0.0279 | PnL: -8.65 | TotPnL: -8.65
|
||||
1766991990350, 2025-12-29 08:06:30,350 - [IDLE] BNB | Px: 862.22 | M: 859.1 | B: 862.2 / S: 856.0 | delta: -0.9328(-0.1398) | Adj: +4.19%, Vol: 1.00, Thr: 0.1399 | PnL: 4.64 | TotPnL: 4.64
|
||||
1766991992476, 2025-12-29 08:06:32,476 - [TRIG] Net BNB: SELL 0.1434 | Tgt: -0.9364 / Cur: -0.7930 | Thresh: 0.1405
|
||||
1766991992477, 2025-12-29 08:06:32,477 - [ORDER] ALO BNB SELL 0.143 @ 862.01
|
||||
1766991993987, 2025-12-29 08:06:33,987 - Sleeping 5s to allow position update...
|
||||
1766992000873, 2025-12-29 08:06:40,873 - [WAIT] BNB Pending SELL Order 281265022642 @ 862.01 (Dist: 0.010%)
|
||||
1766992021181, 2025-12-29 08:07:01,181 - Cancelling idle order 281265022642 (A @ 862.01)
|
||||
1766992080829, 2025-12-29 08:08:00,829 - [IDLE] ETH | Px: 3030.95 | M: 3021.3 | B: 3033.7 / S: 3008.9 | delta: -0.1873(-0.0219) | Adj: -3.14%, Vol: 1.18, Thr: 0.0281 | PnL: -8.56 | TotPnL: -8.56
|
||||
1766992080832, 2025-12-29 08:08:00,832 - [IDLE] BNB | Px: 862.36 | M: 860.7 | B: 863.8 / S: 857.7 | delta: -0.9257(-0.0727) | Adj: +4.11%, Vol: 1.17, Thr: 0.1389 | PnL: 4.60 | TotPnL: 4.60
|
||||
1766992128158, 2025-12-29 08:08:48,158 - [TRIG] Net ETH: SELL 0.0362 | Tgt: -0.2016 / Cur: -0.1654 | Thresh: 0.0302
|
||||
1766992128159, 2025-12-29 08:08:48,159 - [ORDER] ALO ETH SELL 0.0361 @ 3023.5
|
||||
1766992129364, 2025-12-29 08:08:49,364 - Sleeping 5s to allow position update...
|
||||
1766992136310, 2025-12-29 08:08:56,310 - Cancelling stale order 281266440990 (A @ 3023.5)
|
||||
1766992138020, 2025-12-29 08:08:58,020 - [TRIG] Net BNB: SELL 0.1750 | Tgt: -1.0280 / Cur: -0.8530 | Thresh: 0.1542
|
||||
1766992138021, 2025-12-29 08:08:58,021 - [ORDER] ALO BNB SELL 0.175 @ 860.69
|
||||
1766992138779, 2025-12-29 08:08:58,779 - Sleeping 5s to allow position update...
|
||||
1766992170612, 2025-12-29 08:09:30,612 - [IDLE] ETH | Px: 3016.75 | M: 2992.7 | B: 3007.2 / S: 2978.2 | delta: -0.2202(-0.0548) | Adj: -2.41%, Vol: 2.63, Thr: 0.0330 | PnL: -6.21 | TotPnL: -6.21
|
||||
1766992170615, 2025-12-29 08:09:30,615 - [IDLE] BNB | Px: 860.94 | M: 861.5 | B: 864.9 / S: 858.2 | delta: -1.0006(+0.0274) | Adj: +4.94%, Vol: 1.51, Thr: 0.1501 | PnL: 5.52 | TotPnL: 5.52
|
||||
1766992190823, 2025-12-29 08:09:50,823 - [TRIG] Net ETH: SELL 0.0517 | Tgt: -0.2171 / Cur: -0.1654 | Thresh: 0.0326
|
||||
1766992190824, 2025-12-29 08:09:50,824 - [ORDER] ALO ETH SELL 0.0516 @ 3018.2
|
||||
1766992191514, 2025-12-29 08:09:51,514 - Sleeping 5s to allow position update...
|
||||
1766992200292, 2025-12-29 08:10:00,292 - [IDLE] ETH | Px: 3018.85 | M: 3019.6 | B: 3033.8 / S: 3005.4 | delta: -0.2153(+0.0017) | Adj: -2.52%, Vol: 3.00, Thr: 0.0323 | PnL: -6.51 | TotPnL: -6.51
|
||||
1766992200295, 2025-12-29 08:10:00,295 - [IDLE] BNB | Px: 861.44 | M: 862.6 | B: 865.9 / S: 859.4 | delta: -0.9742(+0.0538) | Adj: +4.65%, Vol: 1.64, Thr: 0.1461 | PnL: 5.16 | TotPnL: 5.16
|
||||
1766992230904, 2025-12-29 08:10:30,904 - [IDLE] ETH | Px: 3020.55 | M: 3023.0 | B: 3037.0 / S: 3009.1 | delta: -0.2113(+0.0057) | Adj: -2.61%, Vol: 3.00, Thr: 0.0317 | PnL: -6.95 | TotPnL: -6.95
|
||||
1766992230907, 2025-12-29 08:10:30,907 - [IDLE] BNB | Px: 861.53 | M: 862.8 | B: 866.0 / S: 859.6 | delta: -0.9692(+0.0588) | Adj: +4.60%, Vol: 1.66, Thr: 0.1454 | PnL: 5.05 | TotPnL: 5.05
|
||||
1766992290944, 2025-12-29 08:11:30,944 - [IDLE] ETH | Px: 3021.05 | M: 3024.1 | B: 3037.9 / S: 3010.2 | delta: -0.2102(+0.0068) | Adj: -2.63%, Vol: 3.00, Thr: 0.0315 | PnL: -7.12 | TotPnL: -7.12
|
||||
1766992290947, 2025-12-29 08:11:30,947 - [IDLE] BNB | Px: 861.76 | M: 863.3 | B: 866.5 / S: 860.2 | delta: -0.9571(+0.0709) | Adj: +4.46%, Vol: 1.50, Thr: 0.1436 | PnL: 4.81 | TotPnL: 4.81
|
||||
1766992380590, 2025-12-29 08:13:00,590 - [IDLE] ETH | Px: 3021.65 | M: 3025.3 | B: 3039.0 / S: 3011.5 | delta: -0.2088(+0.0082) | Adj: -2.66%, Vol: 3.00, Thr: 0.0313 | PnL: -7.25 | TotPnL: -7.25
|
||||
1766992380592, 2025-12-29 08:13:00,592 - [IDLE] BNB | Px: 861.56 | M: 862.9 | B: 866.1 / S: 859.7 | delta: -0.9676(+0.0604) | Adj: +4.58%, Vol: 1.18, Thr: 0.1451 | PnL: 4.94 | TotPnL: 4.94
|
||||
1766992440620, 2025-12-29 08:14:00,620 - [IDLE] ETH | Px: 3017.35 | M: 3016.6 | B: 3031.0 / S: 3002.2 | delta: -0.2188(-0.0018) | Adj: -2.44%, Vol: 3.00, Thr: 0.0328 | PnL: -6.38 | TotPnL: -6.38
|
||||
1766992440623, 2025-12-29 08:14:00,623 - [IDLE] BNB | Px: 860.88 | M: 861.4 | B: 864.7 / S: 858.1 | delta: -1.0038(+0.0242) | Adj: +4.98%, Vol: 1.15, Thr: 0.1506 | PnL: 5.76 | TotPnL: 5.76
|
||||
1766992470558, 2025-12-29 08:14:30,558 - [IDLE] ETH | Px: 3019.45 | M: 3020.8 | B: 3034.9 / S: 3006.7 | delta: -0.2139(+0.0031) | Adj: -2.55%, Vol: 3.00, Thr: 0.0321 | PnL: -6.71 | TotPnL: -6.71
|
||||
1766992470561, 2025-12-29 08:14:30,561 - [IDLE] BNB | Px: 860.86 | M: 861.4 | B: 864.7 / S: 858.0 | delta: -1.0049(+0.0231) | Adj: +4.99%, Vol: 1.20, Thr: 0.1507 | PnL: 5.69 | TotPnL: 5.69
|
||||
1766992500517, 2025-12-29 08:15:00,517 - [IDLE] ETH | Px: 3019.65 | M: 3021.2 | B: 3035.3 / S: 3007.2 | delta: -0.2134(+0.0036) | Adj: -2.56%, Vol: 3.00, Thr: 0.0320 | PnL: -6.75 | TotPnL: -6.75
|
||||
1766992500519, 2025-12-29 08:15:00,519 - [IDLE] BNB | Px: 860.84 | M: 861.3 | B: 864.7 / S: 858.0 | delta: -1.0059(+0.0221) | Adj: +5.00%, Vol: 1.24, Thr: 0.1509 | PnL: 5.76 | TotPnL: 5.76
|
||||
1766992560831, 2025-12-29 08:16:00,831 - [IDLE] ETH | Px: 3019.65 | M: 3021.2 | B: 3035.3 / S: 3007.2 | delta: -0.2134(+0.0036) | Adj: -2.56%, Vol: 3.00, Thr: 0.0320 | PnL: -6.77 | TotPnL: -6.77
|
||||
1766992560834, 2025-12-29 08:16:00,834 - [IDLE] BNB | Px: 860.91 | M: 861.5 | B: 864.8 / S: 858.2 | delta: -1.0020(+0.0260) | Adj: +4.96%, Vol: 1.21, Thr: 0.1503 | PnL: 5.68 | TotPnL: 5.68
|
||||
1766992590511, 2025-12-29 08:16:30,511 - [IDLE] ETH | Px: 3017.75 | M: 3017.4 | B: 3031.7 / S: 3003.0 | delta: -0.2179(-0.0009) | Adj: -2.46%, Vol: 3.00, Thr: 0.0327 | PnL: -6.36 | TotPnL: -6.36
|
||||
1766992590514, 2025-12-29 08:16:30,514 - [IDLE] BNB | Px: 860.94 | M: 861.5 | B: 864.9 / S: 858.2 | delta: -1.0006(+0.0274) | Adj: +4.94%, Vol: 1.17, Thr: 0.1501 | PnL: 5.66 | TotPnL: 5.66
|
||||
1766992710683, 2025-12-29 08:18:30,683 - [IDLE] ETH | Px: 3015.75 | M: 3013.3 | B: 3027.9 / S: 2998.7 | delta: -0.2226(-0.0056) | Adj: -2.36%, Vol: 1.63, Thr: 0.0334 | PnL: -5.90 | TotPnL: -5.90
|
||||
1766992710685, 2025-12-29 08:18:30,685 - [IDLE] BNB | Px: 860.54 | M: 860.7 | B: 864.1 / S: 857.3 | delta: -1.0214(+0.0066) | Adj: +5.17%, Vol: 1.04, Thr: 0.1532 | PnL: 5.98 | TotPnL: 5.98
|
||||
1766992740234, 2025-12-29 08:19:00,234 - [IDLE] ETH | Px: 3014.95 | M: 3011.7 | B: 3026.4 / S: 2996.9 | delta: -0.2244(-0.0074) | Adj: -2.32%, Vol: 1.59, Thr: 0.0337 | PnL: -5.73 | TotPnL: -5.73
|
||||
1766992740236, 2025-12-29 08:19:00,236 - [IDLE] BNB | Px: 860.50 | M: 860.6 | B: 864.0 / S: 857.2 | delta: -1.0235(+0.0045) | Adj: +5.19%, Vol: 1.07, Thr: 0.1535 | PnL: 6.12 | TotPnL: 6.12
|
||||
1766992800206, 2025-12-29 08:20:00,206 - [IDLE] ETH | Px: 3015.65 | M: 3013.1 | B: 3027.8 / S: 2998.5 | delta: -0.2228(-0.0058) | Adj: -2.35%, Vol: 1.65, Thr: 0.0334 | PnL: -5.88 | TotPnL: -5.88
|
||||
1766992800208, 2025-12-29 08:20:00,208 - [IDLE] BNB | Px: 859.92 | M: 859.3 | B: 862.8 / S: 855.8 | delta: -1.0545(-0.0265) | Adj: +5.53%, Vol: 1.30, Thr: 0.1582 | PnL: 6.70 | TotPnL: 6.70
|
||||
1766992860362, 2025-12-29 08:21:00,362 - [IDLE] ETH | Px: 3015.35 | M: 3012.5 | B: 3027.2 / S: 2997.8 | delta: -0.2235(-0.0065) | Adj: -2.34%, Vol: 1.65, Thr: 0.0335 | PnL: -5.97 | TotPnL: -5.97
|
||||
1766992860364, 2025-12-29 08:21:00,364 - [IDLE] BNB | Px: 859.72 | M: 858.9 | B: 862.4 / S: 855.4 | delta: -1.0652(-0.0372) | Adj: +5.65%, Vol: 1.33, Thr: 0.1598 | PnL: 6.73 | TotPnL: 6.73
|
||||
1766992980253, 2025-12-29 08:23:00,253 - [IDLE] ETH | Px: 3013.55 | M: 3008.9 | B: 3023.8 / S: 2993.9 | delta: -0.2277(-0.0107) | Adj: -2.25%, Vol: 1.43, Thr: 0.0342 | PnL: -5.45 | TotPnL: -5.45
|
||||
1766992980255, 2025-12-29 08:23:00,255 - [IDLE] BNB | Px: 859.30 | M: 858.0 | B: 861.6 / S: 854.4 | delta: -1.0879(-0.0599) | Adj: +5.89%, Vol: 1.57, Thr: 0.1632 | PnL: 7.42 | TotPnL: 7.42
|
||||
1766993010228, 2025-12-29 08:23:30,228 - [IDLE] ETH | Px: 3012.55 | M: 3006.8 | B: 3021.9 / S: 2991.7 | delta: -0.2301(-0.0131) | Adj: -2.19%, Vol: 1.48, Thr: 0.0345 | PnL: -5.19 | TotPnL: -5.19
|
||||
1766993010230, 2025-12-29 08:23:30,230 - [IDLE] BNB | Px: 859.08 | M: 857.5 | B: 861.1 / S: 853.8 | delta: -1.1003(-0.0723) | Adj: +6.03%, Vol: 1.65, Thr: 0.1650 | PnL: 7.61 | TotPnL: 7.61
|
||||
1766993040197, 2025-12-29 08:24:00,197 - [IDLE] ETH | Px: 3010.75 | M: 3003.2 | B: 3018.5 / S: 2987.8 | delta: -0.2343(-0.0173) | Adj: -2.10%, Vol: 1.62, Thr: 0.0351 | PnL: -4.82 | TotPnL: -4.82
|
||||
1766993040200, 2025-12-29 08:24:00,200 - [IDLE] BNB | Px: 858.96 | M: 857.2 | B: 860.9 / S: 853.6 | delta: -1.1068(-0.0788) | Adj: +6.10%, Vol: 1.72, Thr: 0.1660 | PnL: 7.83 | TotPnL: 7.83
|
||||
1766993070729, 2025-12-29 08:24:30,729 - [IDLE] ETH | Px: 3012.45 | M: 3006.6 | B: 3021.7 / S: 2991.5 | delta: -0.2303(-0.0133) | Adj: -2.19%, Vol: 1.69, Thr: 0.0345 | PnL: -5.23 | TotPnL: -5.23
|
||||
1766993070732, 2025-12-29 08:24:30,732 - [IDLE] BNB | Px: 859.04 | M: 857.4 | B: 861.0 / S: 853.7 | delta: -1.1025(-0.0745) | Adj: +6.05%, Vol: 1.76, Thr: 0.1654 | PnL: 7.68 | TotPnL: 7.68
|
||||
1766993190504, 2025-12-29 08:26:30,504 - [IDLE] ETH | Px: 3013.75 | M: 3009.3 | B: 3024.2 / S: 2994.3 | delta: -0.2273(-0.0103) | Adj: -2.26%, Vol: 1.30, Thr: 0.0341 | PnL: -5.45 | TotPnL: -5.45
|
||||
1766993190507, 2025-12-29 08:26:30,507 - [IDLE] BNB | Px: 859.32 | M: 858.0 | B: 861.6 / S: 854.4 | delta: -1.0868(-0.0588) | Adj: +5.88%, Vol: 1.52, Thr: 0.1630 | PnL: 7.32 | TotPnL: 7.32
|
||||
1766993280912, 2025-12-29 08:28:00,912 - [IDLE] ETH | Px: 3014.75 | M: 3011.3 | B: 3026.1 / S: 2996.5 | delta: -0.2249(-0.0079) | Adj: -2.31%, Vol: 1.02, Thr: 0.0337 | PnL: -5.71 | TotPnL: -5.71
|
||||
1766993280915, 2025-12-29 08:28:00,915 - [IDLE] BNB | Px: 859.42 | M: 858.2 | B: 861.8 / S: 854.7 | delta: -1.0814(-0.0534) | Adj: +5.82%, Vol: 1.05, Thr: 0.1622 | PnL: 7.24 | TotPnL: 7.24
|
||||
1766993370442, 2025-12-29 08:29:30,442 - [IDLE] ETH | Px: 3012.85 | M: 3007.4 | B: 3022.5 / S: 2992.4 | delta: -0.2294(-0.0124) | Adj: -2.21%, Vol: 1.00, Thr: 0.0344 | PnL: -5.27 | TotPnL: -5.27
|
||||
1766993370445, 2025-12-29 08:29:30,445 - [IDLE] BNB | Px: 859.34 | M: 858.1 | B: 861.6 / S: 854.5 | delta: -1.0863(-0.0583) | Adj: +5.88%, Vol: 1.00, Thr: 0.1629 | PnL: 7.34 | TotPnL: 7.34
|
||||
1766993400546, 2025-12-29 08:30:00,546 - [IDLE] ETH | Px: 3013.35 | M: 3008.5 | B: 3023.4 / S: 2993.5 | delta: -0.2282(-0.0112) | Adj: -2.23%, Vol: 1.00, Thr: 0.0342 | PnL: -5.38 | TotPnL: -5.38
|
||||
1766993400549, 2025-12-29 08:30:00,549 - [IDLE] BNB | Px: 858.60 | M: 856.5 | B: 860.2 / S: 852.7 | delta: -1.1258(-0.0978) | Adj: +6.30%, Vol: 1.00, Thr: 0.1689 | PnL: 8.24 | TotPnL: 8.24
|
||||
1766993430162, 2025-12-29 08:30:30,162 - [IDLE] ETH | Px: 3013.95 | M: 3009.7 | B: 3024.6 / S: 2994.8 | delta: -0.2268(-0.0098) | Adj: -2.27%, Vol: 1.00, Thr: 0.0340 | PnL: -5.30 | TotPnL: -5.30
|
||||
1766993430165, 2025-12-29 08:30:30,165 - [IDLE] BNB | Px: 858.68 | M: 856.6 | B: 860.3 / S: 852.9 | delta: -1.1215(-0.0935) | Adj: +6.26%, Vol: 1.00, Thr: 0.1682 | PnL: 8.12 | TotPnL: 8.12
|
||||
1766993460416, 2025-12-29 08:31:00,416 - [IDLE] ETH | Px: 3014.75 | M: 3011.3 | B: 3026.1 / S: 2996.5 | delta: -0.2249(-0.0079) | Adj: -2.31%, Vol: 1.00, Thr: 0.0337 | PnL: -5.67 | TotPnL: -5.67
|
||||
1766993460419, 2025-12-29 08:31:00,419 - [IDLE] BNB | Px: 858.76 | M: 856.8 | B: 860.5 / S: 853.1 | delta: -1.1171(-0.0891) | Adj: +6.21%, Vol: 1.00, Thr: 0.1676 | PnL: 8.01 | TotPnL: 8.01
|
||||
1766993520644, 2025-12-29 08:32:00,644 - [IDLE] ETH | Px: 3020.25 | M: 3022.4 | B: 3036.4 / S: 3008.5 | delta: -0.2120(+0.0050) | Adj: -2.59%, Vol: 1.15, Thr: 0.0318 | PnL: -6.88 | TotPnL: -6.88
|
||||
1766993520647, 2025-12-29 08:32:00,647 - [IDLE] BNB | Px: 859.36 | M: 858.1 | B: 861.7 / S: 854.5 | delta: -1.0846(-0.0566) | Adj: +5.86%, Vol: 1.00, Thr: 0.1627 | PnL: 7.22 | TotPnL: 7.22
|
||||
1766993580062, 2025-12-29 08:33:00,062 - [IDLE] ETH | Px: 3017.65 | M: 3017.2 | B: 3031.5 / S: 3002.8 | delta: -0.2181(-0.0011) | Adj: -2.46%, Vol: 1.46, Thr: 0.0327 | PnL: -6.32 | TotPnL: -6.32
|
||||
1766993580064, 2025-12-29 08:33:00,064 - [IDLE] BNB | Px: 859.57 | M: 858.6 | B: 862.1 / S: 855.0 | delta: -1.0736(-0.0456) | Adj: +5.74%, Vol: 1.00, Thr: 0.1610 | PnL: 7.21 | TotPnL: 7.21
|
||||
1766993610179, 2025-12-29 08:33:30,179 - [IDLE] ETH | Px: 3018.85 | M: 3019.6 | B: 3033.8 / S: 3005.4 | delta: -0.2153(+0.0017) | Adj: -2.52%, Vol: 1.52, Thr: 0.0323 | PnL: -6.60 | TotPnL: -6.60
|
||||
1766993610181, 2025-12-29 08:33:30,181 - [IDLE] BNB | Px: 859.90 | M: 859.3 | B: 862.8 / S: 855.8 | delta: -1.0556(-0.0276) | Adj: +5.54%, Vol: 1.00, Thr: 0.1583 | PnL: 6.64 | TotPnL: 6.64
|
||||
1766993640396, 2025-12-29 08:34:00,396 - [IDLE] ETH | Px: 3018.85 | M: 3019.6 | B: 3033.8 / S: 3005.4 | delta: -0.2153(+0.0017) | Adj: -2.52%, Vol: 1.50, Thr: 0.0323 | PnL: -6.60 | TotPnL: -6.60
|
||||
1766993640399, 2025-12-29 08:34:00,399 - [IDLE] BNB | Px: 859.90 | M: 859.3 | B: 862.8 / S: 855.8 | delta: -1.0556(-0.0276) | Adj: +5.54%, Vol: 1.00, Thr: 0.1583 | PnL: 6.70 | TotPnL: 6.70
|
||||
1766993730678, 2025-12-29 08:35:30,678 - [IDLE] ETH | Px: 3018.35 | M: 3018.6 | B: 3032.8 / S: 3004.3 | delta: -0.2165(+0.0005) | Adj: -2.49%, Vol: 1.69, Thr: 0.0325 | PnL: -6.47 | TotPnL: -6.47
|
||||
1766993730680, 2025-12-29 08:35:30,680 - [IDLE] BNB | Px: 860.44 | M: 860.5 | B: 863.9 / S: 857.1 | delta: -1.0267(+0.0013) | Adj: +5.23%, Vol: 1.15, Thr: 0.1540 | PnL: 6.19 | TotPnL: 6.19
|
||||
1766993820178, 2025-12-29 08:37:00,178 - [IDLE] ETH | Px: 3019.45 | M: 3020.8 | B: 3034.9 / S: 3006.7 | delta: -0.2139(+0.0031) | Adj: -2.55%, Vol: 1.74, Thr: 0.0321 | PnL: -6.73 | TotPnL: -6.73
|
||||
1766993820181, 2025-12-29 08:37:00,181 - [IDLE] BNB | Px: 861.22 | M: 862.2 | B: 865.4 / S: 858.9 | delta: -0.9856(+0.0424) | Adj: +4.78%, Vol: 1.66, Thr: 0.1478 | PnL: 5.40 | TotPnL: 5.40
|
||||
1766993850479, 2025-12-29 08:37:30,479 - [IDLE] ETH | Px: 3018.65 | M: 3019.2 | B: 3033.4 / S: 3005.0 | delta: -0.2158(+0.0012) | Adj: -2.51%, Vol: 1.74, Thr: 0.0324 | PnL: -6.55 | TotPnL: -6.55
|
||||
1766993850482, 2025-12-29 08:37:30,482 - [IDLE] BNB | Px: 860.96 | M: 861.6 | B: 864.9 / S: 858.3 | delta: -0.9991(+0.0289) | Adj: +4.93%, Vol: 1.82, Thr: 0.1499 | PnL: 5.57 | TotPnL: 5.57
|
||||
1766993910407, 2025-12-29 08:38:30,407 - [IDLE] ETH | Px: 3018.85 | M: 3019.6 | B: 3033.8 / S: 3005.4 | delta: -0.2153(+0.0017) | Adj: -2.52%, Vol: 1.60, Thr: 0.0323 | PnL: -6.60 | TotPnL: -6.60
|
||||
1766993910410, 2025-12-29 08:38:30,410 - [IDLE] BNB | Px: 860.80 | M: 861.2 | B: 864.6 / S: 857.9 | delta: -1.0081(+0.0199) | Adj: +5.03%, Vol: 1.93, Thr: 0.1512 | PnL: 5.79 | TotPnL: 5.79
|
||||
1766993940294, 2025-12-29 08:39:00,294 - [IDLE] ETH | Px: 3018.35 | M: 3018.6 | B: 3032.8 / S: 3004.3 | delta: -0.2165(+0.0005) | Adj: -2.49%, Vol: 1.45, Thr: 0.0325 | PnL: -6.49 | TotPnL: -6.49
|
||||
1766993940297, 2025-12-29 08:39:00,297 - [IDLE] BNB | Px: 860.76 | M: 861.1 | B: 864.5 / S: 857.8 | delta: -1.0102(+0.0178) | Adj: +5.05%, Vol: 1.95, Thr: 0.1515 | PnL: 5.78 | TotPnL: 5.78
|
||||
1766994060459, 2025-12-29 08:41:00,459 - [IDLE] ETH | Px: 3016.95 | M: 3015.7 | B: 3030.2 / S: 3001.3 | delta: -0.2197(-0.0027) | Adj: -2.42%, Vol: 1.00, Thr: 0.0330 | PnL: -6.19 | TotPnL: -6.19
|
||||
1766994060462, 2025-12-29 08:41:00,462 - [IDLE] BNB | Px: 860.29 | M: 860.1 | B: 863.6 / S: 856.7 | delta: -1.0350(-0.0070) | Adj: +5.32%, Vol: 1.33, Thr: 0.1552 | PnL: 6.33 | TotPnL: 6.33
|
||||
1766994150022, 2025-12-29 08:42:30,022 - [IDLE] ETH | Px: 3016.55 | M: 3014.9 | B: 3029.4 / S: 3000.4 | delta: -0.2207(-0.0037) | Adj: -2.40%, Vol: 1.00, Thr: 0.0331 | PnL: -6.08 | TotPnL: -6.08
|
||||
1766994150025, 2025-12-29 08:42:30,025 - [IDLE] BNB | Px: 860.38 | M: 860.3 | B: 863.7 / S: 856.9 | delta: -1.0304(-0.0024) | Adj: +5.27%, Vol: 1.00, Thr: 0.1546 | PnL: 6.19 | TotPnL: 6.19
|
||||
1766994180406, 2025-12-29 08:43:00,406 - [IDLE] ETH | Px: 3016.55 | M: 3014.9 | B: 3029.4 / S: 3000.4 | delta: -0.2207(-0.0037) | Adj: -2.40%, Vol: 1.00, Thr: 0.0331 | PnL: -6.03 | TotPnL: -6.03
|
||||
1766994180409, 2025-12-29 08:43:00,409 - [IDLE] BNB | Px: 860.26 | M: 860.1 | B: 863.5 / S: 856.6 | delta: -1.0366(-0.0086) | Adj: +5.34%, Vol: 1.00, Thr: 0.1555 | PnL: 6.36 | TotPnL: 6.36
|
||||
1766994240815, 2025-12-29 08:44:00,815 - [IDLE] ETH | Px: 3015.95 | M: 3013.7 | B: 3028.3 / S: 2999.1 | delta: -0.2221(-0.0051) | Adj: -2.37%, Vol: 1.00, Thr: 0.0333 | PnL: -5.90 | TotPnL: -5.90
|
||||
1766994240818, 2025-12-29 08:44:00,818 - [IDLE] BNB | Px: 860.10 | M: 859.7 | B: 863.2 / S: 856.3 | delta: -1.0451(-0.0171) | Adj: +5.43%, Vol: 1.00, Thr: 0.1568 | PnL: 6.54 | TotPnL: 6.54
|
||||
1766994300293, 2025-12-29 08:45:00,293 - [IDLE] ETH | Px: 3015.15 | M: 3012.1 | B: 3026.8 / S: 2997.4 | delta: -0.2240(-0.0070) | Adj: -2.33%, Vol: 1.00, Thr: 0.0336 | PnL: -5.75 | TotPnL: -5.75
|
||||
1766994300296, 2025-12-29 08:45:00,296 - [IDLE] BNB | Px: 860.00 | M: 859.5 | B: 863.0 / S: 856.0 | delta: -1.0502(-0.0222) | Adj: +5.49%, Vol: 1.00, Thr: 0.1575 | PnL: 6.64 | TotPnL: 6.64
|
||||
1766994330386, 2025-12-29 08:45:30,386 - [IDLE] ETH | Px: 3014.95 | M: 3011.7 | B: 3026.4 / S: 2996.9 | delta: -0.2244(-0.0074) | Adj: -2.32%, Vol: 1.00, Thr: 0.0337 | PnL: -5.77 | TotPnL: -5.77
|
||||
1766994330388, 2025-12-29 08:45:30,388 - [IDLE] BNB | Px: 860.00 | M: 859.5 | B: 863.0 / S: 856.0 | delta: -1.0502(-0.0222) | Adj: +5.49%, Vol: 1.00, Thr: 0.1575 | PnL: 6.65 | TotPnL: 6.65
|
||||
1766994360157, 2025-12-29 08:46:00,157 - [IDLE] ETH | Px: 3014.95 | M: 3011.7 | B: 3026.4 / S: 2996.9 | delta: -0.2244(-0.0074) | Adj: -2.32%, Vol: 1.00, Thr: 0.0337 | PnL: -5.77 | TotPnL: -5.77
|
||||
1766994360159, 2025-12-29 08:46:00,159 - [IDLE] BNB | Px: 860.16 | M: 859.9 | B: 863.3 / S: 856.4 | delta: -1.0416(-0.0136) | Adj: +5.39%, Vol: 1.00, Thr: 0.1562 | PnL: 6.49 | TotPnL: 6.49
|
||||
1766994390964, 2025-12-29 08:46:30,964 - [IDLE] ETH | Px: 3015.05 | M: 3011.9 | B: 3026.6 / S: 2997.2 | delta: -0.2242(-0.0072) | Adj: -2.32%, Vol: 1.00, Thr: 0.0336 | PnL: -5.82 | TotPnL: -5.82
|
||||
1766994390966, 2025-12-29 08:46:30,966 - [IDLE] BNB | Px: 860.38 | M: 860.3 | B: 863.7 / S: 856.9 | delta: -1.0304(-0.0024) | Adj: +5.27%, Vol: 1.00, Thr: 0.1546 | PnL: 6.27 | TotPnL: 6.27
|
||||
1766994510617, 2025-12-29 08:48:30,617 - [IDLE] ETH | Px: 3017.05 | M: 3016.0 | B: 3030.4 / S: 3001.5 | delta: -0.2195(-0.0025) | Adj: -2.43%, Vol: 1.00, Thr: 0.0329 | PnL: -6.21 | TotPnL: -6.21
|
||||
1766994510620, 2025-12-29 08:48:30,620 - [IDLE] BNB | Px: 860.02 | M: 859.6 | B: 863.0 / S: 856.1 | delta: -1.0491(-0.0211) | Adj: +5.47%, Vol: 1.00, Thr: 0.1574 | PnL: 6.63 | TotPnL: 6.63
|
||||
1766994540892, 2025-12-29 08:49:00,892 - [IDLE] ETH | Px: 3017.05 | M: 3016.0 | B: 3030.4 / S: 3001.5 | delta: -0.2195(-0.0025) | Adj: -2.43%, Vol: 1.00, Thr: 0.0329 | PnL: -6.21 | TotPnL: -6.21
|
||||
1766994540895, 2025-12-29 08:49:00,895 - [IDLE] BNB | Px: 859.94 | M: 859.4 | B: 862.9 / S: 855.9 | delta: -1.0540(-0.0260) | Adj: +5.53%, Vol: 1.00, Thr: 0.1581 | PnL: 6.67 | TotPnL: 6.67
|
||||
1766994570035, 2025-12-29 08:49:30,035 - [IDLE] ETH | Px: 3017.85 | M: 3017.6 | B: 3031.9 / S: 3003.3 | delta: -0.2176(-0.0006) | Adj: -2.47%, Vol: 1.00, Thr: 0.0326 | PnL: -6.32 | TotPnL: -6.32
|
||||
1766994570037, 2025-12-29 08:49:30,037 - [IDLE] BNB | Px: 859.96 | M: 859.4 | B: 862.9 / S: 855.9 | delta: -1.0524(-0.0244) | Adj: +5.51%, Vol: 1.00, Thr: 0.1579 | PnL: 6.69 | TotPnL: 6.69
|
||||
1766994630865, 2025-12-29 08:50:30,865 - [IDLE] ETH | Px: 3019.45 | M: 3020.8 | B: 3034.9 / S: 3006.7 | delta: -0.2139(+0.0031) | Adj: -2.55%, Vol: 1.00, Thr: 0.0321 | PnL: -6.73 | TotPnL: -6.73
|
||||
1766994630868, 2025-12-29 08:50:30,868 - [IDLE] BNB | Px: 860.41 | M: 860.4 | B: 863.8 / S: 857.0 | delta: -1.0286(-0.0006) | Adj: +5.25%, Vol: 1.00, Thr: 0.1543 | PnL: 6.25 | TotPnL: 6.25
|
||||
1766994690131, 2025-12-29 08:51:30,131 - [IDLE] ETH | Px: 3019.35 | M: 3020.6 | B: 3034.7 / S: 3006.5 | delta: -0.2141(+0.0029) | Adj: -2.54%, Vol: 1.00, Thr: 0.0321 | PnL: -6.71 | TotPnL: -6.71
|
||||
1766994690133, 2025-12-29 08:51:30,133 - [IDLE] BNB | Px: 860.76 | M: 861.1 | B: 864.5 / S: 857.8 | delta: -1.0102(+0.0178) | Adj: +5.05%, Vol: 1.00, Thr: 0.1515 | PnL: 5.83 | TotPnL: 5.83
|
||||
1766994810521, 2025-12-29 08:53:30,521 - [IDLE] ETH | Px: 3018.35 | M: 3018.6 | B: 3032.8 / S: 3004.3 | delta: -0.2165(+0.0005) | Adj: -2.49%, Vol: 1.12, Thr: 0.0325 | PnL: -6.47 | TotPnL: -6.47
|
||||
1766994810524, 2025-12-29 08:53:30,524 - [IDLE] BNB | Px: 860.68 | M: 861.0 | B: 864.4 / S: 857.6 | delta: -1.0139(+0.0141) | Adj: +5.09%, Vol: 1.00, Thr: 0.1521 | PnL: 5.98 | TotPnL: 5.98
|
||||
1766994840536, 2025-12-29 08:54:00,536 - [IDLE] ETH | Px: 3018.95 | M: 3019.8 | B: 3034.0 / S: 3005.6 | delta: -0.2151(+0.0019) | Adj: -2.52%, Vol: 1.12, Thr: 0.0323 | PnL: -6.58 | TotPnL: -6.58
|
||||
1766994840539, 2025-12-29 08:54:00,539 - [IDLE] BNB | Px: 861.02 | M: 861.7 | B: 865.0 / S: 858.4 | delta: -0.9959(+0.0321) | Adj: +4.89%, Vol: 1.00, Thr: 0.1494 | PnL: 5.58 | TotPnL: 5.58
|
||||
1766994930068, 2025-12-29 08:55:30,068 - [IDLE] ETH | Px: 3019.45 | M: 3020.8 | B: 3034.9 / S: 3006.7 | delta: -0.2139(+0.0031) | Adj: -2.55%, Vol: 1.02, Thr: 0.0321 | PnL: -6.69 | TotPnL: -6.69
|
||||
1766994930071, 2025-12-29 08:55:30,071 - [IDLE] BNB | Px: 860.94 | M: 861.6 | B: 864.9 / S: 858.2 | delta: -1.0001(+0.0279) | Adj: +4.94%, Vol: 1.00, Thr: 0.1500 | PnL: 5.61 | TotPnL: 5.61
|
||||
1766995050477, 2025-12-29 08:57:30,477 - [IDLE] ETH | Px: 3018.15 | M: 3018.2 | B: 3032.5 / S: 3003.9 | delta: -0.2169(+0.0001) | Adj: -2.48%, Vol: 1.00, Thr: 0.0325 | PnL: -6.45 | TotPnL: -6.45
|
||||
1766995050480, 2025-12-29 08:57:30,480 - [IDLE] BNB | Px: 860.58 | M: 860.8 | B: 864.1 / S: 857.4 | delta: -1.0198(+0.0082) | Adj: +5.15%, Vol: 1.00, Thr: 0.1530 | PnL: 6.04 | TotPnL: 6.04
|
||||
1766995140979, 2025-12-29 08:59:00,979 - [IDLE] ETH | Px: 3018.15 | M: 3018.2 | B: 3032.5 / S: 3003.9 | delta: -0.2169(+0.0001) | Adj: -2.48%, Vol: 1.00, Thr: 0.0325 | PnL: -6.40 | TotPnL: -6.40
|
||||
1766995140982, 2025-12-29 08:59:00,982 - [IDLE] BNB | Px: 860.26 | M: 860.1 | B: 863.5 / S: 856.6 | delta: -1.0363(-0.0083) | Adj: +5.33%, Vol: 1.00, Thr: 0.1554 | PnL: 6.28 | TotPnL: 6.28
|
||||
1766995230629, 2025-12-29 09:00:30,629 - [IDLE] ETH | Px: 3018.85 | M: 3019.6 | B: 3033.8 / S: 3005.4 | delta: -0.2153(+0.0017) | Adj: -2.52%, Vol: 1.00, Thr: 0.0323 | PnL: -6.55 | TotPnL: -6.55
|
||||
1766995230632, 2025-12-29 09:00:30,632 - [IDLE] BNB | Px: 860.52 | M: 860.6 | B: 864.0 / S: 857.3 | delta: -1.0224(+0.0056) | Adj: +5.18%, Vol: 1.00, Thr: 0.1534 | PnL: 6.12 | TotPnL: 6.12
|
||||
1766995320013, 2025-12-29 09:02:00,013 - [IDLE] ETH | Px: 3017.15 | M: 3016.2 | B: 3030.6 / S: 3001.7 | delta: -0.2193(-0.0023) | Adj: -2.43%, Vol: 1.00, Thr: 0.0329 | PnL: -6.25 | TotPnL: -6.25
|
||||
1766995320015, 2025-12-29 09:02:00,015 - [IDLE] BNB | Px: 860.46 | M: 860.5 | B: 863.9 / S: 857.1 | delta: -1.0256(+0.0024) | Adj: +5.22%, Vol: 1.00, Thr: 0.1538 | PnL: 6.14 | TotPnL: 6.14
|
||||
1766995350344, 2025-12-29 09:02:30,344 - [IDLE] ETH | Px: 3017.85 | M: 3017.6 | B: 3031.9 / S: 3003.3 | delta: -0.2176(-0.0006) | Adj: -2.47%, Vol: 1.00, Thr: 0.0326 | PnL: -6.38 | TotPnL: -6.38
|
||||
1766995350346, 2025-12-29 09:02:30,346 - [IDLE] BNB | Px: 860.80 | M: 861.2 | B: 864.6 / S: 857.9 | delta: -1.0081(+0.0199) | Adj: +5.03%, Vol: 1.00, Thr: 0.1512 | PnL: 5.88 | TotPnL: 5.88
|
||||
1766995380454, 2025-12-29 09:03:00,454 - [IDLE] ETH | Px: 3017.65 | M: 3017.2 | B: 3031.5 / S: 3002.8 | delta: -0.2181(-0.0011) | Adj: -2.46%, Vol: 1.00, Thr: 0.0327 | PnL: -6.32 | TotPnL: -6.32
|
||||
1766995380456, 2025-12-29 09:03:00,456 - [IDLE] BNB | Px: 860.70 | M: 861.0 | B: 864.4 / S: 857.7 | delta: -1.0131(+0.0149) | Adj: +5.08%, Vol: 1.00, Thr: 0.1520 | PnL: 5.93 | TotPnL: 5.93
|
||||
1766995410053, 2025-12-29 09:03:30,053 - [IDLE] ETH | Px: 3016.65 | M: 3015.1 | B: 3029.6 / S: 3000.6 | delta: -0.2204(-0.0034) | Adj: -2.40%, Vol: 1.00, Thr: 0.0331 | PnL: -6.14 | TotPnL: -6.14
|
||||
1766995410055, 2025-12-29 09:03:30,055 - [IDLE] BNB | Px: 860.74 | M: 861.1 | B: 864.5 / S: 857.8 | delta: -1.0113(+0.0167) | Adj: +5.06%, Vol: 1.00, Thr: 0.1517 | PnL: 5.90 | TotPnL: 5.90
|
||||
1766995620386, 2025-12-29 09:07:00,386 - [IDLE] ETH | Px: 3012.65 | M: 3007.0 | B: 3022.1 / S: 2991.9 | delta: -0.2298(-0.0128) | Adj: -2.20%, Vol: 1.68, Thr: 0.0345 | PnL: -5.30 | TotPnL: -5.30
|
||||
1766995620388, 2025-12-29 09:07:00,388 - [IDLE] BNB | Px: 860.58 | M: 860.8 | B: 864.2 / S: 857.4 | delta: -1.0192(+0.0088) | Adj: +5.15%, Vol: 1.00, Thr: 0.1529 | PnL: 6.04 | TotPnL: 6.04
|
||||
1766995650848, 2025-12-29 09:07:30,848 - [IDLE] ETH | Px: 3012.75 | M: 3007.2 | B: 3022.3 / S: 2992.2 | delta: -0.2296(-0.0126) | Adj: -2.20%, Vol: 1.72, Thr: 0.0344 | PnL: -5.27 | TotPnL: -5.27
|
||||
1766995650850, 2025-12-29 09:07:30,850 - [IDLE] BNB | Px: 860.58 | M: 860.8 | B: 864.2 / S: 857.4 | delta: -1.0192(+0.0088) | Adj: +5.15%, Vol: 1.00, Thr: 0.1529 | PnL: 6.05 | TotPnL: 6.05
|
||||
1766995710887, 2025-12-29 09:08:30,887 - [IDLE] ETH | Px: 3014.15 | M: 3010.1 | B: 3024.9 / S: 2995.2 | delta: -0.2263(-0.0093) | Adj: -2.28%, Vol: 1.67, Thr: 0.0339 | PnL: -5.69 | TotPnL: -5.69
|
||||
1766995710890, 2025-12-29 09:08:30,890 - [IDLE] BNB | Px: 860.68 | M: 861.0 | B: 864.4 / S: 857.6 | delta: -1.0139(+0.0141) | Adj: +5.09%, Vol: 1.00, Thr: 0.1521 | PnL: 5.88 | TotPnL: 5.88
|
||||
1766995770299, 2025-12-29 09:09:30,299 - [IDLE] ETH | Px: 3014.45 | M: 3010.7 | B: 3025.5 / S: 2995.9 | delta: -0.2256(-0.0086) | Adj: -2.29%, Vol: 1.54, Thr: 0.0338 | PnL: -5.64 | TotPnL: -5.64
|
||||
1766995770300, 2025-12-29 09:09:30,300 - [IDLE] BNB | Px: 860.89 | M: 861.4 | B: 864.8 / S: 858.1 | delta: -1.0030(+0.0250) | Adj: +4.97%, Vol: 1.00, Thr: 0.1505 | PnL: 5.68 | TotPnL: 5.68
|
||||
1766995800729, 2025-12-29 09:10:00,729 - [IDLE] ETH | Px: 3013.75 | M: 3009.3 | B: 3024.2 / S: 2994.3 | delta: -0.2273(-0.0103) | Adj: -2.26%, Vol: 1.48, Thr: 0.0341 | PnL: -5.47 | TotPnL: -5.47
|
||||
1766995800731, 2025-12-29 09:10:00,731 - [IDLE] BNB | Px: 860.82 | M: 861.3 | B: 864.6 / S: 858.0 | delta: -1.0067(+0.0213) | Adj: +5.01%, Vol: 1.00, Thr: 0.1510 | PnL: 5.83 | TotPnL: 5.83
|
||||
1766995830973, 2025-12-29 09:10:30,973 - [IDLE] ETH | Px: 3018.65 | M: 3019.2 | B: 3033.4 / S: 3005.0 | delta: -0.2158(+0.0012) | Adj: -2.51%, Vol: 1.42, Thr: 0.0324 | PnL: -6.42 | TotPnL: -6.42
|
||||
1766995830974, 2025-12-29 09:10:30,974 - [IDLE] BNB | Px: 861.12 | M: 862.0 | B: 865.2 / S: 858.7 | delta: -0.9906(+0.0374) | Adj: +4.83%, Vol: 1.00, Thr: 0.1486 | PnL: 5.47 | TotPnL: 5.47
|
||||
1766995950242, 2025-12-29 09:12:30,242 - [IDLE] ETH | Px: 3019.75 | M: 3021.4 | B: 3035.5 / S: 3007.4 | delta: -0.2132(+0.0038) | Adj: -2.56%, Vol: 1.86, Thr: 0.0320 | PnL: -6.79 | TotPnL: -6.79
|
||||
1766995950245, 2025-12-29 09:12:30,245 - [IDLE] BNB | Px: 860.16 | M: 859.8 | B: 863.3 / S: 856.4 | delta: -1.0422(-0.0142) | Adj: +5.40%, Vol: 1.00, Thr: 0.1563 | PnL: 6.49 | TotPnL: 6.49
|
||||
1766995980079, 2025-12-29 09:13:00,079 - [IDLE] ETH | Px: 3019.75 | M: 3021.4 | B: 3035.5 / S: 3007.4 | delta: -0.2132(+0.0038) | Adj: -2.56%, Vol: 1.95, Thr: 0.0320 | PnL: -6.79 | TotPnL: -6.79
|
||||
1766995980080, 2025-12-29 09:13:00,080 - [IDLE] BNB | Px: 860.04 | M: 859.6 | B: 863.1 / S: 856.1 | delta: -1.0486(-0.0206) | Adj: +5.47%, Vol: 1.00, Thr: 0.1573 | PnL: 6.60 | TotPnL: 6.60
|
||||
1766996070110, 2025-12-29 09:14:30,110 - [IDLE] ETH | Px: 3020.15 | M: 3022.2 | B: 3036.2 / S: 3008.2 | delta: -0.2123(+0.0047) | Adj: -2.59%, Vol: 2.20, Thr: 0.0318 | PnL: -6.88 | TotPnL: -6.88
|
||||
1766996070112, 2025-12-29 09:14:30,112 - [IDLE] BNB | Px: 860.10 | M: 859.7 | B: 863.2 / S: 856.2 | delta: -1.0454(-0.0174) | Adj: +5.43%, Vol: 1.00, Thr: 0.1568 | PnL: 6.57 | TotPnL: 6.57
|
||||
1766996100944, 2025-12-29 09:15:00,944 - [IDLE] ETH | Px: 3020.65 | M: 3023.2 | B: 3037.2 / S: 3009.3 | delta: -0.2111(+0.0059) | Adj: -2.61%, Vol: 2.22, Thr: 0.0317 | PnL: -6.99 | TotPnL: -6.99
|
||||
1766996100946, 2025-12-29 09:15:00,946 - [IDLE] BNB | Px: 860.23 | M: 860.0 | B: 863.4 / S: 856.6 | delta: -1.0382(-0.0102) | Adj: +5.36%, Vol: 1.00, Thr: 0.1557 | PnL: 6.46 | TotPnL: 6.46
|
||||
1766996130074, 2025-12-29 09:15:30,074 - [IDLE] ETH | Px: 3020.25 | M: 3022.4 | B: 3036.4 / S: 3008.5 | delta: -0.2120(+0.0050) | Adj: -2.59%, Vol: 2.22, Thr: 0.0318 | PnL: -6.90 | TotPnL: -6.90
|
||||
1766996130076, 2025-12-29 09:15:30,076 - [IDLE] BNB | Px: 859.84 | M: 859.2 | B: 862.7 / S: 855.7 | delta: -1.0588(-0.0308) | Adj: +5.58%, Vol: 1.00, Thr: 0.1588 | PnL: 6.80 | TotPnL: 6.80
|
||||
1766996160340, 2025-12-29 09:16:00,340 - [IDLE] ETH | Px: 3020.25 | M: 3022.4 | B: 3036.4 / S: 3008.5 | delta: -0.2120(+0.0050) | Adj: -2.59%, Vol: 2.10, Thr: 0.0318 | PnL: -6.90 | TotPnL: -6.90
|
||||
1766996160341, 2025-12-29 09:16:00,341 - [IDLE] BNB | Px: 859.64 | M: 858.7 | B: 862.3 / S: 855.2 | delta: -1.0695(-0.0415) | Adj: +5.70%, Vol: 1.06, Thr: 0.1604 | PnL: 7.00 | TotPnL: 7.00
|
||||
1766996190769, 2025-12-29 09:16:30,769 - [IDLE] ETH | Px: 3019.95 | M: 3021.8 | B: 3035.8 / S: 3007.8 | delta: -0.2127(+0.0043) | Adj: -2.57%, Vol: 1.94, Thr: 0.0319 | PnL: -6.86 | TotPnL: -6.86
|
||||
1766996190770, 2025-12-29 09:16:30,770 - [IDLE] BNB | Px: 859.60 | M: 858.6 | B: 862.2 / S: 855.1 | delta: -1.0717(-0.0437) | Adj: +5.72%, Vol: 1.16, Thr: 0.1608 | PnL: 7.05 | TotPnL: 7.05
|
||||
1766996220920, 2025-12-29 09:17:00,920 - [IDLE] ETH | Px: 3020.25 | M: 3022.4 | B: 3036.4 / S: 3008.5 | delta: -0.2120(+0.0050) | Adj: -2.59%, Vol: 1.79, Thr: 0.0318 | PnL: -6.90 | TotPnL: -6.90
|
||||
1766996220922, 2025-12-29 09:17:00,922 - [IDLE] BNB | Px: 859.64 | M: 858.7 | B: 862.3 / S: 855.2 | delta: -1.0695(-0.0415) | Adj: +5.70%, Vol: 1.25, Thr: 0.1604 | PnL: 7.01 | TotPnL: 7.01
|
||||
1766996310636, 2025-12-29 09:18:30,636 - [IDLE] ETH | Px: 3020.65 | M: 3023.2 | B: 3037.2 / S: 3009.3 | delta: -0.2111(+0.0059) | Adj: -2.61%, Vol: 1.37, Thr: 0.0317 | PnL: -6.90 | TotPnL: -6.90
|
||||
1766996310638, 2025-12-29 09:18:30,638 - [IDLE] BNB | Px: 859.10 | M: 857.5 | B: 861.2 / S: 853.9 | delta: -1.0992(-0.0712) | Adj: +6.02%, Vol: 1.38, Thr: 0.1649 | PnL: 7.67 | TotPnL: 7.67
|
||||
1766996340488, 2025-12-29 09:19:00,488 - [IDLE] ETH | Px: 3020.95 | M: 3023.8 | B: 3037.7 / S: 3010.0 | delta: -0.2104(+0.0066) | Adj: -2.63%, Vol: 1.19, Thr: 0.0316 | PnL: -7.05 | TotPnL: -7.05
|
||||
1766996340490, 2025-12-29 09:19:00,490 - [IDLE] BNB | Px: 859.16 | M: 857.7 | B: 861.3 / S: 854.0 | delta: -1.0960(-0.0680) | Adj: +5.98%, Vol: 1.46, Thr: 0.1644 | PnL: 7.62 | TotPnL: 7.62
|
||||
1766996370562, 2025-12-29 09:19:30,562 - [IDLE] ETH | Px: 3021.45 | M: 3024.9 | B: 3038.7 / S: 3011.1 | delta: -0.2092(+0.0078) | Adj: -2.65%, Vol: 1.00, Thr: 0.0314 | PnL: -7.16 | TotPnL: -7.16
|
||||
1766996370564, 2025-12-29 09:19:30,564 - [IDLE] BNB | Px: 859.68 | M: 858.8 | B: 862.3 / S: 855.3 | delta: -1.0679(-0.0399) | Adj: +5.68%, Vol: 1.47, Thr: 0.1602 | PnL: 7.01 | TotPnL: 7.01
|
||||
1766996400592, 2025-12-29 09:20:00,592 - [IDLE] ETH | Px: 3020.95 | M: 3023.8 | B: 3037.7 / S: 3010.0 | delta: -0.2104(+0.0066) | Adj: -2.63%, Vol: 1.00, Thr: 0.0316 | PnL: -7.16 | TotPnL: -7.16
|
||||
1766996400594, 2025-12-29 09:20:00,594 - [IDLE] BNB | Px: 859.59 | M: 858.6 | B: 862.2 / S: 855.1 | delta: -1.0725(-0.0445) | Adj: +5.73%, Vol: 1.42, Thr: 0.1609 | PnL: 7.04 | TotPnL: 7.04
|
||||
1766996430133, 2025-12-29 09:20:30,133 - [IDLE] ETH | Px: 3020.75 | M: 3023.4 | B: 3037.3 / S: 3009.5 | delta: -0.2109(+0.0061) | Adj: -2.62%, Vol: 1.00, Thr: 0.0316 | PnL: -7.01 | TotPnL: -7.01
|
||||
1766996430134, 2025-12-29 09:20:30,134 - [IDLE] BNB | Px: 859.48 | M: 858.4 | B: 861.9 / S: 854.8 | delta: -1.0784(-0.0504) | Adj: +5.79%, Vol: 1.28, Thr: 0.1618 | PnL: 7.11 | TotPnL: 7.11
|
||||
1766996640487, 2025-12-29 09:24:00,487 - [IDLE] ETH | Px: 3023.25 | M: 3028.5 | B: 3042.0 / S: 3015.0 | delta: -0.2051(+0.0119) | Adj: -2.74%, Vol: 1.00, Thr: 0.0308 | PnL: -7.53 | TotPnL: -7.53
|
||||
1766996640488, 2025-12-29 09:24:00,488 - [IDLE] BNB | Px: 859.46 | M: 858.3 | B: 861.9 / S: 854.8 | delta: -1.0792(-0.0512) | Adj: +5.80%, Vol: 1.00, Thr: 0.1619 | PnL: 7.32 | TotPnL: 7.32
|
||||
1766996700034, 2025-12-29 09:25:00,034 - [IDLE] ETH | Px: 3022.85 | M: 3027.7 | B: 3041.3 / S: 3014.1 | delta: -0.2060(+0.0110) | Adj: -2.72%, Vol: 1.00, Thr: 0.0309 | PnL: -7.42 | TotPnL: -7.42
|
||||
1766996700037, 2025-12-29 09:25:00,037 - [IDLE] BNB | Px: 859.68 | M: 858.8 | B: 862.3 / S: 855.3 | delta: -1.0679(-0.0399) | Adj: +5.68%, Vol: 1.00, Thr: 0.1602 | PnL: 7.06 | TotPnL: 7.06
|
||||
1766996760224, 2025-12-29 09:26:00,224 - [IDLE] ETH | Px: 3023.15 | M: 3028.3 | B: 3041.8 / S: 3014.8 | delta: -0.2053(+0.0117) | Adj: -2.74%, Vol: 1.00, Thr: 0.0308 | PnL: -7.53 | TotPnL: -7.53
|
||||
1766996760226, 2025-12-29 09:26:00,226 - [IDLE] BNB | Px: 860.21 | M: 860.0 | B: 863.4 / S: 856.5 | delta: -1.0392(-0.0112) | Adj: +5.37%, Vol: 1.00, Thr: 0.1559 | PnL: 6.46 | TotPnL: 6.46
|
||||
1766996820255, 2025-12-29 09:27:00,255 - [IDLE] ETH | Px: 3023.35 | M: 3028.7 | B: 3042.2 / S: 3015.2 | delta: -0.2048(+0.0122) | Adj: -2.75%, Vol: 1.00, Thr: 0.0307 | PnL: -7.53 | TotPnL: -7.53
|
||||
1766996820258, 2025-12-29 09:27:00,258 - [IDLE] BNB | Px: 860.16 | M: 859.8 | B: 863.3 / S: 856.4 | delta: -1.0422(-0.0142) | Adj: +5.40%, Vol: 1.00, Thr: 0.1563 | PnL: 6.49 | TotPnL: 6.49
|
||||
1766996880676, 2025-12-29 09:28:00,676 - [IDLE] ETH | Px: 3021.75 | M: 3025.5 | B: 3039.2 / S: 3011.7 | delta: -0.2085(+0.0085) | Adj: -2.67%, Vol: 1.00, Thr: 0.0313 | PnL: -7.25 | TotPnL: -7.25
|
||||
1766996880678, 2025-12-29 09:28:00,678 - [IDLE] BNB | Px: 859.72 | M: 858.9 | B: 862.4 / S: 855.4 | delta: -1.0652(-0.0372) | Adj: +5.65%, Vol: 1.00, Thr: 0.1598 | PnL: 7.03 | TotPnL: 7.03
|
||||
1766996970374, 2025-12-29 09:29:30,374 - [IDLE] ETH | Px: 3021.65 | M: 3025.3 | B: 3039.0 / S: 3011.5 | delta: -0.2088(+0.0082) | Adj: -2.66%, Vol: 1.00, Thr: 0.0313 | PnL: -7.21 | TotPnL: -7.21
|
||||
1766996970377, 2025-12-29 09:29:30,377 - [IDLE] BNB | Px: 859.76 | M: 859.0 | B: 862.5 / S: 855.5 | delta: -1.0631(-0.0351) | Adj: +5.63%, Vol: 1.00, Thr: 0.1595 | PnL: 6.97 | TotPnL: 6.97
|
||||
1766997000939, 2025-12-29 09:30:00,939 - [IDLE] ETH | Px: 3021.65 | M: 3025.3 | B: 3039.0 / S: 3011.5 | delta: -0.2088(+0.0082) | Adj: -2.66%, Vol: 1.00, Thr: 0.0313 | PnL: -7.21 | TotPnL: -7.21
|
||||
1766997000941, 2025-12-29 09:30:00,941 - [IDLE] BNB | Px: 859.76 | M: 859.0 | B: 862.5 / S: 855.5 | delta: -1.0631(-0.0351) | Adj: +5.63%, Vol: 1.00, Thr: 0.1595 | PnL: 6.96 | TotPnL: 6.96
|
||||
1766997030689, 2025-12-29 09:30:30,689 - [IDLE] ETH | Px: 3020.95 | M: 3023.8 | B: 3037.7 / S: 3010.0 | delta: -0.2104(+0.0066) | Adj: -2.63%, Vol: 1.00, Thr: 0.0316 | PnL: -7.01 | TotPnL: -7.01
|
||||
1766997030691, 2025-12-29 09:30:30,691 - [IDLE] BNB | Px: 859.70 | M: 858.9 | B: 862.4 / S: 855.3 | delta: -1.0663(-0.0383) | Adj: +5.66%, Vol: 1.00, Thr: 0.1599 | PnL: 6.95 | TotPnL: 6.95
|
||||
1766997060839, 2025-12-29 09:31:00,839 - [IDLE] ETH | Px: 3021.15 | M: 3024.3 | B: 3038.1 / S: 3010.4 | delta: -0.2099(+0.0071) | Adj: -2.64%, Vol: 1.00, Thr: 0.0315 | PnL: -7.10 | TotPnL: -7.10
|
||||
1766997060841, 2025-12-29 09:31:00,841 - [IDLE] BNB | Px: 859.88 | M: 859.2 | B: 862.7 / S: 855.7 | delta: -1.0572(-0.0292) | Adj: +5.56%, Vol: 1.00, Thr: 0.1586 | PnL: 6.79 | TotPnL: 6.79
|
||||
1766997150142, 2025-12-29 09:32:30,142 - [IDLE] ETH | Px: 3022.45 | M: 3026.9 | B: 3040.5 / S: 3013.2 | delta: -0.2069(+0.0101) | Adj: -2.70%, Vol: 1.00, Thr: 0.0310 | PnL: -7.36 | TotPnL: -7.36
|
||||
1766997150144, 2025-12-29 09:32:30,144 - [IDLE] BNB | Px: 860.04 | M: 859.6 | B: 863.1 / S: 856.1 | delta: -1.0486(-0.0206) | Adj: +5.47%, Vol: 1.00, Thr: 0.1573 | PnL: 6.63 | TotPnL: 6.63
|
||||
1766997240700, 2025-12-29 09:34:00,700 - [IDLE] ETH | Px: 3020.65 | M: 3023.2 | B: 3037.2 / S: 3009.3 | delta: -0.2111(+0.0059) | Adj: -2.61%, Vol: 1.00, Thr: 0.0317 | PnL: -6.92 | TotPnL: -6.92
|
||||
1766997240701, 2025-12-29 09:34:00,701 - [IDLE] BNB | Px: 859.84 | M: 859.1 | B: 862.7 / S: 855.6 | delta: -1.0593(-0.0313) | Adj: +5.59%, Vol: 1.00, Thr: 0.1589 | PnL: 6.84 | TotPnL: 6.84
|
||||
1766997330343, 2025-12-29 09:35:30,343 - [IDLE] ETH | Px: 3021.65 | M: 3025.3 | B: 3039.0 / S: 3011.5 | delta: -0.2088(+0.0082) | Adj: -2.66%, Vol: 1.00, Thr: 0.0313 | PnL: -7.16 | TotPnL: -7.16
|
||||
1766997330345, 2025-12-29 09:35:30,345 - [IDLE] BNB | Px: 860.02 | M: 859.6 | B: 863.0 / S: 856.1 | delta: -1.0491(-0.0211) | Adj: +5.47%, Vol: 1.00, Thr: 0.1574 | PnL: 6.64 | TotPnL: 6.64
|
||||
1766997360144, 2025-12-29 09:36:00,144 - [IDLE] ETH | Px: 3021.25 | M: 3024.5 | B: 3038.3 / S: 3010.6 | delta: -0.2097(+0.0073) | Adj: -2.64%, Vol: 1.00, Thr: 0.0315 | PnL: -7.10 | TotPnL: -7.10
|
||||
1766997360145, 2025-12-29 09:36:00,145 - [IDLE] BNB | Px: 860.02 | M: 859.6 | B: 863.0 / S: 856.1 | delta: -1.0491(-0.0211) | Adj: +5.47%, Vol: 1.00, Thr: 0.1574 | PnL: 6.65 | TotPnL: 6.65
|
||||
1766997390410, 2025-12-29 09:36:30,410 - [IDLE] ETH | Px: 3021.55 | M: 3025.1 | B: 3038.8 / S: 3011.3 | delta: -0.2090(+0.0080) | Adj: -2.66%, Vol: 1.00, Thr: 0.0314 | PnL: -7.16 | TotPnL: -7.16
|
||||
1766997390413, 2025-12-29 09:36:30,413 - [IDLE] BNB | Px: 860.02 | M: 859.6 | B: 863.0 / S: 856.1 | delta: -1.0491(-0.0211) | Adj: +5.47%, Vol: 1.00, Thr: 0.1574 | PnL: 6.64 | TotPnL: 6.64
|
||||
1766997420169, 2025-12-29 09:37:00,169 - [IDLE] ETH | Px: 3020.25 | M: 3022.4 | B: 3036.4 / S: 3008.5 | delta: -0.2120(+0.0050) | Adj: -2.59%, Vol: 1.00, Thr: 0.0318 | PnL: -6.86 | TotPnL: -6.86
|
||||
1766997420171, 2025-12-29 09:37:00,171 - [IDLE] BNB | Px: 860.02 | M: 859.6 | B: 863.0 / S: 856.1 | delta: -1.0491(-0.0211) | Adj: +5.47%, Vol: 1.00, Thr: 0.1574 | PnL: 6.63 | TotPnL: 6.63
|
||||
1766997450016, 2025-12-29 09:37:30,016 - [IDLE] ETH | Px: 3018.75 | M: 3019.4 | B: 3033.6 / S: 3005.2 | delta: -0.2155(+0.0015) | Adj: -2.51%, Vol: 1.00, Thr: 0.0323 | PnL: -6.55 | TotPnL: -6.55
|
||||
1766997450019, 2025-12-29 09:37:30,019 - [IDLE] BNB | Px: 859.82 | M: 859.1 | B: 862.6 / S: 855.6 | delta: -1.0599(-0.0319) | Adj: +5.59%, Vol: 1.00, Thr: 0.1590 | PnL: 6.89 | TotPnL: 6.89
|
||||
1766997480749, 2025-12-29 09:38:00,749 - [IDLE] ETH | Px: 3018.35 | M: 3018.6 | B: 3032.8 / S: 3004.3 | delta: -0.2165(+0.0005) | Adj: -2.49%, Vol: 1.00, Thr: 0.0325 | PnL: -6.47 | TotPnL: -6.47
|
||||
1766997480752, 2025-12-29 09:38:00,752 - [IDLE] BNB | Px: 859.82 | M: 859.1 | B: 862.6 / S: 855.6 | delta: -1.0599(-0.0319) | Adj: +5.59%, Vol: 1.00, Thr: 0.1590 | PnL: 6.88 | TotPnL: 6.88
|
||||
1766997510812, 2025-12-29 09:38:30,812 - [IDLE] ETH | Px: 3017.85 | M: 3017.6 | B: 3031.9 / S: 3003.3 | delta: -0.2176(-0.0006) | Adj: -2.47%, Vol: 1.00, Thr: 0.0326 | PnL: -6.36 | TotPnL: -6.36
|
||||
1766997510814, 2025-12-29 09:38:30,814 - [IDLE] BNB | Px: 859.82 | M: 859.1 | B: 862.6 / S: 855.6 | delta: -1.0599(-0.0319) | Adj: +5.59%, Vol: 1.00, Thr: 0.1590 | PnL: 6.86 | TotPnL: 6.86
|
||||
1766997570733, 2025-12-29 09:39:30,733 - [IDLE] ETH | Px: 3015.05 | M: 3011.9 | B: 3026.6 / S: 2997.2 | delta: -0.2242(-0.0072) | Adj: -2.32%, Vol: 1.11, Thr: 0.0336 | PnL: -5.75 | TotPnL: -5.75
|
||||
1766997570735, 2025-12-29 09:39:30,735 - [IDLE] BNB | Px: 859.02 | M: 857.3 | B: 861.0 / S: 853.7 | delta: -1.1036(-0.0756) | Adj: +6.06%, Vol: 1.00, Thr: 0.1655 | PnL: 7.66 | TotPnL: 7.66
|
||||
1766997810733, 2025-12-29 09:43:30,733 - [IDLE] ETH | Px: 3017.65 | M: 3017.2 | B: 3031.5 / S: 3002.8 | delta: -0.2181(-0.0011) | Adj: -2.46%, Vol: 1.26, Thr: 0.0327 | PnL: -6.29 | TotPnL: -6.29
|
||||
1766997810735, 2025-12-29 09:43:30,735 - [IDLE] BNB | Px: 859.44 | M: 858.3 | B: 861.8 / S: 854.7 | delta: -1.0809(-0.0529) | Adj: +5.82%, Vol: 1.00, Thr: 0.1621 | PnL: 7.26 | TotPnL: 7.26
|
||||
1766997870398, 2025-12-29 09:44:30,398 - [IDLE] ETH | Px: 3017.85 | M: 3017.6 | B: 3031.9 / S: 3003.3 | delta: -0.2176(-0.0006) | Adj: -2.47%, Vol: 1.20, Thr: 0.0326 | PnL: -6.40 | TotPnL: -6.40
|
||||
1766997870401, 2025-12-29 09:44:30,401 - [IDLE] BNB | Px: 859.44 | M: 858.3 | B: 861.8 / S: 854.7 | delta: -1.0809(-0.0529) | Adj: +5.82%, Vol: 1.00, Thr: 0.1621 | PnL: 7.22 | TotPnL: 7.22
|
||||
1766997930460, 2025-12-29 09:45:30,460 - [IDLE] ETH | Px: 3017.45 | M: 3016.8 | B: 3031.1 / S: 3002.4 | delta: -0.2186(-0.0016) | Adj: -2.45%, Vol: 1.00, Thr: 0.0328 | PnL: -6.29 | TotPnL: -6.29
|
||||
1766997930462, 2025-12-29 09:45:30,462 - [IDLE] BNB | Px: 859.84 | M: 859.2 | B: 862.7 / S: 855.7 | delta: -1.0588(-0.0308) | Adj: +5.58%, Vol: 1.00, Thr: 0.1588 | PnL: 6.80 | TotPnL: 6.80
|
||||
1766997960749, 2025-12-29 09:46:00,749 - [IDLE] ETH | Px: 3017.35 | M: 3016.6 | B: 3031.0 / S: 3002.2 | delta: -0.2188(-0.0018) | Adj: -2.44%, Vol: 1.00, Thr: 0.0328 | PnL: -6.27 | TotPnL: -6.27
|
||||
1766997960751, 2025-12-29 09:46:00,751 - [IDLE] BNB | Px: 859.90 | M: 859.3 | B: 862.8 / S: 855.8 | delta: -1.0558(-0.0278) | Adj: +5.55%, Vol: 1.00, Thr: 0.1584 | PnL: 6.72 | TotPnL: 6.72
|
||||
1766997990732, 2025-12-29 09:46:30,732 - [IDLE] ETH | Px: 3016.35 | M: 3014.5 | B: 3029.1 / S: 3000.0 | delta: -0.2211(-0.0041) | Adj: -2.39%, Vol: 1.00, Thr: 0.0332 | PnL: -6.03 | TotPnL: -6.03
|
||||
1766997990734, 2025-12-29 09:46:30,734 - [IDLE] BNB | Px: 859.70 | M: 858.9 | B: 862.4 / S: 855.3 | delta: -1.0663(-0.0383) | Adj: +5.66%, Vol: 1.00, Thr: 0.1599 | PnL: 6.95 | TotPnL: 6.95
|
||||
1766998020261, 2025-12-29 09:47:00,261 - [IDLE] ETH | Px: 3016.65 | M: 3015.1 | B: 3029.6 / S: 3000.6 | delta: -0.2204(-0.0034) | Adj: -2.40%, Vol: 1.00, Thr: 0.0331 | PnL: -6.12 | TotPnL: -6.12
|
||||
1766998020263, 2025-12-29 09:47:00,263 - [IDLE] BNB | Px: 859.60 | M: 858.6 | B: 862.2 / S: 855.1 | delta: -1.0717(-0.0437) | Adj: +5.72%, Vol: 1.00, Thr: 0.1608 | PnL: 6.99 | TotPnL: 6.99
|
||||
1766998080698, 2025-12-29 09:48:00,698 - [IDLE] ETH | Px: 3016.85 | M: 3015.5 | B: 3030.0 / S: 3001.1 | delta: -0.2200(-0.0030) | Adj: -2.42%, Vol: 1.00, Thr: 0.0330 | PnL: -6.19 | TotPnL: -6.19
|
||||
1766998080699, 2025-12-29 09:48:00,699 - [IDLE] BNB | Px: 859.68 | M: 858.8 | B: 862.3 / S: 855.3 | delta: -1.0674(-0.0394) | Adj: +5.67%, Vol: 1.00, Thr: 0.1601 | PnL: 6.97 | TotPnL: 6.97
|
||||
1766998110936, 2025-12-29 09:48:30,936 - [IDLE] ETH | Px: 3017.15 | M: 3016.2 | B: 3030.6 / S: 3001.7 | delta: -0.2193(-0.0023) | Adj: -2.43%, Vol: 1.00, Thr: 0.0329 | PnL: -6.23 | TotPnL: -6.23
|
||||
1766998110938, 2025-12-29 09:48:30,938 - [IDLE] BNB | Px: 859.92 | M: 859.3 | B: 862.8 / S: 855.8 | delta: -1.0550(-0.0270) | Adj: +5.54%, Vol: 1.00, Thr: 0.1583 | PnL: 6.66 | TotPnL: 6.66
|
||||
1766998200510, 2025-12-29 09:50:00,510 - [IDLE] ETH | Px: 3018.55 | M: 3019.0 | B: 3033.2 / S: 3004.8 | delta: -0.2160(+0.0010) | Adj: -2.50%, Vol: 1.00, Thr: 0.0324 | PnL: -6.45 | TotPnL: -6.45
|
||||
1766998200512, 2025-12-29 09:50:00,512 - [IDLE] BNB | Px: 860.27 | M: 860.1 | B: 863.5 / S: 856.7 | delta: -1.0360(-0.0080) | Adj: +5.33%, Vol: 1.00, Thr: 0.1554 | PnL: 6.39 | TotPnL: 6.39
|
||||
1766998230211, 2025-12-29 09:50:30,211 - [IDLE] ETH | Px: 3017.95 | M: 3017.8 | B: 3032.1 / S: 3003.5 | delta: -0.2174(-0.0004) | Adj: -2.47%, Vol: 1.00, Thr: 0.0326 | PnL: -6.40 | TotPnL: -6.40
|
||||
1766998230212, 2025-12-29 09:50:30,212 - [IDLE] BNB | Px: 860.10 | M: 859.7 | B: 863.2 / S: 856.3 | delta: -1.0449(-0.0169) | Adj: +5.43%, Vol: 1.00, Thr: 0.1567 | PnL: 6.53 | TotPnL: 6.53
|
||||
1766998260457, 2025-12-29 09:51:00,457 - [IDLE] ETH | Px: 3016.65 | M: 3015.1 | B: 3029.6 / S: 3000.6 | delta: -0.2204(-0.0034) | Adj: -2.40%, Vol: 1.00, Thr: 0.0331 | PnL: -6.12 | TotPnL: -6.12
|
||||
1766998260459, 2025-12-29 09:51:00,459 - [IDLE] BNB | Px: 859.94 | M: 859.4 | B: 862.9 / S: 855.9 | delta: -1.0534(-0.0254) | Adj: +5.52%, Vol: 1.00, Thr: 0.1580 | PnL: 6.79 | TotPnL: 6.79
|
||||
1766998290182, 2025-12-29 09:51:30,182 - [IDLE] ETH | Px: 3015.65 | M: 3013.1 | B: 3027.8 / S: 2998.5 | delta: -0.2228(-0.0058) | Adj: -2.35%, Vol: 1.00, Thr: 0.0334 | PnL: -5.90 | TotPnL: -5.90
|
||||
1766998290183, 2025-12-29 09:51:30,183 - [IDLE] BNB | Px: 859.72 | M: 858.9 | B: 862.4 / S: 855.4 | delta: -1.0652(-0.0372) | Adj: +5.65%, Vol: 1.00, Thr: 0.1598 | PnL: 6.99 | TotPnL: 6.99
|
||||
1766998320319, 2025-12-29 09:52:00,319 - [IDLE] ETH | Px: 3015.25 | M: 3012.3 | B: 3027.0 / S: 2997.6 | delta: -0.2237(-0.0067) | Adj: -2.33%, Vol: 1.00, Thr: 0.0336 | PnL: -5.88 | TotPnL: -5.88
|
||||
1766998320322, 2025-12-29 09:52:00,322 - [IDLE] BNB | Px: 859.61 | M: 858.7 | B: 862.2 / S: 855.1 | delta: -1.0714(-0.0434) | Adj: +5.72%, Vol: 1.00, Thr: 0.1607 | PnL: 7.01 | TotPnL: 7.01
|
||||
1766998350138, 2025-12-29 09:52:30,138 - [IDLE] ETH | Px: 3013.70 | M: 3009.2 | B: 3024.1 / S: 2994.2 | delta: -0.2274(-0.0104) | Adj: -2.25%, Vol: 1.00, Thr: 0.0341 | PnL: -5.47 | TotPnL: -5.47
|
||||
1766998350141, 2025-12-29 09:52:30,141 - [IDLE] BNB | Px: 859.30 | M: 858.0 | B: 861.6 / S: 854.4 | delta: -1.0884(-0.0604) | Adj: +5.90%, Vol: 1.00, Thr: 0.1633 | PnL: 7.42 | TotPnL: 7.42
|
||||
1766998410259, 2025-12-29 09:53:30,259 - [IDLE] ETH | Px: 3012.45 | M: 3006.6 | B: 3021.7 / S: 2991.5 | delta: -0.2303(-0.0133) | Adj: -2.19%, Vol: 1.00, Thr: 0.0345 | PnL: -5.17 | TotPnL: -5.17
|
||||
1766998410261, 2025-12-29 09:53:30,261 - [IDLE] BNB | Px: 859.10 | M: 857.5 | B: 861.2 / S: 853.9 | delta: -1.0987(-0.0707) | Adj: +6.01%, Vol: 1.00, Thr: 0.1648 | PnL: 7.67 | TotPnL: 7.67
|
||||
1766998440833, 2025-12-29 09:54:00,833 - [IDLE] ETH | Px: 3012.45 | M: 3006.6 | B: 3021.7 / S: 2991.5 | delta: -0.2303(-0.0133) | Adj: -2.19%, Vol: 1.11, Thr: 0.0345 | PnL: -5.25 | TotPnL: -5.25
|
||||
1766998440835, 2025-12-29 09:54:00,835 - [IDLE] BNB | Px: 859.04 | M: 857.4 | B: 861.1 / S: 853.8 | delta: -1.1019(-0.0739) | Adj: +6.05%, Vol: 1.00, Thr: 0.1653 | PnL: 7.71 | TotPnL: 7.71
|
||||
1766998500401, 2025-12-29 09:55:00,401 - [IDLE] ETH | Px: 3011.75 | M: 3005.2 | B: 3020.4 / S: 2990.0 | delta: -0.2320(-0.0150) | Adj: -2.15%, Vol: 1.42, Thr: 0.0348 | PnL: -5.04 | TotPnL: -5.04
|
||||
1766998500403, 2025-12-29 09:55:00,403 - [IDLE] BNB | Px: 858.65 | M: 856.5 | B: 860.3 / S: 852.8 | delta: -1.1234(-0.0954) | Adj: +6.28%, Vol: 1.00, Thr: 0.1685 | PnL: 8.01 | TotPnL: 8.01
|
||||
1766998560662, 2025-12-29 09:56:00,662 - [IDLE] ETH | Px: 3012.95 | M: 3007.6 | B: 3022.7 / S: 2992.6 | delta: -0.2291(-0.0121) | Adj: -2.21%, Vol: 1.51, Thr: 0.0344 | PnL: -5.30 | TotPnL: -5.30
|
||||
1766998560665, 2025-12-29 09:56:00,665 - [IDLE] BNB | Px: 858.76 | M: 856.8 | B: 860.5 / S: 853.1 | delta: -1.1177(-0.0897) | Adj: +6.21%, Vol: 1.12, Thr: 0.1677 | PnL: 7.95 | TotPnL: 7.95
|
||||
1766998650910, 2025-12-29 09:57:30,910 - [IDLE] ETH | Px: 3014.15 | M: 3010.1 | B: 3024.9 / S: 2995.2 | delta: -0.2263(-0.0093) | Adj: -2.28%, Vol: 1.51, Thr: 0.0339 | PnL: -5.58 | TotPnL: -5.58
|
||||
1766998650912, 2025-12-29 09:57:30,912 - [IDLE] BNB | Px: 859.00 | M: 857.3 | B: 861.0 / S: 853.7 | delta: -1.1046(-0.0766) | Adj: +6.08%, Vol: 1.20, Thr: 0.1657 | PnL: 7.67 | TotPnL: 7.67
|
||||
1766998770939, 2025-12-29 09:59:30,939 - [IDLE] ETH | Px: 3013.55 | M: 3008.9 | B: 3023.8 / S: 2993.9 | delta: -0.2277(-0.0107) | Adj: -2.25%, Vol: 1.25, Thr: 0.0342 | PnL: -5.40 | TotPnL: -5.40
|
||||
1766998770942, 2025-12-29 09:59:30,942 - [IDLE] BNB | Px: 858.72 | M: 856.7 | B: 860.4 / S: 853.0 | delta: -1.1196(-0.0916) | Adj: +6.24%, Vol: 1.18, Thr: 0.1679 | PnL: 8.03 | TotPnL: 8.03
|
||||
1766998800146, 2025-12-29 10:00:00,146 - [IDLE] ETH | Px: 3013.25 | M: 3008.2 | B: 3023.2 / S: 2993.3 | delta: -0.2284(-0.0114) | Adj: -2.23%, Vol: 1.07, Thr: 0.0343 | PnL: -5.36 | TotPnL: -5.36
|
||||
1766998800147, 2025-12-29 10:00:00,147 - [IDLE] BNB | Px: 858.70 | M: 856.7 | B: 860.4 / S: 853.0 | delta: -1.1204(-0.0924) | Adj: +6.24%, Vol: 1.05, Thr: 0.1681 | PnL: 8.02 | TotPnL: 8.02
|
||||
1766998920531, 2025-12-29 10:02:00,531 - [IDLE] ETH | Px: 3012.25 | M: 3006.2 | B: 3021.4 / S: 2991.1 | delta: -0.2308(-0.0138) | Adj: -2.18%, Vol: 1.00, Thr: 0.0346 | PnL: -5.17 | TotPnL: -5.17
|
||||
1766998920533, 2025-12-29 10:02:00,533 - [IDLE] BNB | Px: 858.34 | M: 855.9 | B: 859.6 / S: 852.1 | delta: -1.1400(-0.1120) | Adj: +6.45%, Vol: 1.00, Thr: 0.1710 | PnL: 8.38 | TotPnL: 8.38
|
||||
1766998980197, 2025-12-29 10:03:00,197 - [IDLE] ETH | Px: 3011.35 | M: 3004.4 | B: 3019.7 / S: 2989.1 | delta: -0.2329(-0.0159) | Adj: -2.13%, Vol: 1.00, Thr: 0.0349 | PnL: -4.93 | TotPnL: -4.93
|
||||
1766998980200, 2025-12-29 10:03:00,200 - [IDLE] BNB | Px: 857.86 | M: 854.8 | B: 858.7 / S: 851.0 | delta: -1.1668(-0.1388) | Adj: +6.74%, Vol: 1.00, Thr: 0.1750 | PnL: 8.85 | TotPnL: 8.85
|
||||
1766999070388, 2025-12-29 10:04:30,388 - [IDLE] ETH | Px: 3012.95 | M: 3007.6 | B: 3022.7 / S: 2992.6 | delta: -0.2291(-0.0121) | Adj: -2.21%, Vol: 1.00, Thr: 0.0344 | PnL: -5.23 | TotPnL: -5.23
|
||||
1766999070389, 2025-12-29 10:04:30,389 - [IDLE] BNB | Px: 857.96 | M: 855.0 | B: 858.9 / S: 851.2 | delta: -1.1608(-0.1328) | Adj: +6.68%, Vol: 1.00, Thr: 0.1741 | PnL: 8.79 | TotPnL: 8.79
|
||||
1766999100672, 2025-12-29 10:05:00,672 - [IDLE] ETH | Px: 3013.25 | M: 3008.2 | B: 3023.2 / S: 2993.3 | delta: -0.2284(-0.0114) | Adj: -2.23%, Vol: 1.00, Thr: 0.0343 | PnL: -5.34 | TotPnL: -5.34
|
||||
1766999100673, 2025-12-29 10:05:00,673 - [IDLE] BNB | Px: 858.04 | M: 855.2 | B: 859.0 / S: 851.4 | delta: -1.1570(-0.1290) | Adj: +6.63%, Vol: 1.00, Thr: 0.1735 | PnL: 8.71 | TotPnL: 8.71
|
||||
1766999211165, 2025-12-29 10:06:51,165 - [WARN] LARGE HEDGE: 0.1758 > 0.0080 (x5.0)
|
||||
1766999211520, 2025-12-29 10:06:51,520 - [TRIG] Net BNB: SELL 0.1758 | Tgt: -1.2038 / Cur: -1.0280 | Thresh: 0.0080
|
||||
1766999211522, 2025-12-29 10:06:51,522 - [ORDER] IOC BNB SELL 0.175 @ 856.26
|
||||
1766999213606, 2025-12-29 10:06:53,606 - Order filled immediately.
|
||||
1766999213607, 2025-12-29 10:06:53,607 - [SHADOW] Created Maker SELL @ 857.15
|
||||
1766999213607, 2025-12-29 10:06:53,607 - Sleeping 5s to allow position update...
|
||||
1766999220453, 2025-12-29 10:07:00,453 - [IDLE] ETH | Px: 3009.85 | M: 3001.4 | B: 3016.8 / S: 2985.9 | delta: -0.2365(-0.0195) | Adj: -2.05%, Vol: 1.00, Thr: 0.0355 | PnL: -4.65 | TotPnL: -4.65
|
||||
1766999220456, 2025-12-29 10:07:00,456 - [IDLE] BNB | Px: 857.15 | M: 857.1 | B: 857.3 / S: 856.9 | delta: -1.2057(-0.0027) | Adj: +7.15%, Vol: 1.04, Thr: 0.0080 | PnL: 9.57 | TotPnL: 9.57
|
||||
1766999250312, 2025-12-29 10:07:30,312 - [IDLE] ETH | Px: 3008.55 | M: 2998.7 | B: 3014.4 / S: 2983.0 | delta: -0.2395(-0.0225) | Adj: -1.99%, Vol: 1.00, Thr: 0.0359 | PnL: -4.38 | TotPnL: -4.38
|
||||
1766999250314, 2025-12-29 10:07:30,314 - [IDLE] BNB | Px: 857.00 | M: 856.8 | B: 856.9 / S: 856.6 | delta: -1.2137(-0.0107) | Adj: +7.23%, Vol: 1.11, Thr: 0.0080 | PnL: 9.91 | TotPnL: 9.91
|
||||
1766999277600, 2025-12-29 10:07:57,600 - [TRIG] Net BNB: SELL 0.0093 | Tgt: -1.2123 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999285895, 2025-12-29 10:08:05,895 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999288065, 2025-12-29 10:08:08,065 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999290417, 2025-12-29 10:08:10,417 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999292566, 2025-12-29 10:08:12,566 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999295191, 2025-12-29 10:08:15,191 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999297313, 2025-12-29 10:08:17,313 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999299381, 2025-12-29 10:08:19,381 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999301630, 2025-12-29 10:08:21,630 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999303728, 2025-12-29 10:08:23,728 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999306239, 2025-12-29 10:08:26,239 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999308393, 2025-12-29 10:08:28,393 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999310241, 2025-12-29 10:08:30,241 - [IDLE] ETH | Px: 3008.25 | M: 2998.1 | B: 3013.8 / S: 2982.4 | delta: -0.2402(-0.0232) | Adj: -1.97%, Vol: 1.05, Thr: 0.0360 | PnL: -4.32 | TotPnL: -4.32
|
||||
1766999310506, 2025-12-29 10:08:30,506 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999310507, 2025-12-29 10:08:30,507 - [IDLE] BNB | Px: 857.04 | M: 856.8 | B: 857.0 / S: 856.7 | delta: -1.2121(-0.0091) | Adj: +7.22%, Vol: 1.27, Thr: 0.0080 | PnL: 9.72 | TotPnL: 9.72
|
||||
1766999312672, 2025-12-29 10:08:32,672 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999315265, 2025-12-29 10:08:35,265 - [TRIG] Net BNB: SELL 0.0091 | Tgt: -1.2121 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1766999340113, 2025-12-29 10:09:00,113 - [IDLE] ETH | Px: 3008.95 | M: 2999.5 | B: 3015.2 / S: 2983.9 | delta: -0.2386(-0.0216) | Adj: -2.01%, Vol: 1.14, Thr: 0.0358 | PnL: -4.45 | TotPnL: -4.45
|
||||
1766999340115, 2025-12-29 10:09:00,115 - [IDLE] BNB | Px: 857.20 | M: 857.2 | B: 861.2 / S: 853.2 | delta: -1.2032(-0.0002) | Adj: +7.12%, Vol: 1.29, Thr: 0.1805 | PnL: 9.51 | TotPnL: 9.51
|
||||
1766999370458, 2025-12-29 10:09:30,458 - [IDLE] ETH | Px: 3008.15 | M: 2997.9 | B: 3013.6 / S: 2982.2 | delta: -0.2405(-0.0235) | Adj: -1.97%, Vol: 1.21, Thr: 0.0361 | PnL: -4.28 | TotPnL: -4.28
|
||||
1766999370459, 2025-12-29 10:09:30,459 - [IDLE] BNB | Px: 857.08 | M: 856.9 | B: 857.1 / S: 856.8 | delta: -1.2093(-0.0063) | Adj: +7.19%, Vol: 1.28, Thr: 0.0080 | PnL: 9.65 | TotPnL: 9.65
|
||||
1766999417106, 2025-12-29 10:10:17,106 - Stopping...
|
||||
1766999426067, 2025-12-29 10:10:26,067 - [UNIFIED] Master Hedger initialized. Agent: 0xf94D30610B524D7FFF01d5062bE20e0d96b09Ae6
|
||||
1766999426068, 2025-12-29 10:10:26,068 - Starting Unified Hedger Loop...
|
||||
1766999426639, 2025-12-29 10:10:26,639 - === HEDGER CONFIGURATION ===
|
||||
1766999426640, 2025-12-29 10:10:26,640 - --- ETH ---
|
||||
1766999426640, 2025-12-29 10:10:26,640 - MONITOR_INTERVAL_SECONDS: 60
|
||||
1766999426641, 2025-12-29 10:10:26,641 - CLOSE_POSITION_ENABLED: True
|
||||
1766999426642, 2025-12-29 10:10:26,642 - OPEN_POSITION_ENABLED: True
|
||||
1766999426642, 2025-12-29 10:10:26,642 - REBALANCE_ON_CLOSE_BELOW_RANGE: True
|
||||
1766999426643, 2025-12-29 10:10:26,643 - TARGET_INVESTMENT_AMOUNT: 200
|
||||
1766999426643, 2025-12-29 10:10:26,643 - INITIAL_HEDGE_CAPITAL: 1000
|
||||
1766999426644, 2025-12-29 10:10:26,644 - VALUE_REFERENCE: USD
|
||||
1766999426644, 2025-12-29 10:10:26,644 - WRAPPED_NATIVE_ADDRESS: 0x4200000000000000000000000000000000000006
|
||||
1766999426645, 2025-12-29 10:10:26,645 - RANGE_WIDTH_PCT: 0.10
|
||||
1766999426645, 2025-12-29 10:10:26,645 - SLIPPAGE_TOLERANCE: 0.02
|
||||
1766999426646, 2025-12-29 10:10:26,646 - TRANSACTION_TIMEOUT_SECONDS: 30
|
||||
1766999426646, 2025-12-29 10:10:26,646 - MIN_HEDGE_THRESHOLD: 0.01
|
||||
1766999426647, 2025-12-29 10:10:26,647 - CHECK_INTERVAL: 1
|
||||
1766999426647, 2025-12-29 10:10:26,647 - LEVERAGE: 5
|
||||
1766999426647, 2025-12-29 10:10:26,647 - ZONE_BOTTOM_HEDGE_LIMIT: 1.0
|
||||
1766999426648, 2025-12-29 10:10:26,648 - ZONE_CLOSE_START: 10.0
|
||||
1766999426648, 2025-12-29 10:10:26,648 - ZONE_CLOSE_END: 11.0
|
||||
1766999426648, 2025-12-29 10:10:26,648 - ZONE_TOP_HEDGE_START: 10.0
|
||||
1766999426649, 2025-12-29 10:10:26,649 - PRICE_BUFFER_PCT: 0.0015
|
||||
1766999426649, 2025-12-29 10:10:26,649 - MIN_ORDER_VALUE_USD: 10.0
|
||||
1766999426650, 2025-12-29 10:10:26,650 - DYNAMIC_THRESHOLD_MULTIPLIER: 1.2
|
||||
1766999426650, 2025-12-29 10:10:26,650 - MIN_TIME_BETWEEN_TRADES: 60
|
||||
1766999426651, 2025-12-29 10:10:26,651 - MAX_HEDGE_MULTIPLIER: 1.25
|
||||
1766999426651, 2025-12-29 10:10:26,651 - BASE_REBALANCE_THRESHOLD_PCT: 0.15
|
||||
1766999426651, 2025-12-29 10:10:26,651 - EDGE_PROXIMITY_PCT: 0.04
|
||||
1766999426652, 2025-12-29 10:10:26,652 - VELOCITY_THRESHOLD_PCT: 0.0005
|
||||
1766999426652, 2025-12-29 10:10:26,652 - POSITION_OPEN_EDGE_PROXIMITY_PCT: 0.06
|
||||
1766999426653, 2025-12-29 10:10:26,653 - POSITION_CLOSED_EDGE_PROXIMITY_PCT: 0.025
|
||||
1766999426653, 2025-12-29 10:10:26,653 - LARGE_HEDGE_MULTIPLIER: 5.0
|
||||
1766999426654, 2025-12-29 10:10:26,654 - ENABLE_EDGE_CLEANUP: True
|
||||
1766999426654, 2025-12-29 10:10:26,654 - EDGE_CLEANUP_MARGIN_PCT: 0.02
|
||||
1766999426655, 2025-12-29 10:10:26,655 - MAKER_ORDER_TIMEOUT: 600
|
||||
1766999426655, 2025-12-29 10:10:26,655 - SHADOW_ORDER_TIMEOUT: 600
|
||||
1766999426656, 2025-12-29 10:10:26,656 - ENABLE_FISHING: False
|
||||
1766999426656, 2025-12-29 10:10:26,656 - FISHING_ORDER_SIZE_PCT: 0.10
|
||||
1766999426657, 2025-12-29 10:10:26,657 - sz_decimals: 4
|
||||
1766999426657, 2025-12-29 10:10:26,657 - NAME: Aerodrome/Uni (Base) - WETH/cbBTC
|
||||
1766999426658, 2025-12-29 10:10:26,658 - COIN_SYMBOL: ETH
|
||||
1766999426658, 2025-12-29 10:10:26,658 - RPC_ENV_VAR: BASE_RPC_URL
|
||||
1766999426659, 2025-12-29 10:10:26,659 - NPM_ADDRESS: 0x0000000000000000000000000000000000000000
|
||||
1766999426659, 2025-12-29 10:10:26,659 - ROUTER_ADDRESS: 0x0000000000000000000000000000000000000000
|
||||
1766999426660, 2025-12-29 10:10:26,660 - TOKEN_A_ADDRESS: 0x4200000000000000000000000000000000000006
|
||||
1766999426661, 2025-12-29 10:10:26,661 - TOKEN_B_ADDRESS: 0xcbB7C915AB58735a1391B9fE18541b4d8926D412
|
||||
1766999426661, 2025-12-29 10:10:26,661 - POOL_FEE: 3000
|
||||
1766999426662, 2025-12-29 10:10:26,662 - --- BNB ---
|
||||
1766999426662, 2025-12-29 10:10:26,662 - MONITOR_INTERVAL_SECONDS: 60
|
||||
1766999426663, 2025-12-29 10:10:26,663 - CLOSE_POSITION_ENABLED: True
|
||||
1766999426663, 2025-12-29 10:10:26,663 - OPEN_POSITION_ENABLED: True
|
||||
1766999426663, 2025-12-29 10:10:26,663 - REBALANCE_ON_CLOSE_BELOW_RANGE: True
|
||||
1766999426664, 2025-12-29 10:10:26,664 - TARGET_INVESTMENT_AMOUNT: 1000
|
||||
1766999426664, 2025-12-29 10:10:26,664 - INITIAL_HEDGE_CAPITAL: 1000
|
||||
1766999426664, 2025-12-29 10:10:26,664 - VALUE_REFERENCE: USD
|
||||
1766999426665, 2025-12-29 10:10:26,665 - WRAPPED_NATIVE_ADDRESS: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
|
||||
1766999426665, 2025-12-29 10:10:26,665 - RANGE_WIDTH_PCT: 0.015
|
||||
1766999426665, 2025-12-29 10:10:26,665 - SLIPPAGE_TOLERANCE: 0.02
|
||||
1766999426666, 2025-12-29 10:10:26,666 - TRANSACTION_TIMEOUT_SECONDS: 30
|
||||
1766999426666, 2025-12-29 10:10:26,666 - MIN_HEDGE_THRESHOLD: 0.05
|
||||
1766999426667, 2025-12-29 10:10:26,667 - CHECK_INTERVAL: 1
|
||||
1766999426667, 2025-12-29 10:10:26,667 - LEVERAGE: 5
|
||||
1766999426667, 2025-12-29 10:10:26,667 - ZONE_BOTTOM_HEDGE_LIMIT: 1.0
|
||||
1766999426668, 2025-12-29 10:10:26,668 - ZONE_CLOSE_START: 10.0
|
||||
1766999426668, 2025-12-29 10:10:26,668 - ZONE_CLOSE_END: 11.0
|
||||
1766999426668, 2025-12-29 10:10:26,668 - ZONE_TOP_HEDGE_START: 10.0
|
||||
1766999426669, 2025-12-29 10:10:26,669 - PRICE_BUFFER_PCT: 0.0015
|
||||
1766999426669, 2025-12-29 10:10:26,669 - MIN_ORDER_VALUE_USD: 10.0
|
||||
1766999426669, 2025-12-29 10:10:26,669 - DYNAMIC_THRESHOLD_MULTIPLIER: 1.2
|
||||
1766999426670, 2025-12-29 10:10:26,670 - MIN_TIME_BETWEEN_TRADES: 60
|
||||
1766999426670, 2025-12-29 10:10:26,670 - MAX_HEDGE_MULTIPLIER: 1.25
|
||||
1766999426671, 2025-12-29 10:10:26,671 - BASE_REBALANCE_THRESHOLD_PCT: 0.25
|
||||
1766999426671, 2025-12-29 10:10:26,671 - EDGE_PROXIMITY_PCT: 0.04
|
||||
1766999426671, 2025-12-29 10:10:26,671 - VELOCITY_THRESHOLD_PCT: 0.0005
|
||||
1766999426672, 2025-12-29 10:10:26,672 - POSITION_OPEN_EDGE_PROXIMITY_PCT: 0.06
|
||||
1766999426672, 2025-12-29 10:10:26,672 - POSITION_CLOSED_EDGE_PROXIMITY_PCT: 0.025
|
||||
1766999426672, 2025-12-29 10:10:26,672 - LARGE_HEDGE_MULTIPLIER: 5.0
|
||||
1766999426673, 2025-12-29 10:10:26,673 - ENABLE_EDGE_CLEANUP: True
|
||||
1766999426673, 2025-12-29 10:10:26,673 - EDGE_CLEANUP_MARGIN_PCT: 0.02
|
||||
1766999426673, 2025-12-29 10:10:26,673 - MAKER_ORDER_TIMEOUT: 600
|
||||
1766999426674, 2025-12-29 10:10:26,674 - SHADOW_ORDER_TIMEOUT: 600
|
||||
1766999426674, 2025-12-29 10:10:26,674 - ENABLE_FISHING: False
|
||||
1766999426674, 2025-12-29 10:10:26,674 - FISHING_ORDER_SIZE_PCT: 0.10
|
||||
1766999426675, 2025-12-29 10:10:26,675 - sz_decimals: 3
|
||||
1766999426675, 2025-12-29 10:10:26,675 - NAME: PancakeSwap V3 (BNB Chain) - BNB/USDT
|
||||
1766999426676, 2025-12-29 10:10:26,676 - COIN_SYMBOL: BNB
|
||||
1766999426676, 2025-12-29 10:10:26,676 - RPC_ENV_VAR: BNB_RPC_URL
|
||||
1766999426677, 2025-12-29 10:10:26,677 - NPM_ADDRESS: 0x46A15B0b27311cedF172AB29E4f4766fbE7F4364
|
||||
1766999426677, 2025-12-29 10:10:26,677 - ROUTER_ADDRESS: 0x1b81D678ffb9C0263b24A97847620C99d213eB14
|
||||
1766999426677, 2025-12-29 10:10:26,677 - TOKEN_A_ADDRESS: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c
|
||||
1766999426678, 2025-12-29 10:10:26,678 - TOKEN_B_ADDRESS: 0x55d398326f99059fF775485246999027B3197955
|
||||
1766999426678, 2025-12-29 10:10:26,678 - POOL_FEE: 100
|
||||
1766999426678, 2025-12-29 10:10:26,678 - ============================
|
||||
1766999427980, 2025-12-29 10:10:27,980 - [STARTUP] Skipping Ghost Cleanup for ETH (Grace Period)
|
||||
1766999427980, 2025-12-29 10:10:27,980 - [STARTUP] Skipping Ghost Cleanup for BNB (Grace Period)
|
||||
1766999428984, 2025-12-29 10:10:28,984 - [STRAT] Init 6153292 (BNB) | Range: 856.6782-882.4136
|
||||
1766999428986, 2025-12-29 10:10:28,986 - [STRAT] Init 5182179 (ETH) | Range: 2827.096-3118.1664
|
||||
1766999430021, 2025-12-29 10:10:30,021 - [IDLE] ETH | Px: 3009.45 | M: 3000.5 | B: 3016.1 / S: 2985.0 | delta: -0.2374(-0.0204) | Adj: -2.03%, Vol: 1.00, Thr: 0.0356 | PnL: -4.62 | TotPnL: -4.62
|
||||
1766999430023, 2025-12-29 10:10:30,023 - [IDLE] BNB | Px: 857.72 | M: 858.3 | B: 862.2 / S: 854.5 | delta: -1.1745(+0.0285) | Adj: +6.82%, Vol: 1.00, Thr: 0.1762 | PnL: 8.94 | TotPnL: 8.94
|
||||
1766999490692, 2025-12-29 10:11:30,692 - [IDLE] ETH | Px: 3010.55 | M: 3002.8 | B: 3018.2 / S: 2987.4 | delta: -0.2348(-0.0178) | Adj: -2.09%, Vol: 1.00, Thr: 0.0352 | PnL: -4.82 | TotPnL: -4.82
|
||||
1766999490693, 2025-12-29 10:11:30,693 - [IDLE] BNB | Px: 857.97 | M: 858.9 | B: 862.7 / S: 855.1 | delta: -1.1605(+0.0425) | Adj: +6.67%, Vol: 1.00, Thr: 0.1741 | PnL: 8.62 | TotPnL: 8.62
|
||||
1766999550821, 2025-12-29 10:12:30,821 - [IDLE] ETH | Px: 3009.95 | M: 3001.6 | B: 3017.0 / S: 2986.1 | delta: -0.2362(-0.0192) | Adj: -2.06%, Vol: 1.00, Thr: 0.0354 | PnL: -4.67 | TotPnL: -4.67
|
||||
1766999550823, 2025-12-29 10:12:30,823 - [IDLE] BNB | Px: 857.50 | M: 857.9 | B: 861.8 / S: 854.0 | delta: -1.1861(+0.0169) | Adj: +6.94%, Vol: 1.00, Thr: 0.1779 | PnL: 9.06 | TotPnL: 9.06
|
||||
1766999610366, 2025-12-29 10:13:30,366 - [IDLE] ETH | Px: 3009.95 | M: 3001.6 | B: 3017.0 / S: 2986.1 | delta: -0.2362(-0.0192) | Adj: -2.06%, Vol: 1.00, Thr: 0.0354 | PnL: -4.69 | TotPnL: -4.69
|
||||
1766999610368, 2025-12-29 10:13:30,368 - [IDLE] BNB | Px: 857.60 | M: 858.1 | B: 862.0 / S: 854.2 | delta: -1.1809(+0.0221) | Adj: +6.89%, Vol: 1.00, Thr: 0.1771 | PnL: 9.06 | TotPnL: 9.06
|
||||
1766999640818, 2025-12-29 10:14:00,818 - [IDLE] ETH | Px: 3010.55 | M: 3002.8 | B: 3018.2 / S: 2987.4 | delta: -0.2348(-0.0178) | Adj: -2.09%, Vol: 1.00, Thr: 0.0352 | PnL: -4.80 | TotPnL: -4.80
|
||||
1766999640820, 2025-12-29 10:14:00,820 - [IDLE] BNB | Px: 857.59 | M: 858.1 | B: 862.0 / S: 854.2 | delta: -1.1814(+0.0216) | Adj: +6.89%, Vol: 1.00, Thr: 0.1772 | PnL: 9.06 | TotPnL: 9.06
|
||||
1766999700810, 2025-12-29 10:15:00,810 - [IDLE] ETH | Px: 3010.55 | M: 3002.8 | B: 3018.2 / S: 2987.4 | delta: -0.2348(-0.0178) | Adj: -2.09%, Vol: 1.00, Thr: 0.0352 | PnL: -4.75 | TotPnL: -4.75
|
||||
1766999700812, 2025-12-29 10:15:00,812 - [IDLE] BNB | Px: 857.68 | M: 858.3 | B: 862.1 / S: 854.4 | delta: -1.1767(+0.0263) | Adj: +6.84%, Vol: 1.00, Thr: 0.1765 | PnL: 8.95 | TotPnL: 8.95
|
||||
1766999790664, 2025-12-29 10:16:30,664 - [IDLE] ETH | Px: 3009.75 | M: 3001.1 | B: 3016.7 / S: 2985.6 | delta: -0.2367(-0.0197) | Adj: -2.05%, Vol: 1.00, Thr: 0.0355 | PnL: -4.62 | TotPnL: -4.62
|
||||
1766999790666, 2025-12-29 10:16:30,666 - [IDLE] BNB | Px: 857.72 | M: 858.4 | B: 862.2 / S: 854.5 | delta: -1.1740(+0.0290) | Adj: +6.82%, Vol: 1.00, Thr: 0.1761 | PnL: 8.88 | TotPnL: 8.88
|
||||
1766999880731, 2025-12-29 10:18:00,731 - [IDLE] ETH | Px: 3009.15 | M: 2999.9 | B: 3015.5 / S: 2984.3 | delta: -0.2381(-0.0211) | Adj: -2.02%, Vol: 1.00, Thr: 0.0357 | PnL: -4.49 | TotPnL: -4.49
|
||||
1766999880734, 2025-12-29 10:18:00,734 - [IDLE] BNB | Px: 857.40 | M: 857.7 | B: 861.6 / S: 853.7 | delta: -1.1916(+0.0114) | Adj: +7.00%, Vol: 1.00, Thr: 0.1787 | PnL: 9.26 | TotPnL: 9.26
|
||||
1766999940148, 2025-12-29 10:19:00,148 - [IDLE] ETH | Px: 3010.15 | M: 3002.0 | B: 3017.4 / S: 2986.5 | delta: -0.2357(-0.0187) | Adj: -2.07%, Vol: 1.00, Thr: 0.0354 | PnL: -4.73 | TotPnL: -4.73
|
||||
1766999940150, 2025-12-29 10:19:00,150 - [IDLE] BNB | Px: 857.82 | M: 858.6 | B: 862.4 / S: 854.7 | delta: -1.1690(+0.0340) | Adj: +6.76%, Vol: 1.00, Thr: 0.1754 | PnL: 8.82 | TotPnL: 8.82
|
||||
1767000000185, 2025-12-29 10:20:00,185 - [IDLE] ETH | Px: 3010.75 | M: 3003.2 | B: 3018.5 / S: 2987.8 | delta: -0.2343(-0.0173) | Adj: -2.10%, Vol: 1.00, Thr: 0.0351 | PnL: -4.84 | TotPnL: -4.84
|
||||
1767000000188, 2025-12-29 10:20:00,188 - [IDLE] BNB | Px: 857.80 | M: 858.5 | B: 862.4 / S: 854.7 | delta: -1.1701(+0.0329) | Adj: +6.77%, Vol: 1.00, Thr: 0.1755 | PnL: 8.76 | TotPnL: 8.76
|
||||
1767000210575, 2025-12-29 10:23:30,575 - [IDLE] ETH | Px: 3011.35 | M: 3004.4 | B: 3019.7 / S: 2989.1 | delta: -0.2329(-0.0159) | Adj: -2.13%, Vol: 1.00, Thr: 0.0349 | PnL: -4.97 | TotPnL: -4.97
|
||||
1767000210578, 2025-12-29 10:23:30,578 - [IDLE] BNB | Px: 857.82 | M: 858.6 | B: 862.4 / S: 854.7 | delta: -1.1690(+0.0340) | Adj: +6.76%, Vol: 1.00, Thr: 0.1754 | PnL: 8.78 | TotPnL: 8.78
|
||||
1767000270441, 2025-12-29 10:24:30,441 - [IDLE] ETH | Px: 3013.35 | M: 3008.5 | B: 3023.4 / S: 2993.5 | delta: -0.2282(-0.0112) | Adj: -2.23%, Vol: 1.00, Thr: 0.0342 | PnL: -5.40 | TotPnL: -5.40
|
||||
1767000270443, 2025-12-29 10:24:30,443 - [IDLE] BNB | Px: 857.90 | M: 858.7 | B: 862.6 / S: 854.9 | delta: -1.1646(+0.0384) | Adj: +6.72%, Vol: 1.00, Thr: 0.1747 | PnL: 8.67 | TotPnL: 8.67
|
||||
1767000330307, 2025-12-29 10:25:30,307 - [IDLE] ETH | Px: 3012.55 | M: 3006.8 | B: 3021.9 / S: 2991.7 | delta: -0.2301(-0.0131) | Adj: -2.19%, Vol: 1.00, Thr: 0.0345 | PnL: -5.23 | TotPnL: -5.23
|
||||
1767000330309, 2025-12-29 10:25:30,309 - [IDLE] BNB | Px: 857.60 | M: 858.1 | B: 862.0 / S: 854.2 | delta: -1.1811(+0.0219) | Adj: +6.89%, Vol: 1.00, Thr: 0.1772 | PnL: 9.04 | TotPnL: 9.04
|
||||
1767000390450, 2025-12-29 10:26:30,450 - [IDLE] ETH | Px: 3008.55 | M: 2998.7 | B: 3014.4 / S: 2983.0 | delta: -0.2395(-0.0225) | Adj: -1.99%, Vol: 1.00, Thr: 0.0359 | PnL: -4.49 | TotPnL: -4.49
|
||||
1767000390452, 2025-12-29 10:26:30,452 - [IDLE] BNB | Px: 857.30 | M: 857.4 | B: 861.4 / S: 853.5 | delta: -1.1977(+0.0053) | Adj: +7.07%, Vol: 1.00, Thr: 0.1797 | PnL: 9.33 | TotPnL: 9.33
|
||||
1767000450745, 2025-12-29 10:27:30,745 - [IDLE] ETH | Px: 3004.85 | M: 2991.2 | B: 3007.4 / S: 2975.0 | delta: -0.2483(-0.0313) | Adj: -1.80%, Vol: 1.29, Thr: 0.0372 | PnL: -3.52 | TotPnL: -3.52
|
||||
1767000450748, 2025-12-29 10:27:30,748 - [IDLE] BNB | Px: 857.30 | M: 857.4 | B: 861.4 / S: 853.5 | delta: -1.1971(+0.0059) | Adj: +7.06%, Vol: 1.00, Thr: 0.1796 | PnL: 9.53 | TotPnL: 9.53
|
||||
1767000600237, 2025-12-29 10:30:00,237 - [IDLE] ETH | Px: 3006.75 | M: 2995.1 | B: 3011.0 / S: 2979.1 | delta: -0.2438(-0.0268) | Adj: -1.89%, Vol: 1.78, Thr: 0.0366 | PnL: -4.02 | TotPnL: -4.02
|
||||
1767000600239, 2025-12-29 10:30:00,239 - [IDLE] BNB | Px: 857.62 | M: 858.1 | B: 862.0 / S: 854.3 | delta: -1.1795(+0.0235) | Adj: +6.87%, Vol: 1.00, Thr: 0.1769 | PnL: 9.08 | TotPnL: 9.08
|
||||
1767000660107, 2025-12-29 10:31:00,107 - [IDLE] ETH | Px: 3006.25 | M: 2994.0 | B: 3010.1 / S: 2978.0 | delta: -0.2450(-0.0280) | Adj: -1.87%, Vol: 1.86, Thr: 0.0367 | PnL: -3.86 | TotPnL: -3.86
|
||||
1767000660108, 2025-12-29 10:31:00,108 - [IDLE] BNB | Px: 857.40 | M: 857.6 | B: 861.6 / S: 853.7 | delta: -1.1922(+0.0108) | Adj: +7.01%, Vol: 1.00, Thr: 0.1788 | PnL: 9.27 | TotPnL: 9.27
|
||||
1767000690915, 2025-12-29 10:31:30,915 - [IDLE] ETH | Px: 3005.15 | M: 2991.8 | B: 3008.0 / S: 2975.6 | delta: -0.2476(-0.0306) | Adj: -1.81%, Vol: 1.94, Thr: 0.0371 | PnL: -3.65 | TotPnL: -3.65
|
||||
1767000690917, 2025-12-29 10:31:30,917 - [IDLE] BNB | Px: 857.56 | M: 858.0 | B: 861.9 / S: 854.1 | delta: -1.1833(+0.0197) | Adj: +6.91%, Vol: 1.00, Thr: 0.1775 | PnL: 9.01 | TotPnL: 9.01
|
||||
1767000705122, 2025-12-29 10:31:45,122 - [TRIG] Net ETH: SELL 0.0397 | Tgt: -0.2567 / Cur: -0.2170 | Thresh: 0.0385
|
||||
1767000705123, 2025-12-29 10:31:45,123 - [ORDER] ALO ETH SELL 0.0396 @ 3001.4
|
||||
1767000707582, 2025-12-29 10:31:47,582 - Sleeping 5s to allow position update...
|
||||
1767000712865, 2025-12-29 10:31:52,865 - [TRIG] Net BNB: SELL 0.0295 | Tgt: -1.2325 / Cur: -1.2030 | Thresh: 0.0080
|
||||
1767000712866, 2025-12-29 10:31:52,866 - [ORDER] ALO BNB SELL 0.029 @ 856.11
|
||||
1767000713605, 2025-12-29 10:31:53,605 - Sleeping 5s to allow position update...
|
||||
1767000720440, 2025-12-29 10:32:00,440 - [IDLE] ETH | Px: 3002.85 | M: 3004.4 | B: 3020.9 / S: 2987.8 | delta: -0.2531(+0.0035) | Adj: -1.69%, Vol: 2.01, Thr: 0.0380 | PnL: -3.16 | TotPnL: -3.16
|
||||
1767000720443, 2025-12-29 10:32:00,443 - [IDLE] BNB | Px: 856.30 | M: 856.3 | B: 856.5 / S: 856.1 | delta: -1.2327(-0.0007) | Adj: +7.50%, Vol: 1.00, Thr: 0.0080 | PnL: 10.60 | TotPnL: 10.60
|
||||
1767000730199, 2025-12-29 10:32:10,199 - [STRAT] 6153292 is CLOSING -> Force Target 0
|
||||
1767000730202, 2025-12-29 10:32:10,202 - [URGENT] BNB Closing Strategy -> Force Taker Exit
|
||||
1767000730203, 2025-12-29 10:32:10,203 - [WARN] LARGE HEDGE: 1.2320 > 0.0080 (x5.0)
|
||||
1767000730473, 2025-12-29 10:32:10,473 - [TRIG] Net BNB: BUY 1.2320 | Tgt: 0.0000 / Cur: -1.2320 | Thresh: 0.0080
|
||||
1767000730474, 2025-12-29 10:32:10,474 - [ORDER] IOC BNB BUY 1.232 @ 856.95
|
||||
1767000731824, 2025-12-29 10:32:11,824 - Order filled immediately.
|
||||
1767000731825, 2025-12-29 10:32:11,825 - [SHADOW] Created Maker BUY @ 856.03
|
||||
1767000731826, 2025-12-29 10:32:11,826 - Sleeping 5s to allow position update...
|
||||
1767000737851, 2025-12-29 10:32:17,851 - Strategy 6153292 removed (Closed/Gone).
|
||||
117
florida/summaries/6153292/6153292_analisis.md
Normal file
117
florida/summaries/6153292/6153292_analisis.md
Normal file
@ -0,0 +1,117 @@
|
||||
# CLP Strategy & Configuration Log
|
||||
|
||||
This document detailed information about clp position (6153292) + auto hedge on Hyperliquide.
|
||||
|
||||
---
|
||||
|
||||
## 1. Low Volatility / Weekend Optimization (Narrow Range)
|
||||
**Date:** 2025-12-29
|
||||
**Status:** Active
|
||||
**Objective:** Further optimalization of hedging on Hyperliquide.
|
||||
|
||||
### 🔍 Context
|
||||
* **Market Condition:** Monday, th 29th of Dec
|
||||
* **Capital:** $1,000 USDC
|
||||
* **Range:** +/- 1.5% (Narrow)
|
||||
|
||||
### ⚙️ Configuration of scripts
|
||||
(See original file for full config dump)
|
||||
|
||||
### test results
|
||||
1. clp position:
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 6153292,
|
||||
"status": "CLOSED",
|
||||
"target_value": 993.31,
|
||||
"entry_price": 869.418,
|
||||
"amount0_initial": 500.0094,
|
||||
"amount1_initial": 0.5674,
|
||||
"liquidity": "2284728345715808667084",
|
||||
"range_upper": 882.4136,
|
||||
"range_lower": 856.6782,
|
||||
"token0_decimals": 18,
|
||||
"token1_decimals": 18,
|
||||
"timestamp_open": 1766982584,
|
||||
"target_value_end": 982.48,
|
||||
"timestamp_close": 1767000734
|
||||
}
|
||||
2. hedge transactions (from Hyperliquide)
|
||||
(See original file for table)
|
||||
|
||||
## results of tests are not satisfactional:
|
||||
1. the main problem is that the hedge doesn't cover lost value of clp pool (without earned fees) -> **-$2.23**
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Analysis & Diagnosis (2025-12-29)
|
||||
|
||||
After reviewing the logs and transaction history, here is the breakdown of the PnL and the root cause of the slippage.
|
||||
|
||||
#### **1. PnL Breakdown (The "Missing" $2.23)**
|
||||
|
||||
* **LP Position Value Change:**
|
||||
* Initial Value: ~$993.31
|
||||
* Final Value: ~$982.50
|
||||
* **LP Loss:** **-$10.81** (This is the Impermanent Loss + Delta Loss from holding BNB as it dropped).
|
||||
* **Hedge Compensation:**
|
||||
* Hedge Gross Profit: **+$10.61** (from your table).
|
||||
* **Net Delta Efficiency:** The hedge covered the LP loss almost perfectly (Diff: -$0.20).
|
||||
* *Conclusion:* The **Delta Calculation is CORRECT**. The math effectively neutralized the market move.
|
||||
* **The Costs (The Real Leak):**
|
||||
* **Trading Fees:** You paid ~$0.79 in fees on Hyperliquid (mostly from the initial Taker entry and the final Taker exit).
|
||||
* **Funding Fees:** Since you were Shorting BNB for ~5 hours, and rates might have been negative (Shorts pay Longs) or just the cost of carry, the remaining discrepancy (~$1.20) is likely **Funding Costs** or slight slippage between LP exit price and Hedge exit price.
|
||||
|
||||
**Total Net:** -$0.20 (Delta Slippage) - $0.79 (Fees) - $1.24 (Funding/Execution Slippage) = **-$2.23**
|
||||
|
||||
#### **2. Execution Inefficiency (The "Panic Loop")**
|
||||
|
||||
The logs reveal a flaw in the execution strategy during trending moves:
|
||||
1. **Drift:** Price drops, Delta drift exceeds threshold (`0.05`).
|
||||
2. **Maker Attempt:** Bot places an `ALO` (Maker) order at the Bid.
|
||||
3. **Timeout:** Market moves down faster than the order fills. The order sits pending (`[WAIT]`).
|
||||
4. **Cancel:** Bot cancels the stale order.
|
||||
5. **Drift Worsens:** The drift continues to grow as price drops further.
|
||||
6. **Panic:** Eventually, `Drift > 5x Threshold` (Large Hedge).
|
||||
7. **Taker Smash:** Bot forces an `IOC` (Taker) trade, paying high fees (0.035%) and eating slippage.
|
||||
|
||||
**Evidence:**
|
||||
* Initial Entry: `[WARN] LARGE HEDGE` -> Taker (Fee: $0.21).
|
||||
* Final Exit: `[URGENT] ... Force Taker Exit` -> Taker (Fee: $0.45).
|
||||
* These two trades alone account for ~85% of your fee costs.
|
||||
|
||||
---
|
||||
|
||||
### 🛠️ Answers to Questions
|
||||
|
||||
**1. Is the calculation of hedge wrong?**
|
||||
**No.** The Delta calculation is accurate. The gross profit of the hedge (+$10.61) almost exactly matched the raw value loss of the LP (-$10.81). The math works.
|
||||
|
||||
**2. How we can proactively fix it?**
|
||||
We need to fix the **Execution Strategy** to avoid the "Wait -> Cancel -> Panic Taker" loop.
|
||||
* **Soft Taker Fallback:** If a Maker order times out (e.g., after 30s), retry as Taker immediately *before* the drift becomes huge.
|
||||
* **Asymmetric Compensation:** Increase the "Over-Hedge" factor. Since V3 LP accumulates "Long" exposure as price drops (Negative Gamma), we should short *more* aggressively early on.
|
||||
|
||||
**3. What we can do with configuration?**
|
||||
We need to tune for **Narrow Ranges** (High Gamma).
|
||||
|
||||
#### **Recommended Config Changes**
|
||||
|
||||
| Parameter | Current | Recommended | Reason |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `MIN_HEDGE_THRESHOLD` | `0.05` | **0.02** | Hedge smaller deviations sooner to prevent runaway gamma. |
|
||||
| `LARGE_HEDGE_MULTIPLIER` | `5.0` | **2.5** | Trigger Taker/Urgent correction sooner, before the hole gets too deep. |
|
||||
| `MAKER_ORDER_TIMEOUT` | `600` | **60** | Don't let orders rot for 10 minutes. Cancel and retry faster. |
|
||||
| `SHADOW_ORDER_TIMEOUT` | `600` | **30** | Same as above. |
|
||||
| `BASE_REBALANCE_THRESHOLD_PCT` | `0.25` | **0.15** | Tighten the percentage-based trigger. |
|
||||
|
||||
#### **Code Change Recommendation (Unified Hedger)**
|
||||
We should update `unified_hedger.py` to:
|
||||
1. **Persist Realized PnL:** Fix the bug where `_status.json` isn't updated with hedge PnL, so your logs reflect reality.
|
||||
2. **Execution Fallback:** If an `ALO` order fails/cancels, decrement a counter. If it fails twice, force `IOC`.
|
||||
|
||||
---
|
||||
|
||||
### 📝 Action Plan
|
||||
1. Apply the **Config Changes** above to `clp_config.py`.
|
||||
2. (Optional) I can patch `unified_hedger.py` to fix the PnL logging and execution logic if you approve.
|
||||
76
florida/summaries/CLP_STRATEGY_LOG.md
Normal file
76
florida/summaries/CLP_STRATEGY_LOG.md
Normal file
@ -0,0 +1,76 @@
|
||||
# CLP Strategy & Configuration Log
|
||||
|
||||
This document tracks different configuration approaches for the Uniswap V3 CLP Hedger, their specific settings, and the observed results. Use this to refine the strategy over time.
|
||||
|
||||
---
|
||||
|
||||
## 1. Low Volatility / Weekend Optimization (Narrow Range)
|
||||
**Date:** 2025-12-23
|
||||
**Status:** Active
|
||||
**Objective:** Optimize for stable market conditions with a narrow trading range to maximize fee collection while maintaining tight delta neutrality.
|
||||
|
||||
### 🔍 Context
|
||||
* **Market Condition:** Low Volatility / Weekend / Chop
|
||||
* **Capital:** $2,000 USDC
|
||||
* **Range:** +/- 1% (Narrow)
|
||||
|
||||
### ⚙️ Configuration
|
||||
**`uniswap_manager.py`**
|
||||
* `RANGE_WIDTH_PCT`: **0.01** (+/- 1%)
|
||||
* `INITIAL_HEDGE_CAPITAL_USDC`: **2000**
|
||||
* `SLIPPAGE_TOLERANCE`: **0.02** (2%)
|
||||
|
||||
**`clp_hedger.py`**
|
||||
* `PRICE_BUFFER_PCT`: **0.0015** (0.15%)
|
||||
* `MIN_THRESHOLD_ETH`: **0.008** (~$24)
|
||||
* `BASE_REBALANCE_THRESHOLD_PCT`: **0.09** (9%)
|
||||
* `LARGE_HEDGE_MULTIPLIER`: **2.8**
|
||||
|
||||
### test results
|
||||
position:
|
||||
{
|
||||
"type": "AUTOMATIC",
|
||||
"token_id": 5174808,
|
||||
"status": "CLOSED",
|
||||
"target_value": 1994.89,
|
||||
"entry_price": 2954.93,
|
||||
"amount0_initial": 0.3299,
|
||||
"amount1_initial": 1019.94,
|
||||
"liquidity": "3679197389549125",
|
||||
"range_upper": 2983.95,
|
||||
"range_lower": 2924.86,
|
||||
"timestamp_open": 1766529348,
|
||||
"initial_hedge_usdc": 1001.01805,
|
||||
"hedge_equity_usd": 1008.884158,
|
||||
"hedge_pnl_realized": -5.85,
|
||||
"hedge_fees_paid": 2.21,
|
||||
"timestamp_close": 1766545502
|
||||
}
|
||||
|
||||
## results of tests are not satisfactional:
|
||||
1. the main problem of 5174808 is still difference between value of clp position at the end (start: $1994.42, end $1982.71 value of (tokens plus fees ~$4)) versus value of the hedge position (0.6735 weth, price 2922.5 -> 1968.3), it gives delta 1978 - 1968 = **-$10**...
|
||||
|
||||
# questions
|
||||
1. is the calculation of hedge wrong?
|
||||
2. how we can proactivly fix it?
|
||||
|
||||
---
|
||||
|
||||
### 🚀 Analysis & New Idea: Asymmetric Compensation (2025-12-24)
|
||||
|
||||
#### **Findings from Pos 5174808:**
|
||||
* **The Problem:** Alpha (NAV vs Hold) dropped by ~$3.84 during a 1.1% price drop.
|
||||
* **The Cause:** Even though the **Mathematical Delta** was correct (using raw Liquidity), **Execution Friction** (Slippage and "Gamma Bleed") eroded the profit.
|
||||
* **The Mechanism:** When price drops, we must sell to increase the short. We are always "selling the bottom" of each micro-move. This means our average entry price for the hedge is always worse than the ideal theoretical price.
|
||||
|
||||
#### **Proactive Fix: "Leaning into the Trend"**
|
||||
We have implemented a creative solution to offset this 0.35% leakage: **Asymmetric Compensation**.
|
||||
|
||||
1. **Linear Bias Adjustment:** The bot no longer calculates a purely symmetric hedge. It now "leans" into the price direction to create a PnL buffer.
|
||||
* **On Drops (Price < Entry):** The bot **Over-Hedges** by up to **+2.5%** at the bottom edge. This ensures the short gains "extra" value to cover the slippage/fees paid during rebalancing.
|
||||
* **On Rises (Price > Entry):** The bot **Under-Hedges** by up to **-2.5%** at the top edge.
|
||||
2. **Efficiency:** Increased rebalance threshold to **15%** and price buffer to **0.25%** to reduce unnecessary churn ("Chop Bleed").
|
||||
3. **Visibility:** IDLE and rebalance logs now include an `Adj: +X.X%` tag so the compensation is transparent.
|
||||
|
||||
#### **Lessons Learned:**
|
||||
* Pure delta-neutrality is theoretical. In a live market with fees and slippage, you must be "slightly biased" in the direction of the move to maintain a neutral **value** (NAV).
|
||||
BIN
florida/summaries/positions_chart.PNG
Normal file
BIN
florida/summaries/positions_chart.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
397
florida/telegram_monitor.py
Normal file
397
florida/telegram_monitor.py
Normal file
@ -0,0 +1,397 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Telegram Monitor for CLP Position Notifications
|
||||
Monitors hedge_status.json file for new position openings and sends Telegram notifications
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import hashlib
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Dict, List, Tuple, Any
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# --- SETUP PROJECT PATH ---
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(current_dir)
|
||||
sys.path.append(current_dir)
|
||||
|
||||
# --- LOGGING SETUP ---
|
||||
os.makedirs(os.path.join(current_dir, 'logs'), exist_ok=True)
|
||||
|
||||
class UnixMsLogFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
record.unix_ms = int(record.created * 1000)
|
||||
return True
|
||||
|
||||
logger = logging.getLogger("TELEGRAM_MONITOR")
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.propagate = False
|
||||
logger.handlers.clear()
|
||||
|
||||
# Console handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(console_formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# File handler
|
||||
from clp_config import TARGET_DEX
|
||||
|
||||
file_handler = logging.FileHandler(os.path.join(current_dir, 'logs', f'{TARGET_DEX}_telegram_monitor.log'), encoding='utf-8')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.addFilter(UnixMsLogFilter())
|
||||
file_formatter = logging.Formatter('%(unix_ms)d, %(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
load_dotenv(os.path.join(current_dir, '.env'))
|
||||
|
||||
TELEGRAM_ENABLED = os.getenv('TELEGRAM_MONITOR_ENABLED', 'False').lower() == 'true'
|
||||
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '')
|
||||
TELEGRAM_CHAT_ID = os.getenv('TELEGRAM_CHAT_ID', '')
|
||||
TELEGRAM_CHECK_INTERVAL = int(os.getenv('TELEGRAM_CHECK_INTERVAL_SECONDS', '60'))
|
||||
from clp_config import STATUS_FILE
|
||||
TELEGRAM_STATE_FILE = os.getenv('TELEGRAM_STATE_FILE', 'telegram_monitor_state.json')
|
||||
TELEGRAM_TIMEOUT = int(os.getenv('TELEGRAM_TIMEOUT_SECONDS', '10'))
|
||||
HEDGE_STATUS_FILE = os.getenv('HEDGE_STATUS_FILE', STATUS_FILE)
|
||||
|
||||
class TelegramNotifier:
|
||||
"""Handles Telegram API communication"""
|
||||
|
||||
def __init__(self, bot_token: str, chat_id: str):
|
||||
self.bot_token = bot_token
|
||||
self.chat_id = chat_id
|
||||
self.base_url = f"https://api.telegram.org/bot{bot_token}"
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""Test Telegram bot connection"""
|
||||
try:
|
||||
url = f"{self.base_url}/getMe"
|
||||
response = requests.get(url, timeout=TELEGRAM_TIMEOUT)
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
logger.error(f"Telegram connection test failed: {e}")
|
||||
return False
|
||||
|
||||
def send_message(self, text: str) -> bool:
|
||||
"""Send message to Telegram chat"""
|
||||
if not TELEGRAM_ENABLED or not self.bot_token or not self.chat_id:
|
||||
logger.debug("Telegram notifications disabled or missing credentials")
|
||||
return False
|
||||
|
||||
try:
|
||||
url = f"{self.base_url}/sendMessage"
|
||||
payload = {
|
||||
'chat_id': self.chat_id,
|
||||
'text': text,
|
||||
'parse_mode': 'Markdown'
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, timeout=TELEGRAM_TIMEOUT)
|
||||
result = response.json()
|
||||
|
||||
if result.get('ok'):
|
||||
logger.info("Telegram notification sent successfully")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Telegram API error: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Telegram message: {e}")
|
||||
return False
|
||||
|
||||
def format_position_message(self, last_closed: Optional[Dict], current_open: Dict) -> str:
|
||||
"""Format position data into readable message"""
|
||||
lines = ["NEW CLP POSITION DETECTED\n"]
|
||||
|
||||
# Previous closed position section
|
||||
if last_closed:
|
||||
lines.append("LAST CLOSED POSITION:")
|
||||
lines.append(f"• Token ID: {last_closed.get('token_id', 'N/A')}")
|
||||
lines.append(f"• Entry: ${last_closed.get('entry_price', 0):.2f}")
|
||||
lines.append(f"• Target Value: ${last_closed.get('target_value', 0):.2f}")
|
||||
|
||||
# Add duration if timestamps available
|
||||
if last_closed.get('timestamp_open') and last_closed.get('timestamp_close'):
|
||||
duration = last_closed['timestamp_close'] - last_closed['timestamp_open']
|
||||
hours = duration // 3600
|
||||
minutes = (duration % 3600) // 60
|
||||
lines.append(f"• Duration: {hours}h {minutes}m")
|
||||
|
||||
# Add hedge performance if available
|
||||
hedge_pnl = last_closed.get('hedge_pnl_realized', 0)
|
||||
if hedge_pnl != 0:
|
||||
lines.append(f"• Hedge PnL: ${hedge_pnl:.2f}")
|
||||
|
||||
hedge_fees = last_closed.get('hedge_fees_paid', 0)
|
||||
if hedge_fees != 0:
|
||||
lines.append(f"• Hedge Fees: ${hedge_fees:.2f}")
|
||||
else:
|
||||
lines.append("LAST CLOSED POSITION: None")
|
||||
|
||||
lines.append("") # Empty line
|
||||
|
||||
# Current opened position section
|
||||
lines.append("CURRENTLY OPENED:")
|
||||
lines.append(f"• Token ID: {current_open.get('token_id', 'N/A')}")
|
||||
lines.append(f"• Entry: ${current_open.get('entry_price', 0):.2f}")
|
||||
lines.append(f"• Target Value: ${current_open.get('target_value', 0):.2f}")
|
||||
|
||||
# Range information
|
||||
range_lower = current_open.get('range_lower', 0)
|
||||
range_upper = current_open.get('range_upper', 0)
|
||||
if range_lower and range_upper:
|
||||
lines.append(f"• Range: ${range_lower:.2f} - ${range_upper:.2f}")
|
||||
|
||||
# Initial amounts
|
||||
amount0 = current_open.get('amount0_initial', 0)
|
||||
amount1 = current_open.get('amount1_initial', 0)
|
||||
if amount0 and amount1:
|
||||
lines.append(f"• Initial: {amount0:.4f} ETH + {amount1:.2f} USDC")
|
||||
|
||||
# Time since opening
|
||||
if current_open.get('timestamp_open'):
|
||||
age = int(time.time()) - current_open['timestamp_open']
|
||||
hours = age // 3600
|
||||
minutes = (age % 3600) // 60
|
||||
lines.append(f"• Time: {hours}h {minutes}m ago")
|
||||
|
||||
# Performance comparison if we have both positions
|
||||
if last_closed and current_open:
|
||||
lines.append("") # Empty line
|
||||
lines.append("PERFORMANCE COMPARISON:")
|
||||
|
||||
# Entry price change
|
||||
last_entry = Decimal(str(last_closed.get('entry_price', 0)))
|
||||
curr_entry = Decimal(str(current_open.get('entry_price', 0)))
|
||||
if last_entry > 0:
|
||||
entry_change = curr_entry - last_entry
|
||||
entry_change_pct = (entry_change / last_entry) * 100
|
||||
sign = "+" if entry_change >= 0 else ""
|
||||
lines.append(f"• Entry Change: {sign}${entry_change:.2f} ({sign}{entry_change_pct:.2f}%)")
|
||||
|
||||
# Value change
|
||||
last_value = Decimal(str(last_closed.get('target_value', 0)))
|
||||
curr_value = Decimal(str(current_open.get('target_value', 0)))
|
||||
if last_value > 0:
|
||||
value_change = curr_value - last_value
|
||||
value_change_pct = (value_change / last_value) * 100
|
||||
sign = "+" if value_change >= 0 else ""
|
||||
lines.append(f"• Value Change: {sign}${value_change:.2f} ({sign}{value_change_pct:.2f}%)")
|
||||
|
||||
# Hedge PnL trend
|
||||
last_hedge_pnl = last_closed.get('hedge_pnl_realized', 0)
|
||||
curr_hedge_equity = current_open.get('hedge_equity_usd', 0)
|
||||
if last_hedge_pnl != 0 and curr_hedge_equity != 0:
|
||||
hedge_trend = curr_hedge_equity - last_hedge_pnl
|
||||
sign = "+" if hedge_trend >= 0 else ""
|
||||
lines.append(f"• Hedge PnL Trend: ${last_hedge_pnl:.2f} -> ${curr_hedge_equity:.2f} ({sign}${hedge_trend:.2f})")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class PositionMonitor:
|
||||
"""Monitors hedge_status.json for changes"""
|
||||
|
||||
def __init__(self, json_file_path: str, state_file_path: str):
|
||||
self.json_file_path = json_file_path
|
||||
self.state_file_path = state_file_path
|
||||
self.last_known_data = []
|
||||
self.last_file_hash = ""
|
||||
self.state = self.load_state()
|
||||
|
||||
def load_state(self) -> Dict[str, Any]:
|
||||
"""Load monitor state from file"""
|
||||
try:
|
||||
if os.path.exists(self.state_file_path):
|
||||
with open(self.state_file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load state file: {e}")
|
||||
|
||||
return {
|
||||
"last_known_open_positions": [],
|
||||
"last_processed_timestamp": 0,
|
||||
"last_file_hash": ""
|
||||
}
|
||||
|
||||
def save_state(self):
|
||||
"""Save monitor state to file"""
|
||||
try:
|
||||
with open(self.state_file_path, 'w') as f:
|
||||
json.dump(self.state, f, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not save state file: {e}")
|
||||
|
||||
def get_file_hash(self, data: List[Dict]) -> str:
|
||||
"""Generate hash of file content to detect changes"""
|
||||
content = json.dumps(data, sort_keys=True)
|
||||
return hashlib.md5(content.encode()).hexdigest()
|
||||
|
||||
def safe_read_json(self) -> List[Dict]:
|
||||
"""Safely read JSON file with retry logic"""
|
||||
attempts = 0
|
||||
while attempts < 3:
|
||||
try:
|
||||
if not os.path.exists(self.json_file_path):
|
||||
logger.warning(f"JSON file not found: {self.json_file_path}")
|
||||
return []
|
||||
|
||||
with open(self.json_file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"Attempt {attempts + 1}: Error reading JSON file: {e}")
|
||||
time.sleep(1)
|
||||
attempts += 1
|
||||
|
||||
logger.error("Failed to read JSON file after 3 attempts")
|
||||
return []
|
||||
|
||||
def extract_notification_data(self, data: List[Dict]) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""Extract last closed and current open positions"""
|
||||
current_open = None
|
||||
last_closed = None
|
||||
|
||||
# Find current open position
|
||||
for item in data:
|
||||
if item.get('status') == 'OPEN':
|
||||
current_open = item
|
||||
break
|
||||
|
||||
# Find most recent closed position
|
||||
closed_positions = [item for item in data if item.get('status') == 'CLOSED']
|
||||
if closed_positions:
|
||||
# Sort by timestamp_open (descending) to get most recent
|
||||
closed_positions.sort(key=lambda x: x.get('timestamp_open', 0), reverse=True)
|
||||
last_closed = closed_positions[0]
|
||||
|
||||
return last_closed, current_open
|
||||
|
||||
def check_for_changes(self) -> bool:
|
||||
"""Check if there are changes requiring notification"""
|
||||
current_data = self.safe_read_json()
|
||||
|
||||
if not current_data:
|
||||
return False
|
||||
|
||||
# Check if file content actually changed
|
||||
current_hash = self.get_file_hash(current_data)
|
||||
if current_hash == self.state.get("last_file_hash", ""):
|
||||
return False
|
||||
|
||||
# Extract positions
|
||||
last_closed, current_open = self.extract_notification_data(current_data)
|
||||
|
||||
if not current_open:
|
||||
# No open position, nothing to notify about
|
||||
return False
|
||||
|
||||
current_open_id = current_open.get('token_id')
|
||||
last_known_opens = self.state.get("last_known_open_positions", [])
|
||||
|
||||
# Check if this is a new open position
|
||||
if current_open_id not in last_known_opens:
|
||||
# New position detected!
|
||||
self.last_known_data = current_data
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_notification_data(self) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""Get data for notification"""
|
||||
current_data = self.safe_read_json()
|
||||
return self.extract_notification_data(current_data)
|
||||
|
||||
def update_state(self):
|
||||
"""Update internal state after notification"""
|
||||
current_data = self.safe_read_json()
|
||||
if current_data:
|
||||
_, current_open = self.extract_notification_data(current_data)
|
||||
|
||||
if current_open:
|
||||
# Update state with current open positions
|
||||
self.state["last_known_open_positions"] = [current_open.get('token_id')]
|
||||
self.state["last_processed_timestamp"] = int(time.time())
|
||||
self.state["last_file_hash"] = self.get_file_hash(current_data)
|
||||
self.save_state()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main monitoring loop"""
|
||||
logger.info("🤖 Telegram Monitor Starting...")
|
||||
|
||||
notifier = None
|
||||
if not TELEGRAM_ENABLED:
|
||||
logger.info("📵 Telegram notifications disabled (TELEGRAM_MONITOR_ENABLED=False)")
|
||||
else:
|
||||
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
|
||||
logger.error("❌ Telegram enabled but missing BOT_TOKEN or CHAT_ID")
|
||||
return
|
||||
|
||||
# Initialize notifier and test connection
|
||||
notifier = TelegramNotifier(TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
||||
if not notifier.test_connection():
|
||||
logger.error("❌ Telegram connection failed - check token and network")
|
||||
return
|
||||
|
||||
logger.info(f"✅ Telegram connection established to chat ID: {TELEGRAM_CHAT_ID}")
|
||||
|
||||
# Initialize monitor
|
||||
monitor = PositionMonitor(HEDGE_STATUS_FILE, TELEGRAM_STATE_FILE)
|
||||
|
||||
logger.info(f"Monitoring file: {HEDGE_STATUS_FILE}")
|
||||
logger.info(f"Check interval: {TELEGRAM_CHECK_INTERVAL} seconds")
|
||||
logger.info(f"State file: {TELEGRAM_STATE_FILE}")
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
if monitor.check_for_changes():
|
||||
logger.info("New position opening detected!")
|
||||
|
||||
if TELEGRAM_ENABLED and notifier:
|
||||
last_closed, current_open = monitor.get_notification_data()
|
||||
|
||||
if current_open:
|
||||
message = notifier.format_position_message(last_closed, current_open)
|
||||
success = notifier.send_message(message)
|
||||
|
||||
if success:
|
||||
monitor.update_state()
|
||||
logger.info(f"Notification sent for position {current_open.get('token_id')}")
|
||||
else:
|
||||
logger.error("Failed to send notification")
|
||||
else:
|
||||
logger.warning("Position change detected but no open position found")
|
||||
else:
|
||||
logger.info("Telegram disabled or notifier not available - skipping notification")
|
||||
monitor.update_state() # Still update state to avoid loops
|
||||
else:
|
||||
logger.debug("No changes detected")
|
||||
|
||||
time.sleep(TELEGRAM_CHECK_INTERVAL)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Error in monitoring loop: {e}")
|
||||
time.sleep(TELEGRAM_CHECK_INTERVAL) # Continue after error
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Shutting down Telegram Monitor...")
|
||||
|
||||
logger.info("Telegram Monitor stopped")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
7
florida/telegram_monitor_state.json
Normal file
7
florida/telegram_monitor_state.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"last_known_open_positions": [
|
||||
5180968
|
||||
],
|
||||
"last_processed_timestamp": 1766874912,
|
||||
"last_file_hash": "beda382fdbca542dc8357002b4527702"
|
||||
}
|
||||
49
florida/tests/backtest/analyze_results.py
Normal file
49
florida/tests/backtest/analyze_results.py
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
import csv
|
||||
import sys
|
||||
import os
|
||||
|
||||
def analyze():
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
results_file = os.path.join(current_dir, "optimization_results.csv")
|
||||
|
||||
if not os.path.exists(results_file):
|
||||
print(f"File not found: {results_file}")
|
||||
return
|
||||
|
||||
print(f"Analyzing {results_file}...")
|
||||
|
||||
best_pnl = -float('inf')
|
||||
best_config = None
|
||||
|
||||
rows = []
|
||||
|
||||
with open(results_file, 'r') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
rows.append(row)
|
||||
|
||||
pnl = float(row['TOTAL_PNL'])
|
||||
uni_fees = float(row['UNI_FEES'])
|
||||
hl_pnl = float(row['HL_PNL'])
|
||||
|
||||
print(f"Config: Range={row['RANGE_WIDTH_PCT']}, Thresh={row['BASE_REBALANCE_THRESHOLD_PCT']} | PnL: ${pnl:.2f} (Fees: ${uni_fees:.2f}, Hedge: ${hl_pnl:.2f})")
|
||||
|
||||
if pnl > best_pnl:
|
||||
best_pnl = pnl
|
||||
best_config = row
|
||||
|
||||
print("\n" + "="*40)
|
||||
print(f"🏆 BEST CONFIGURATION")
|
||||
print("="*40)
|
||||
if best_config:
|
||||
print(f"Range Width: {float(best_config['RANGE_WIDTH_PCT'])*100:.2f}%")
|
||||
print(f"Rebalance Thresh: {float(best_config['BASE_REBALANCE_THRESHOLD_PCT'])*100:.0f}%")
|
||||
print(f"Total PnL: ${float(best_config['TOTAL_PNL']):.2f}")
|
||||
print(f" > Uni Fees: ${float(best_config['UNI_FEES']):.2f}")
|
||||
print(f" > Hedge PnL: ${float(best_config['HL_PNL']):.2f}")
|
||||
else:
|
||||
print("No valid results found.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze()
|
||||
312
florida/tests/backtest/backtester.py
Normal file
312
florida/tests/backtest/backtester.py
Normal file
@ -0,0 +1,312 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
import csv
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# Add project root to path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(os.path.dirname(current_dir))
|
||||
sys.path.append(project_root)
|
||||
|
||||
from tests.backtest.mocks import MockExchangeState, MockWeb3, MockExchangeAPI, MockInfo, MockContract
|
||||
|
||||
# Setup Logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s')
|
||||
logger = logging.getLogger("BACKTESTER")
|
||||
|
||||
class Backtester:
|
||||
def __init__(self, book_file, trades_file, config_overrides=None):
|
||||
self.book_file = book_file
|
||||
self.trades_file = trades_file
|
||||
self.config_overrides = config_overrides or {}
|
||||
self.events = []
|
||||
self.state = MockExchangeState()
|
||||
|
||||
# Mocks
|
||||
self.mock_web3 = MockWeb3(self.state)
|
||||
self.mock_hl_api = MockExchangeAPI(self.state)
|
||||
self.mock_hl_info = MockInfo(self.state)
|
||||
|
||||
# Components (Lazy loaded)
|
||||
self.manager = None
|
||||
self.hedger = None
|
||||
|
||||
def load_data(self):
|
||||
logger.info("Loading Market Data...")
|
||||
# Load Book
|
||||
with open(self.book_file, 'r') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
self.events.append({
|
||||
"type": "BOOK",
|
||||
"ts": int(row['timestamp_ms']),
|
||||
"data": row
|
||||
})
|
||||
|
||||
# Load Trades
|
||||
# (Optional: Trades are useful for market impact, but for basic PnL tracking
|
||||
# based on mid-price, Book is sufficient. Loading trades just to advance time)
|
||||
with open(self.trades_file, 'r') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
self.events.append({
|
||||
"type": "TRADE",
|
||||
"ts": int(row['timestamp_ms']),
|
||||
"data": row
|
||||
})
|
||||
|
||||
# Sort by Timestamp
|
||||
self.events.sort(key=lambda x: x['ts'])
|
||||
logger.info(f"Loaded {len(self.events)} events.")
|
||||
|
||||
def patch_and_init(self):
|
||||
logger.info("Initializing Logic...")
|
||||
|
||||
# --- PATCH MANAGER ---
|
||||
# We need to patch clp_manager.Web3 to return our MockWeb3
|
||||
# And os.environ for config
|
||||
|
||||
with patch.dict(os.environ, {
|
||||
"TARGET_DEX": "PANCAKESWAP_BNB", # Example
|
||||
"MAIN_WALLET_PRIVATE_KEY": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"BNB_RPC_URL": "http://mock",
|
||||
"HEDGER_PRIVATE_KEY": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"MAIN_WALLET_ADDRESS": "0xMyWallet"
|
||||
}):
|
||||
import clp_manager
|
||||
import clp_hedger
|
||||
|
||||
# Apply Config Overrides
|
||||
if self.config_overrides:
|
||||
logger.info(f"Applying Config Overrides: {self.config_overrides}")
|
||||
for k, v in self.config_overrides.items():
|
||||
# Patch Manager
|
||||
if hasattr(clp_manager, k):
|
||||
setattr(clp_manager, k, v)
|
||||
# Patch Hedger
|
||||
if hasattr(clp_hedger, k):
|
||||
setattr(clp_hedger, k, v)
|
||||
|
||||
# 1. Init Manager
|
||||
# clp_manager.main() connects to Web3. We need to inject our mock.
|
||||
# Since clp_manager creates w3 inside main(), we can't inject easily without patching Web3 class.
|
||||
|
||||
self.manager_module = clp_manager
|
||||
self.hedger_module = clp_hedger
|
||||
|
||||
def run(self):
|
||||
self.load_data()
|
||||
self.patch_and_init()
|
||||
|
||||
# MOCK TIME
|
||||
start_time = self.events[0]['ts'] / 1000.0
|
||||
|
||||
# STATUS FILE MOCK
|
||||
self.status_memory = [] # List[Dict]
|
||||
|
||||
def mock_load_status():
|
||||
logger.info(f"MOCK LOAD STATUS: Found {len(self.status_memory)} items")
|
||||
return self.status_memory
|
||||
|
||||
def mock_save_status(data):
|
||||
logger.info(f"MOCK SAVE STATUS: Saving {len(data)} items")
|
||||
self.status_memory = data
|
||||
|
||||
def mock_hedger_scan():
|
||||
return []
|
||||
|
||||
# We need to globally patch time.time and the Libraries
|
||||
web3_class_mock = MagicMock(return_value=self.mock_web3)
|
||||
web3_class_mock.to_wei = self.mock_web3.to_wei
|
||||
web3_class_mock.from_wei = self.mock_web3.from_wei
|
||||
web3_class_mock.is_address = self.mock_web3.is_address
|
||||
web3_class_mock.to_checksum_address = lambda x: x
|
||||
|
||||
# Mock Web3.keccak to return correct topics
|
||||
def mock_keccak(text=None, hexstr=None):
|
||||
# Known Topics
|
||||
if text == "Transfer(address,address,uint256)":
|
||||
return bytes.fromhex("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
|
||||
if text == "IncreaseLiquidity(uint256,uint128,uint256,uint256)":
|
||||
return bytes.fromhex("7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde")
|
||||
if text == "DecreaseLiquidity(uint256,uint128,uint256,uint256)":
|
||||
# 0x26f6a048ee9138f2c0ce266f322cb99228e8d619ae2bff30c67f8dcf9d2377b4
|
||||
return bytes.fromhex("26f6a048ee9138f2c0ce266f322cb99228e8d619ae2bff30c67f8dcf9d2377b4")
|
||||
if text == "Collect(uint256,address,uint256,uint256)":
|
||||
# 0x70935338e69775456a85ddef226c395fb668b63fa0115f5f206227278f746d4d
|
||||
return bytes.fromhex("70935338e69775456a85ddef226c395fb668b63fa0115f5f206227278f746d4d")
|
||||
|
||||
return b'\x00'*32
|
||||
|
||||
web3_class_mock.keccak = MagicMock(side_effect=mock_keccak)
|
||||
|
||||
# Ensure environment is patched during the whole run
|
||||
env_patch = {
|
||||
"TARGET_DEX": "PANCAKESWAP_BNB",
|
||||
"MAIN_WALLET_PRIVATE_KEY": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"BNB_RPC_URL": "http://mock",
|
||||
"HEDGER_PRIVATE_KEY": "0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"MAIN_WALLET_ADDRESS": "0xMyWallet"
|
||||
}
|
||||
|
||||
with patch.dict(os.environ, env_patch), \
|
||||
patch('time.time', side_effect=lambda: self.state.current_time_ms / 1000.0), \
|
||||
patch('clp_manager.Web3', web3_class_mock), \
|
||||
patch('clp_hedger.Account.from_key', return_value=MagicMock(address="0xMyWallet")), \
|
||||
patch('clp_hedger.Exchange', return_value=self.mock_hl_api), \
|
||||
patch('clp_hedger.Info', return_value=self.mock_hl_info), \
|
||||
patch('clp_manager.load_status_data', side_effect=mock_load_status), \
|
||||
patch('clp_manager.save_status_data', side_effect=mock_save_status), \
|
||||
patch('clp_manager.clean_address', side_effect=lambda x: x), \
|
||||
patch('clp_hedger.glob.glob', return_value=[]):
|
||||
|
||||
# Initialize Hedger (It creates the classes in __init__)
|
||||
self.hedger = self.hedger_module.UnifiedHedger()
|
||||
|
||||
# Initialize Manager Components manually (simulate main setup)
|
||||
w3 = self.mock_web3
|
||||
account = MagicMock(address="0xMyWallet")
|
||||
npm = w3.eth.contract("0xNPM", [])
|
||||
factory = w3.eth.contract("0xFactory", [])
|
||||
router = w3.eth.contract("0xRouter", [])
|
||||
|
||||
# --- SIMULATION LOOP ---
|
||||
last_manager_tick = 0
|
||||
manager_interval = 60 * 1000
|
||||
|
||||
trade_count = len([e for e in self.events if e['type'] == "TRADE"])
|
||||
book_count = len([e for e in self.events if e['type'] == "BOOK"])
|
||||
logger.info(f"SIMULATION START: {len(self.events)} total events ({book_count} BOOK, {trade_count} TRADE)")
|
||||
|
||||
for event in self.events:
|
||||
self.state.current_time_ms = event['ts']
|
||||
|
||||
# Update Market
|
||||
if event['type'] == "BOOK":
|
||||
row = event['data']
|
||||
mid = Decimal(row['mid_price'])
|
||||
self.state.update_price("BNB", mid)
|
||||
|
||||
if event['type'] == "TRADE":
|
||||
self.state.process_trade(event['data'])
|
||||
|
||||
# Run Logic
|
||||
|
||||
# 1. Manager (Every X seconds)
|
||||
if self.state.current_time_ms - last_manager_tick > manager_interval:
|
||||
self.manager_module.run_tick(w3, account, npm, factory, router)
|
||||
last_manager_tick = self.state.current_time_ms
|
||||
|
||||
# SYNC MANAGER STATUS TO HEDGER
|
||||
for pos in self.status_memory:
|
||||
if pos.get('status') == 'OPEN' and pos.get('type') == 'AUTOMATIC':
|
||||
key = ("MOCK", pos['token_id'])
|
||||
if key not in self.hedger.strategies:
|
||||
self.hedger._init_single_strategy(key, pos, "BNB")
|
||||
else:
|
||||
self.hedger.strategy_states[key]['status'] = pos.get('status', 'OPEN')
|
||||
elif pos.get('status') == 'CLOSED':
|
||||
key = ("MOCK", pos['token_id'])
|
||||
if key in self.hedger.strategies:
|
||||
self.hedger.strategy_states[key]['status'] = 'CLOSED'
|
||||
|
||||
|
||||
# 2. Hedger (Every Tick/Event)
|
||||
self.hedger.run_tick()
|
||||
|
||||
# Finalize: Collect accrued fees from open positions
|
||||
logger.info(f"Finalizing... Checking {len(self.state.uni_positions)} open positions.")
|
||||
for token_id, pos in self.state.uni_positions.items():
|
||||
raw_owed0 = pos.get('tokensOwed0', 0)
|
||||
logger.info(f"DEBUG: Position {token_id} Raw TokensOwed0: {raw_owed0}")
|
||||
|
||||
owed0 = Decimal(raw_owed0) / Decimal(10**18)
|
||||
owed1 = Decimal(pos.get('tokensOwed1', 0)) / Decimal(10**18)
|
||||
|
||||
# Convert to USD
|
||||
price = self.state.prices.get("BNB", Decimal("0"))
|
||||
# Fee0 is USDT (USD), Fee1 is WBNB
|
||||
usd_val = owed0 + (owed1 * price)
|
||||
|
||||
if usd_val > 0:
|
||||
self.state.uni_fees_collected += usd_val
|
||||
logger.info(f"Finalizing Open Position {token_id}: Accrued Fees ${usd_val:.2f}")
|
||||
|
||||
logger.info("Backtest Complete.")
|
||||
logger.info(f"Final Uni Fees: {self.state.uni_fees_collected}")
|
||||
logger.info(f"Final HL PnL: {self.state.hl_realized_pnl - self.state.hl_fees_paid}")
|
||||
|
||||
def calculate_final_nav(self):
|
||||
"""Calculates total Net Asset Value (USD) at the end of simulation."""
|
||||
total_usd = Decimal("0")
|
||||
|
||||
# 1. Wallet Balances
|
||||
# We assume T0=USDT, T1=WBNB for this profile
|
||||
price = self.state.prices.get("BNB", Decimal("0"))
|
||||
|
||||
for sym, bal in self.state.wallet_balances.items():
|
||||
if sym in ["USDC", "USDT"]:
|
||||
total_usd += bal
|
||||
elif sym in ["BNB", "WBNB", "NATIVE"]:
|
||||
total_usd += bal * price
|
||||
elif sym in ["ETH", "WETH"]:
|
||||
# If ETH price available? We mocked update_price("BNB") only.
|
||||
# Assuming ETH price static or 0 if not tracked
|
||||
eth_price = self.state.prices.get("ETH", Decimal("0"))
|
||||
total_usd += bal * eth_price
|
||||
|
||||
# 2. Uniswap Positions (Liquidity Value)
|
||||
# Value = Amount0 * Price0 + Amount1 * Price1
|
||||
# We need to calculate amounts from liquidity & current price
|
||||
import math
|
||||
# Helper to get amounts from liquidity
|
||||
def get_amounts(liquidity, sqrt_price_x96, tick_lower, tick_upper):
|
||||
# Simplified: Use the amounts we stored at mint time?
|
||||
# No, that's initial. We need current value.
|
||||
# But calculating precise amounts from liquidity/sqrtPrice requires complex math.
|
||||
# For approximation, we can look at what the manager logged as "Deposited"
|
||||
# if price hasn't moved much, or implement full liquidity math.
|
||||
|
||||
# Since implementing full math here is complex, let's use a simplified approach:
|
||||
# If we are in range, we have a mix.
|
||||
# If out of range, we have 100% of one token.
|
||||
|
||||
# Better: The Mock 'mint' stored initial amounts.
|
||||
# We can adjust by price ratio? No, IL is non-linear.
|
||||
|
||||
# Let's use the 'decrease_liquidity' logic mock if available?
|
||||
# Or just assume Liquidity Value = Initial Value + PnL (Fees) - IL.
|
||||
|
||||
# For this MVP, let's just count the Fees collected (Realized) + Initial Capital (Wallet).
|
||||
# BUT we spent wallet funds to open LP.
|
||||
# So Wallet is LOW. LP has Value.
|
||||
|
||||
# We MUST value the LP.
|
||||
# Let's approximate:
|
||||
# Value = Liquidity / (something) ...
|
||||
# Actually, `clp_manager.py` calculates `actual_value` on entry.
|
||||
# We can track `entry_value` in the position state.
|
||||
return Decimal("0") # Placeholder if we can't calc easily
|
||||
|
||||
# 3. Hyperliquid Positions (Unrealized PnL + Margin)
|
||||
hl_equity = self.state.hl_balances.get("USDC", 0) # Margin
|
||||
for sym, pos in self.state.hl_positions.items():
|
||||
hl_equity += pos['unrealized_pnl']
|
||||
|
||||
total_usd += hl_equity
|
||||
|
||||
return total_usd
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage:
|
||||
# python tests/backtest/backtester.py market_data/BNB_raw_20251230_book.csv market_data/BNB_raw_20251230_trades.csv
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python backtester.py <book_csv> <trades_csv>")
|
||||
else:
|
||||
bt = Backtester(sys.argv[1], sys.argv[2])
|
||||
bt.run()
|
||||
82
florida/tests/backtest/grid_search.py
Normal file
82
florida/tests/backtest/grid_search.py
Normal file
@ -0,0 +1,82 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
import csv
|
||||
import itertools
|
||||
from decimal import Decimal
|
||||
|
||||
# Add project root to path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(os.path.dirname(current_dir))
|
||||
sys.path.append(project_root)
|
||||
|
||||
from tests.backtest.backtester import Backtester
|
||||
|
||||
def main():
|
||||
# Grid Parameters
|
||||
# We want to optimize:
|
||||
# 1. RANGE_WIDTH_PCT: How wide is the LP position? (e.g. 0.01 = +/-1%, 0.05 = +/-5%)
|
||||
# 2. BASE_REBALANCE_THRESHOLD_PCT: When do we hedge? (e.g. 0.10 = 10% delta drift, 0.20 = 20%)
|
||||
|
||||
param_grid = {
|
||||
"RANGE_WIDTH_PCT": [0.005, 0.01, 0.025, 0.05],
|
||||
"BASE_REBALANCE_THRESHOLD_PCT": [0.01, 0.05]
|
||||
}
|
||||
|
||||
keys, values = zip(*param_grid.items())
|
||||
combinations = [dict(zip(keys, v)) for v in itertools.product(*values)]
|
||||
|
||||
results = []
|
||||
|
||||
book_file = os.path.join(project_root, "market_data", "BNB_raw_20251230_book.csv")
|
||||
trades_file = os.path.join(project_root, "market_data", "BNB_raw_20251230_trades.csv")
|
||||
|
||||
print(f"Starting Grid Search with {len(combinations)} combinations...")
|
||||
|
||||
for idx, config in enumerate(combinations):
|
||||
print(f"\n--- Run {idx+1}/{len(combinations)}: {config} ---")
|
||||
|
||||
# Initialize Backtester with overrides
|
||||
bt = Backtester(book_file, trades_file, config_overrides=config)
|
||||
|
||||
try:
|
||||
bt.run()
|
||||
|
||||
# Collect Metrics
|
||||
uni_fees = bt.state.uni_fees_collected
|
||||
hl_realized = bt.state.hl_realized_pnl - bt.state.hl_fees_paid
|
||||
|
||||
# HL Unrealized
|
||||
hl_unrealized = sum(p['unrealized_pnl'] for p in bt.state.hl_positions.values())
|
||||
|
||||
# Total PnL (Yield + Hedge Result) - Ignoring IL for now (Mock limitation)
|
||||
total_pnl = uni_fees + hl_realized + hl_unrealized
|
||||
|
||||
result = {
|
||||
**config,
|
||||
"UNI_FEES": float(uni_fees),
|
||||
"HL_REALIZED": float(hl_realized),
|
||||
"HL_UNREALIZED": float(hl_unrealized),
|
||||
"TOTAL_PNL": float(total_pnl)
|
||||
}
|
||||
results.append(result)
|
||||
print(f"Result: {result}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Run failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Save Results
|
||||
out_file = os.path.join(current_dir, "optimization_results.csv")
|
||||
keys = list(combinations[0].keys()) + ["UNI_FEES", "HL_REALIZED", "HL_UNREALIZED", "TOTAL_PNL"]
|
||||
|
||||
with open(out_file, 'w', newline='') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=keys)
|
||||
writer.writeheader()
|
||||
writer.writerows(results)
|
||||
|
||||
print(f"\nGrid Search Complete. Results saved to {out_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
607
florida/tests/backtest/mocks.py
Normal file
607
florida/tests/backtest/mocks.py
Normal file
@ -0,0 +1,607 @@
|
||||
|
||||
import time
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Any, Optional
|
||||
from hexbytes import HexBytes
|
||||
|
||||
logger = logging.getLogger("BACKTEST_MOCK")
|
||||
|
||||
class MockExchangeState:
|
||||
"""
|
||||
Central source of truth for the simulation.
|
||||
Acts as the "Blockchain" and the "CEX Engine".
|
||||
"""
|
||||
def __init__(self):
|
||||
self.current_time_ms = 0
|
||||
self.prices = {} # symbol -> price (Decimal)
|
||||
self.ticks = {} # symbol -> current tick (int)
|
||||
|
||||
# Balances
|
||||
self.wallet_balances = {
|
||||
"NATIVE": Decimal("100.0"), # ETH or BNB
|
||||
"ETH": Decimal("100.0"),
|
||||
"USDC": Decimal("100000.0"),
|
||||
"WETH": Decimal("100.0"),
|
||||
"BNB": Decimal("100.0"),
|
||||
"WBNB": Decimal("100.0"),
|
||||
"USDT": Decimal("100000.0")
|
||||
}
|
||||
self.hl_balances = {"USDC": Decimal("10000.0"), "USDT": Decimal("10000.0")}
|
||||
self.hl_positions = {} # symbol -> {size, entry_px, unrealized_pnl}
|
||||
|
||||
# Uniswap Positions
|
||||
self.next_token_id = 1000
|
||||
self.uni_positions = {} # token_id -> {liquidity, tickLower, tickUpper, ...}
|
||||
|
||||
# Hyperliquid Orders
|
||||
self.hl_orders = [] # List of {oid, coin, side, limitPx, sz, timestamp}
|
||||
self.next_oid = 1
|
||||
|
||||
# Fees/PnL Tracking
|
||||
self.uni_fees_collected = Decimal("0.0")
|
||||
self.hl_fees_paid = Decimal("0.0")
|
||||
self.hl_realized_pnl = Decimal("0.0")
|
||||
|
||||
# Pending TXs (Simulating Mempool/Execution)
|
||||
self.pending_txs = []
|
||||
|
||||
def update_price(self, symbol: str, price: Decimal, tick: int = 0):
|
||||
self.prices[symbol] = price
|
||||
if tick:
|
||||
self.ticks[symbol] = tick
|
||||
|
||||
# Update PnL for open positions
|
||||
if symbol in self.hl_positions:
|
||||
pos = self.hl_positions[symbol]
|
||||
size = pos['size']
|
||||
if size != 0:
|
||||
# Long: (Price - Entry) * Size
|
||||
# Short: (Entry - Price) * abs(Size)
|
||||
if size > 0:
|
||||
pos['unrealized_pnl'] = (price - pos['entry_px']) * size
|
||||
else:
|
||||
pos['unrealized_pnl'] = (pos['entry_px'] - price) * abs(size)
|
||||
|
||||
def process_trade(self, trade_data):
|
||||
"""Simulate fee accumulation from market trades."""
|
||||
# trade_data: {price, size, ...} from CSV
|
||||
try:
|
||||
# DEBUG: Confirm entry
|
||||
if getattr(self, '_debug_trade_entry', False) is False:
|
||||
logger.info(f"DEBUG: Processing Trades... Positions: {len(self.uni_positions)}")
|
||||
self._debug_trade_entry = True
|
||||
|
||||
price = Decimal(trade_data['price'])
|
||||
size = Decimal(trade_data['size']) # Amount in Base Token (BNB)
|
||||
|
||||
# Simple Fee Logic:
|
||||
# If trade price is within a position's range, it earns fees.
|
||||
# Fee = Volume * 0.05% (Fee Tier) * MarketShare (Assume 10%)
|
||||
|
||||
fee_tier = Decimal("0.0005") # 0.05%
|
||||
|
||||
# Realistic Market Share Simulation
|
||||
# Assume Pool Depth in active ticks is $5,000,000
|
||||
# Our Position is approx $1,000
|
||||
# Share = 1,000 / 5,000,000 = 0.0002 (0.02%)
|
||||
market_share = Decimal("0.0002")
|
||||
|
||||
import math
|
||||
# Current Tick of the trade
|
||||
try:
|
||||
# price = 1.0001^tick -> tick = log(price) / log(1.0001)
|
||||
# Note: If T0=USDT, Price T0/T1 = 1/Price_USD.
|
||||
# But our TickLower/Upper in Mock are generated based on T0=USDT logic?
|
||||
# clp_manager calculates ticks based on price_from_tick logic.
|
||||
|
||||
# If T0=USDT, T1=WBNB. Price (T0/T1) ~ 0.00116.
|
||||
# Ticks will be negative.
|
||||
# Trade Price is 860.
|
||||
# We need to invert price to get tick if the pool is inverted.
|
||||
# For BNB tests, we know T0=USDT.
|
||||
|
||||
# Invert price for tick calc
|
||||
inv_price = Decimal("1") / price
|
||||
tick = int(math.log(float(inv_price)) / math.log(1.0001))
|
||||
except:
|
||||
tick = 0
|
||||
|
||||
# Iterate all OPEN Uniswap positions
|
||||
for token_id, pos in self.uni_positions.items():
|
||||
# Check Range
|
||||
if pos['tickLower'] <= tick <= pos['tickUpper']:
|
||||
vol_usd = price * size
|
||||
fee_earned = vol_usd * fee_tier * market_share
|
||||
pos['tokensOwed0'] = pos.get('tokensOwed0', 0) + int(fee_earned * 10**18)
|
||||
|
||||
# Debug logging (Disabled for production runs)
|
||||
# if getattr(self, '_debug_fee_log_count', 0) < 10:
|
||||
# logger.info(f"DEBUG: Fee Earned! Tick {tick} inside {pos['tickLower']} <-> {pos['tickUpper']}. Fee: {fee_earned}")
|
||||
# self._debug_fee_log_count = getattr(self, '_debug_fee_log_count', 0) + 1
|
||||
|
||||
else:
|
||||
# Debug logging (Disabled)
|
||||
# if getattr(self, '_debug_tick_log_count', 0) < 10:
|
||||
# logger.info(f"DEBUG: Trade Tick {tick} OUTSIDE {pos['tickLower']} <-> {pos['tickUpper']} (Price: {price})")
|
||||
# self._debug_tick_log_count = getattr(self, '_debug_tick_log_count', 0) + 1
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing trade: {e}")
|
||||
|
||||
def process_transaction(self, tx_data):
|
||||
"""Executes a pending transaction and updates state."""
|
||||
func = tx_data['func']
|
||||
args = tx_data['args']
|
||||
value = tx_data.get('value', 0)
|
||||
contract_addr = tx_data['contract']
|
||||
|
||||
logger.info(f"PROCESSING TX: {func} on {contract_addr} Val: {value}")
|
||||
|
||||
# 1. DEPOSIT (Wrap)
|
||||
if func == "deposit":
|
||||
# Wrap Native -> Wrapped
|
||||
# Assume contract_addr is the wrapped token
|
||||
# In mocks, we map address to symbol
|
||||
# But we don't have the instance here easily, so we guess.
|
||||
# If value > 0, it's a wrap.
|
||||
amount = Decimal(value) / Decimal(10**18)
|
||||
if self.wallet_balances.get("NATIVE", 0) >= amount:
|
||||
self.wallet_balances["NATIVE"] -= amount
|
||||
# Find which token this is.
|
||||
# If it's the WETH/WBNB address
|
||||
target_token = "WBNB" # Default assumption for this test profile
|
||||
if contract_addr == "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1": target_token = "WETH"
|
||||
|
||||
self.wallet_balances[target_token] = self.wallet_balances.get(target_token, 0) + amount
|
||||
logger.info(f"Wrapped {amount} NATIVE to {target_token}")
|
||||
else:
|
||||
logger.error("Insufficient NATIVE balance for wrap")
|
||||
|
||||
# 2. MINT (New Position)
|
||||
elif func == "mint":
|
||||
# Params: (token0, token1, fee, tickLower, tickUpper, amount0Desired, amount1Desired, ...)
|
||||
params = args[0]
|
||||
token0 = params[0]
|
||||
token1 = params[1]
|
||||
amount0 = Decimal(params[5]) / Decimal(10**18) # Approx decimals
|
||||
amount1 = Decimal(params[6]) / Decimal(10**18)
|
||||
|
||||
logger.info(f"Minting Position: {amount0} T0, {amount1} T1")
|
||||
|
||||
# Deduct Balances (Simplified: assuming we have enough)
|
||||
# In real mock we should check symbols
|
||||
self.wallet_balances["WBNB"] = max(0, self.wallet_balances.get("WBNB", 0) - amount0)
|
||||
self.wallet_balances["USDT"] = max(0, self.wallet_balances.get("USDT", 0) - amount1)
|
||||
|
||||
# Create Position
|
||||
token_id = self.next_token_id
|
||||
self.next_token_id += 1
|
||||
|
||||
self.uni_positions[token_id] = {
|
||||
"token0": token0,
|
||||
"token1": token1,
|
||||
"tickLower": params[3],
|
||||
"tickUpper": params[4],
|
||||
"liquidity": 1000000 # Dummy liquidity
|
||||
}
|
||||
logger.info(f"Minted TokenID: {token_id}")
|
||||
self.last_minted_token_id = token_id
|
||||
return token_id # Helper return
|
||||
|
||||
# 3. COLLECT (Fees)
|
||||
elif func == "collect":
|
||||
# Params: (params) -> (tokenId, recipient, amount0Max, amount1Max)
|
||||
params = args[0]
|
||||
token_id = params[0]
|
||||
|
||||
# Retrieve accumulated fees
|
||||
if token_id in self.uni_positions:
|
||||
pos = self.uni_positions[token_id]
|
||||
owed1 = Decimal(pos.get('tokensOwed1', 0)) / Decimal(10**18)
|
||||
owed0 = Decimal(pos.get('tokensOwed0', 0)) / Decimal(10**18)
|
||||
|
||||
# Reset
|
||||
pos['tokensOwed1'] = 0
|
||||
pos['tokensOwed0'] = 0
|
||||
|
||||
fee0 = owed0
|
||||
fee1 = owed1
|
||||
else:
|
||||
fee0 = Decimal("0")
|
||||
fee1 = Decimal("0")
|
||||
|
||||
self.wallet_balances["WBNB"] = self.wallet_balances.get("WBNB", 0) + fee0
|
||||
self.wallet_balances["USDT"] = self.wallet_balances.get("USDT", 0) + fee1
|
||||
|
||||
# Calculate USD Value of fees
|
||||
# T0 = USDT, T1 = WBNB
|
||||
# fee0 is USDT, fee1 is WBNB
|
||||
price = self.state.prices.get("BNB", Decimal("0"))
|
||||
usd_val = fee0 + (fee1 * price)
|
||||
|
||||
self.uni_fees_collected += usd_val
|
||||
logger.info(f"Collected Fees for {token_id}: {fee0:.4f} T0 + {fee1:.4f} T1 = ${usd_val:.2f}")
|
||||
|
||||
# 4. SWAP (ExactInputSingle)
|
||||
elif func == "exactInputSingle":
|
||||
# Params: (params) -> struct
|
||||
# struct ExactInputSingleParams {
|
||||
# address tokenIn; address tokenOut; fee; recipient; deadline; amountIn; amountOutMinimum; sqrtPriceLimitX96;
|
||||
# }
|
||||
# Since args[0] is the struct (tuple/list)
|
||||
# We need to guess indices or check ABI.
|
||||
# Standard: tokenIn(0), tokenOut(1), fee(2), recipient(3), deadline(4), amountIn(5), minOut(6)
|
||||
params = args[0]
|
||||
token_in_addr = params[0]
|
||||
token_out_addr = params[1]
|
||||
amount_in_wei = params[5]
|
||||
|
||||
# Map address to symbol
|
||||
# We can't access contract instance here easily, so use known map or iterate
|
||||
sym_map = {
|
||||
"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1": "WETH",
|
||||
"0xaf88d065e77c8cC2239327C5EDb3A432268e5831": "USDC",
|
||||
"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c": "WBNB",
|
||||
"0x55d398326f99059fF775485246999027B3197955": "USDT"
|
||||
}
|
||||
|
||||
sym_in = sym_map.get(token_in_addr, "UNKNOWN")
|
||||
sym_out = sym_map.get(token_out_addr, "UNKNOWN")
|
||||
|
||||
amount_in = Decimal(amount_in_wei) / Decimal(10**18) # Approx
|
||||
if sym_in in ["USDC", "USDT"]: amount_in = Decimal(amount_in_wei) / Decimal(10**18) # Mock usually 18 dec for simplicity unless specified
|
||||
|
||||
# Price calculation
|
||||
# If swapping Base (WBNB) -> Quote (USDT), Price is ~300
|
||||
# If Quote -> Base, Price is 1/300
|
||||
price = self.prices.get("BNB", Decimal("300"))
|
||||
|
||||
amount_out = 0
|
||||
if sym_in == "WBNB" and sym_out == "USDT":
|
||||
amount_out = amount_in * price
|
||||
elif sym_in == "USDT" and sym_out == "WBNB":
|
||||
amount_out = amount_in / price
|
||||
else:
|
||||
amount_out = amount_in # 1:1 fallback
|
||||
|
||||
self.wallet_balances[sym_in] = max(0, self.wallet_balances.get(sym_in, 0) - amount_in)
|
||||
self.wallet_balances[sym_out] = self.wallet_balances.get(sym_out, 0) + amount_out
|
||||
|
||||
logger.info(f"SWAP: {amount_in:.4f} {sym_in} -> {amount_out:.4f} {sym_out}")
|
||||
|
||||
def match_orders(self):
|
||||
"""Simple order matching against current price."""
|
||||
# In a real backtest, we'd check High/Low of the candle or Orderbook depth.
|
||||
# Here we assume perfect liquidity at current price for simplicity,
|
||||
# or implement simple slippage.
|
||||
pass
|
||||
|
||||
# --- WEB3 MOCKS ---
|
||||
|
||||
class MockContractFunction:
|
||||
def __init__(self, name, parent_contract, state: MockExchangeState):
|
||||
self.name = name
|
||||
self.contract = parent_contract
|
||||
self.state = state
|
||||
self.args = []
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
return self
|
||||
|
||||
def call(self, transaction=None):
|
||||
# SIMULATE READS
|
||||
if self.name == "slot0":
|
||||
# Determine Pair
|
||||
symbol = "BNB" if "BNB" in self.state.prices else "ETH"
|
||||
price = self.state.prices.get(symbol, Decimal("300"))
|
||||
|
||||
# For BNB Chain, T0 is USDT (0x55d), T1 is WBNB (0xbb4)
|
||||
# Price of T0 (USDT) in T1 (WBNB) is 1 / Price
|
||||
if symbol == "BNB":
|
||||
if price > 0:
|
||||
price = Decimal("1") / price
|
||||
else:
|
||||
price = Decimal("0")
|
||||
|
||||
sqrt_px = price.sqrt() * (2**96)
|
||||
|
||||
# Tick
|
||||
import math
|
||||
try:
|
||||
# price = 1.0001^tick
|
||||
tick = int(math.log(float(price)) / math.log(1.0001))
|
||||
except:
|
||||
tick = 0
|
||||
|
||||
return (int(sqrt_px), tick, 0, 0, 0, 0, True)
|
||||
|
||||
if self.name == "positions":
|
||||
token_id = self.args[0]
|
||||
if token_id in self.state.uni_positions:
|
||||
p = self.state.uni_positions[token_id]
|
||||
return (0, "", p['token0'], p['token1'], 500, p['tickLower'], p['tickUpper'], p['liquidity'],
|
||||
0, 0, p.get('tokensOwed0', 0), p.get('tokensOwed1', 0))
|
||||
else:
|
||||
raise Exception("Position not found")
|
||||
|
||||
if self.name == "balanceOf":
|
||||
addr = self.args[0]
|
||||
# Hacky: detect token by contract address
|
||||
symbol = self.contract.symbol_map.get(self.contract.address, "UNKNOWN")
|
||||
return int(self.state.wallet_balances.get(symbol, 0) * (10**self.contract.decimals_val))
|
||||
|
||||
if self.name == "decimals":
|
||||
return self.contract.decimals_val
|
||||
|
||||
if self.name == "symbol":
|
||||
return self.contract.symbol_val
|
||||
|
||||
if self.name == "allowance":
|
||||
return 10**50 # Infinite allowance
|
||||
|
||||
# Pool Methods
|
||||
if self.name == "tickSpacing":
|
||||
return 10
|
||||
if self.name == "token0":
|
||||
# Return USDT for BNB profile (0x55d < 0xbb4)
|
||||
if "BNB" in self.state.prices:
|
||||
return "0x55d398326f99059fF775485246999027B3197955"
|
||||
return "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1" # WETH
|
||||
|
||||
if self.name == "token1":
|
||||
# Return WBNB for BNB profile
|
||||
if "BNB" in self.state.prices:
|
||||
return "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
|
||||
return "0xaf88d065e77c8cC2239327C5EDb3A432268e5831" # USDC
|
||||
|
||||
if self.name == "fee":
|
||||
return 500
|
||||
if self.name == "liquidity":
|
||||
return 1000000000000000000
|
||||
|
||||
return None
|
||||
|
||||
def build_transaction(self, tx_params):
|
||||
# Queue Transaction for Execution
|
||||
self.state.pending_txs.append({
|
||||
"func": self.name,
|
||||
"args": self.args,
|
||||
"contract": self.contract.address,
|
||||
"value": tx_params.get("value", 0)
|
||||
})
|
||||
|
||||
return {"data": "0xMOCK", "to": self.contract.address, "value": tx_params.get("value", 0)}
|
||||
|
||||
def estimate_gas(self, tx_params):
|
||||
return 100000
|
||||
|
||||
class MockContract:
|
||||
def __init__(self, address, abi, state: MockExchangeState):
|
||||
self.address = address
|
||||
self.abi = abi
|
||||
self.state = state
|
||||
self.functions = self
|
||||
|
||||
# Meta for simulation
|
||||
self.symbol_map = {
|
||||
"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1": "WETH",
|
||||
"0xaf88d065e77c8cC2239327C5EDb3A432268e5831": "USDC",
|
||||
# BNB Chain
|
||||
"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c": "WBNB", # FIXED: Was BNB, but address is WBNB
|
||||
"0x55d398326f99059fF775485246999027B3197955": "USDT"
|
||||
}
|
||||
symbol = self.symbol_map.get(address, "MOCK")
|
||||
is_stable = symbol in ["USDC", "USDT"]
|
||||
self.decimals_val = 18 if not is_stable else 6
|
||||
if symbol == "USDT": self.decimals_val = 18 # BNB USDT is 18
|
||||
self.symbol_val = symbol
|
||||
|
||||
def __getattr__(self, name):
|
||||
return MockContractFunction(name, self, self.state)
|
||||
|
||||
class MockEth:
|
||||
def __init__(self, state: MockExchangeState):
|
||||
self.state = state
|
||||
self.chain_id = 42161
|
||||
self.max_priority_fee = 100000000
|
||||
|
||||
def contract(self, address, abi):
|
||||
return MockContract(address, abi, self.state)
|
||||
|
||||
def get_block(self, block_identifier):
|
||||
return {'baseFeePerGas': 100000000, 'timestamp': self.state.current_time_ms // 1000}
|
||||
|
||||
def get_balance(self, address):
|
||||
# Native balance
|
||||
return int(self.state.wallet_balances.get("NATIVE", 0) * 10**18)
|
||||
|
||||
def get_transaction_count(self, account, block_identifier=None):
|
||||
return 1
|
||||
|
||||
def send_raw_transaction(self, raw_tx):
|
||||
# EXECUTE PENDING TX
|
||||
if self.state.pending_txs:
|
||||
tx_data = self.state.pending_txs.pop(0)
|
||||
res = self.state.process_transaction(tx_data)
|
||||
|
||||
return b'\x00' * 32
|
||||
|
||||
def wait_for_transaction_receipt(self, tx_hash, timeout=120):
|
||||
# MOCK LOGS GENERATION
|
||||
# We assume every tx is a successful Mint for now to test the flow
|
||||
# In a real engine we'd inspect the tx data to determine the event
|
||||
|
||||
# Transfer Topic: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
|
||||
transfer_topic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
||||
# IncreaseLiquidity Topic: 0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde
|
||||
increase_topic = "0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde"
|
||||
|
||||
# Use the actual minted ID if available, else dummy
|
||||
real_token_id = getattr(self.state, 'last_minted_token_id', 123456)
|
||||
token_id_hex = hex(real_token_id)[2:].zfill(64)
|
||||
|
||||
# Liquidity + Amount0 + Amount1
|
||||
# 1000 Liquidity, 1 ETH (18 dec), 3000 USDC (6 dec)
|
||||
# 1 ETH = 1e18, 3000 USDC = 3e9
|
||||
data_liq = "00000000000000000000000000000000000000000000000000000000000003e8" # 1000
|
||||
data_amt0 = "0000000000000000000000000000000000000000000000000de0b6b3a7640000" # 1e18
|
||||
data_amt1 = "00000000000000000000000000000000000000000000000000000000b2d05e00" # 3e9
|
||||
|
||||
class Receipt:
|
||||
status = 1
|
||||
blockNumber = 12345
|
||||
logs = [
|
||||
{
|
||||
'topics': [
|
||||
HexBytes(transfer_topic),
|
||||
HexBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), # From
|
||||
HexBytes("0x0000000000000000000000000000000000000000000000000000000000000000"), # To
|
||||
HexBytes("0x" + token_id_hex) # TokenID
|
||||
],
|
||||
'data': b''
|
||||
},
|
||||
{
|
||||
'topics': [
|
||||
HexBytes(increase_topic)
|
||||
],
|
||||
'data': bytes.fromhex(data_liq + data_amt0 + data_amt1)
|
||||
}
|
||||
]
|
||||
|
||||
return Receipt()
|
||||
|
||||
class MockWeb3:
|
||||
def __init__(self, state: MockExchangeState):
|
||||
self.eth = MockEth(state)
|
||||
self.middleware_onion = type('obj', (object,), {'inject': lambda *args, **kwargs: None})
|
||||
|
||||
def is_connected(self):
|
||||
return True
|
||||
def to_checksum_address(self, addr):
|
||||
return addr
|
||||
def is_address(self, addr):
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def to_wei(val, unit):
|
||||
if unit == 'gwei': return int(val * 10**9)
|
||||
if unit == 'ether': return int(val * 10**18)
|
||||
return int(val)
|
||||
|
||||
@staticmethod
|
||||
def from_wei(val, unit):
|
||||
if unit == 'gwei': return Decimal(val) / Decimal(10**9)
|
||||
if unit == 'ether': return Decimal(val) / Decimal(10**18)
|
||||
return Decimal(val)
|
||||
|
||||
@staticmethod
|
||||
def keccak(text=None):
|
||||
return b'\x00'*32 # Dummy
|
||||
|
||||
# --- HYPERLIQUID MOCKS ---
|
||||
|
||||
class MockInfo:
|
||||
def __init__(self, state: MockExchangeState):
|
||||
self.state = state
|
||||
|
||||
def all_mids(self):
|
||||
# Return string prices as per API
|
||||
return {k: str(v) for k, v in self.state.prices.items()}
|
||||
|
||||
def user_state(self, address):
|
||||
positions = []
|
||||
for sym, pos in self.state.hl_positions.items():
|
||||
positions.append({
|
||||
"position": {
|
||||
"coin": sym,
|
||||
"szi": str(pos['size']),
|
||||
"entryPx": str(pos['entry_px']),
|
||||
"unrealizedPnl": str(pos['unrealized_pnl'])
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
"marginSummary": {
|
||||
"accountValue": str(self.state.hl_balances.get("USDC", 0)),
|
||||
"totalMarginUsed": "0",
|
||||
"totalNtlPos": "0",
|
||||
"totalRawUsd": "0"
|
||||
},
|
||||
"assetPositions": positions
|
||||
}
|
||||
|
||||
def open_orders(self, address):
|
||||
return self.state.hl_orders
|
||||
|
||||
def user_fills(self, address):
|
||||
return [] # TODO: Store fills in state
|
||||
|
||||
def l2_snapshot(self, coin):
|
||||
# Generate artificial orderbook around mid price
|
||||
price = self.state.prices.get(coin, Decimal("0"))
|
||||
if price == 0: return {'levels': [[], []]}
|
||||
|
||||
# Spread 0.05%
|
||||
bid = price * Decimal("0.99975")
|
||||
ask = price * Decimal("1.00025")
|
||||
|
||||
return {
|
||||
"levels": [
|
||||
[{"px": str(bid), "sz": "100.0", "n": 1}], # Bids
|
||||
[{"px": str(ask), "sz": "100.0", "n": 1}] # Asks
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class MockExchangeAPI:
|
||||
def __init__(self, state: MockExchangeState):
|
||||
self.state = state
|
||||
|
||||
def order(self, coin, is_buy, sz, limit_px, order_type, reduce_only=False):
|
||||
# Execute immediately for IO/Market, or add to book
|
||||
# Simulating Fill
|
||||
price = Decimal(str(limit_px))
|
||||
size = Decimal(str(sz))
|
||||
cost = price * size
|
||||
|
||||
# Fee (Taker 0.035%)
|
||||
fee = cost * Decimal("0.00035")
|
||||
self.state.hl_fees_paid += fee
|
||||
|
||||
# Update Position
|
||||
if coin not in self.state.hl_positions:
|
||||
self.state.hl_positions[coin] = {'size': Decimal(0), 'entry_px': Decimal(0), 'unrealized_pnl': Decimal(0)}
|
||||
|
||||
pos = self.state.hl_positions[coin]
|
||||
current_size = pos['size']
|
||||
|
||||
# Update Entry Price (Weighted Average)
|
||||
# New Entry = (OldSize * OldEntry + NewSize * NewPrice) / (OldSize + NewSize)
|
||||
signed_size = size if is_buy else -size
|
||||
new_size = current_size + signed_size
|
||||
|
||||
if new_size == 0:
|
||||
pos['entry_px'] = 0
|
||||
elif (current_size > 0 and signed_size > 0) or (current_size < 0 and signed_size < 0):
|
||||
# Increasing position
|
||||
val_old = abs(current_size) * pos['entry_px']
|
||||
val_new = size * price
|
||||
pos['entry_px'] = (val_old + val_new) / abs(new_size)
|
||||
else:
|
||||
# Closing/Reducing - Entry Price doesn't change, PnL is realized
|
||||
# Fraction closed
|
||||
closed_ratio = min(abs(signed_size), abs(current_size)) / abs(current_size)
|
||||
# This logic is simplified, real PnL logic is complex
|
||||
|
||||
pos['size'] = new_size
|
||||
|
||||
logger.info(f"MOCK HL EXEC: {coin} {'BUY' if is_buy else 'SELL'} {size} @ {price}. New Size: {new_size}")
|
||||
|
||||
return {"status": "ok", "response": {"data": {"statuses": [{"filled": {"oid": 123}}]}}}
|
||||
|
||||
def cancel(self, coin, oid):
|
||||
self.state.hl_orders = [o for o in self.state.hl_orders if o['oid'] != oid]
|
||||
return {"status": "ok"}
|
||||
25
florida/tests/backtest/run_backtest.py
Normal file
25
florida/tests/backtest/run_backtest.py
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add project root to path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(os.path.dirname(current_dir))
|
||||
sys.path.append(project_root)
|
||||
|
||||
from tests.backtest.backtester import Backtester
|
||||
|
||||
def main():
|
||||
book_file = os.path.join(project_root, "market_data", "BNB_raw_20251230_book.csv")
|
||||
trades_file = os.path.join(project_root, "market_data", "BNB_raw_20251230_trades.csv")
|
||||
|
||||
if not os.path.exists(book_file):
|
||||
print(f"Error: Data file not found: {book_file}")
|
||||
return
|
||||
|
||||
print(f"Starting Backtest on {book_file}...")
|
||||
bt = Backtester(book_file, trades_file)
|
||||
bt.run()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
262
florida/tools/README_GIT_AGENT.md
Normal file
262
florida/tools/README_GIT_AGENT.md
Normal file
@ -0,0 +1,262 @@
|
||||
# Git Agent for Uniswap Auto CLP
|
||||
|
||||
## Overview
|
||||
Automated backup and version control system for your Uniswap Auto CLP trading bot.
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### 1. Initialize Repository
|
||||
```bash
|
||||
# Navigate to project directory
|
||||
cd K:\Projects\uniswap_auto_clp
|
||||
|
||||
# Create initial commit
|
||||
python tools\git_agent.py --init
|
||||
|
||||
# Add and push initial setup
|
||||
git add .
|
||||
git commit -m "🎯 Initial commit: Uniswap Auto CLP system"
|
||||
git remote add origin https://git.kapuscinski.pl/ditus/uniswap_auto_clp.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### 2. Create First Backup
|
||||
```bash
|
||||
# Test backup creation
|
||||
python tools\git_agent.py --backup
|
||||
```
|
||||
|
||||
### 3. Check Status
|
||||
```bash
|
||||
# View current status
|
||||
python tools\git_agent.py --status
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `tools/agent_config.json` as needed:
|
||||
|
||||
```json
|
||||
{
|
||||
"backup": {
|
||||
"enabled": true,
|
||||
"frequency_hours": 1,
|
||||
"keep_max_count": 100,
|
||||
"push_to_remote": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Commands
|
||||
|
||||
### Manual Operations
|
||||
```bash
|
||||
# Create backup now
|
||||
python tools\git_agent.py --backup
|
||||
|
||||
# Check status
|
||||
python tools\git_agent.py --status
|
||||
|
||||
# Cleanup old backups
|
||||
python tools\git_agent.py --cleanup
|
||||
|
||||
# Initialize repository (one-time)
|
||||
python tools\git_agent.py --init
|
||||
```
|
||||
|
||||
### Automated Scheduling
|
||||
|
||||
#### Windows Task Scheduler
|
||||
```powershell
|
||||
# Create hourly task
|
||||
schtasks /create /tn "Git Backup" /tr "python tools\git_agent.py --backup" /sc hourly
|
||||
```
|
||||
|
||||
#### Linux Cron (if needed)
|
||||
```bash
|
||||
# Add to crontab
|
||||
0 * * * * cd /path/to/project && python tools/git_agent.py --backup
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### Branch Strategy
|
||||
- **main branch**: Your manual development (you control pushes)
|
||||
- **backup-* branches**: Automatic hourly backups (agent managed)
|
||||
|
||||
### Backup Process
|
||||
1. **Hourly**: Agent checks for file changes
|
||||
2. **Creates backup branch**: Named `backup-YYYY-MM-DD-HH`
|
||||
3. **Commits changes**: With detailed file and parameter tracking
|
||||
4. **Pushes to remote**: Automatic backup to Gitea
|
||||
5. **Cleans up**: Keeps only last 100 backups
|
||||
|
||||
### Backup Naming
|
||||
```
|
||||
backup-2025-01-15-14 # 2 PM backup on Jan 15, 2025
|
||||
backup-2025-01-15-15 # 3 PM backup
|
||||
backup-2025-01-15-16 # 4 PM backup
|
||||
```
|
||||
|
||||
### Commit Messages
|
||||
Agent creates detailed commit messages showing:
|
||||
- Files changed with status icons
|
||||
- Parameter changes with percentage differences
|
||||
- Security validation confirmation
|
||||
- Timestamp and backup number
|
||||
|
||||
## Security
|
||||
|
||||
### What's Excluded
|
||||
✅ Private keys and tokens (`.env` files)
|
||||
✅ Log files (`*.log`)
|
||||
✅ State files (`hedge_status.json`)
|
||||
✅ Temporary files
|
||||
|
||||
### What's Included
|
||||
✅ All code changes
|
||||
✅ Configuration modifications
|
||||
✅ Documentation updates
|
||||
✅ Parameter tracking
|
||||
|
||||
## Emergency Recovery
|
||||
|
||||
### Quick Rollback
|
||||
```bash
|
||||
# List recent backups
|
||||
python tools\git_agent.py --status
|
||||
|
||||
# Switch to backup
|
||||
git checkout backup-2025-01-15-14
|
||||
|
||||
# Copy files to main
|
||||
git checkout main -- .
|
||||
git commit -m "🔄 Emergency restore from backup-2025-01-15-14"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### File Recovery
|
||||
```bash
|
||||
# Restore specific file from backup
|
||||
git checkout backup-2025-01-15-14 -- path/to/file.py
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Backup Health
|
||||
```bash
|
||||
# Check backup count and status
|
||||
python tools\git_agent.py --status
|
||||
|
||||
# Expected output:
|
||||
# 📊 Git Agent Status:
|
||||
# Current Branch: main
|
||||
# Backup Count: 47
|
||||
# Has Changes: false
|
||||
# Remote Connected: true
|
||||
# Last Backup: backup-2025-01-15-16
|
||||
```
|
||||
|
||||
### Manual Cleanup
|
||||
```bash
|
||||
# Remove old backups (keeps last 100)
|
||||
python tools\git_agent.py --cleanup
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "Configuration file not found"
|
||||
```bash
|
||||
# Ensure agent_config.json exists in tools/ directory
|
||||
ls tools/agent_config.json
|
||||
```
|
||||
|
||||
#### "Git command failed"
|
||||
```bash
|
||||
# Check Git installation and repository status
|
||||
git status
|
||||
git --version
|
||||
```
|
||||
|
||||
#### "Remote connection failed"
|
||||
```bash
|
||||
# Verify Gitea URL and credentials
|
||||
git remote -v
|
||||
ping git.kapuscinski.pl
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
Edit `agent_config.json`:
|
||||
```json
|
||||
{
|
||||
"logging": {
|
||||
"enabled": true,
|
||||
"log_level": "DEBUG"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then check `git_agent.log` in project root.
|
||||
|
||||
## Integration with Trading Bot
|
||||
|
||||
### Parameter Changes
|
||||
Agent automatically tracks changes to:
|
||||
- `TARGET_INVESTMENT_VALUE_USDC`
|
||||
- `RANGE_WIDTH_PCT`
|
||||
- `SLIPPAGE_TOLERANCE`
|
||||
- `LEVERAGE`
|
||||
- `CHECK_INTERVAL`
|
||||
- `PRICE_BUFFER_PCT`
|
||||
|
||||
### Backup Triggers
|
||||
Consider manual backups when:
|
||||
- Changing trading strategy parameters
|
||||
- Updating risk management settings
|
||||
- Before major system changes
|
||||
- After successful backtesting
|
||||
|
||||
```bash
|
||||
# Manual backup before important changes
|
||||
python tools\git_agent.py --backup
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Development Workflow
|
||||
1. **Work on main branch** for normal development
|
||||
2. **Manual commits** for your changes
|
||||
3. **Agent handles backups** automatically
|
||||
4. **Manual push** to main when ready
|
||||
|
||||
### Backup Management
|
||||
- **100 backup limit** = ~4 days of hourly coverage
|
||||
- **Automatic cleanup** maintains repository size
|
||||
- **Remote storage** provides offsite backup
|
||||
|
||||
### Security Reminders
|
||||
- **Never commit private keys** (automatically excluded)
|
||||
- **Check .gitignore** if adding sensitive files
|
||||
- **Review backup commits** for accidental secrets
|
||||
|
||||
## Support
|
||||
|
||||
### Log Files
|
||||
- `git_agent.log`: Agent activity and errors
|
||||
- Check logs for troubleshooting issues
|
||||
|
||||
### Repository Structure
|
||||
```
|
||||
tools/
|
||||
├── git_agent.py # Main automation script
|
||||
├── agent_config.json # Configuration settings
|
||||
├── git_utils.py # Git operations
|
||||
├── backup_manager.py # Backup branch logic
|
||||
├── change_detector.py # Change analysis
|
||||
├── cleanup_manager.py # Backup rotation
|
||||
└── commit_formatter.py # Message formatting
|
||||
```
|
||||
|
||||
This automated backup system ensures your trading bot code is always versioned and recoverable, while keeping your main development workflow clean and manual.
|
||||
35
florida/tools/agent_config.json
Normal file
35
florida/tools/agent_config.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"gitea": {
|
||||
"server_url": "https://git.kapuscinski.pl",
|
||||
"username": "ditus",
|
||||
"repository": "uniswap_auto_clp",
|
||||
"token": "b24fc3203597b2bdcb2f2da6634c618"
|
||||
},
|
||||
"backup": {
|
||||
"enabled": true,
|
||||
"frequency_hours": 1,
|
||||
"branch_prefix": "backup-",
|
||||
"push_to_remote": true,
|
||||
"keep_max_count": 100,
|
||||
"cleanup_with_backup": true,
|
||||
"detailed_commit_messages": true
|
||||
},
|
||||
"main_branch": {
|
||||
"manual_pushes_only": true,
|
||||
"auto_commits": false,
|
||||
"protect_from_agent": true,
|
||||
"name": "main"
|
||||
},
|
||||
"change_tracking": {
|
||||
"method": "commit_message",
|
||||
"include_file_diffs": true,
|
||||
"track_parameter_changes": true,
|
||||
"format": "detailed",
|
||||
"security_validation": false
|
||||
},
|
||||
"logging": {
|
||||
"enabled": true,
|
||||
"log_file": "git_agent.log",
|
||||
"log_level": "INFO"
|
||||
}
|
||||
}
|
||||
107
florida/tools/analyze_pool_data.py
Normal file
107
florida/tools/analyze_pool_data.py
Normal 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()
|
||||
89
florida/tools/backup_manager.py
Normal file
89
florida/tools/backup_manager.py
Normal file
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Backup Manager for Git Agent
|
||||
Handles backup branch creation and management
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, Any
|
||||
|
||||
class BackupManager:
|
||||
"""Manages backup branch operations"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any], logger: logging.Logger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.backup_config = config.get('backup', {})
|
||||
self.prefix = self.backup_config.get('branch_prefix', 'backup-')
|
||||
|
||||
def create_backup_branch(self) -> str:
|
||||
"""Create a new backup branch with timestamp"""
|
||||
timestamp = datetime.now(timezone.utc)
|
||||
branch_name = f"{self.prefix}{timestamp.strftime('%Y-%m-%d-%H')}"
|
||||
|
||||
# Get current directory from git utils
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Create backup branch
|
||||
import subprocess
|
||||
try:
|
||||
# Create and checkout new branch
|
||||
result = subprocess.run(
|
||||
['git', 'checkout', '-b', branch_name],
|
||||
cwd=current_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
self.logger.info(f"✅ Created backup branch: {branch_name}")
|
||||
return branch_name
|
||||
else:
|
||||
# Branch might already exist, just checkout
|
||||
result = subprocess.run(
|
||||
['git', 'checkout', branch_name],
|
||||
cwd=current_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
self.logger.info(f"✅ Using existing backup branch: {branch_name}")
|
||||
return branch_name
|
||||
else:
|
||||
self.logger.error(f"❌ Failed to create/checkout backup branch: {result.stderr}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Exception creating backup branch: {e}")
|
||||
return None
|
||||
|
||||
def get_backup_count(self) -> int:
|
||||
"""Get current number of backup branches"""
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['git', 'branch', '-a'],
|
||||
cwd=current_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
branches = result.stdout.strip().split('\n')
|
||||
backup_branches = [
|
||||
b.strip().replace('* ', '').replace('remotes/origin/', '')
|
||||
for b in branches
|
||||
if b.strip() and self.prefix in b
|
||||
]
|
||||
return len(backup_branches)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Error counting backup branches: {e}")
|
||||
|
||||
return 0
|
||||
171
florida/tools/calculate_market_data.py
Normal file
171
florida/tools/calculate_market_data.py
Normal 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()
|
||||
230
florida/tools/change_detector.py
Normal file
230
florida/tools/change_detector.py
Normal file
@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Change Detector for Git Agent
|
||||
Detects and analyzes file changes for detailed commit messages
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
from decimal import Decimal
|
||||
|
||||
class ChangeDetector:
|
||||
"""Detects and categorizes file changes"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any], logger: logging.Logger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def detect_changes(self) -> Dict[str, Any]:
|
||||
"""Detect all changes in the repository"""
|
||||
try:
|
||||
# Get changed files
|
||||
changed_files = self._get_changed_files()
|
||||
|
||||
if not changed_files:
|
||||
return {
|
||||
'has_changes': False,
|
||||
'files': [],
|
||||
'categories': {},
|
||||
'parameter_changes': {}
|
||||
}
|
||||
|
||||
# Analyze changes
|
||||
file_details = []
|
||||
categories = {
|
||||
'python': [],
|
||||
'config': [],
|
||||
'docs': [],
|
||||
'other': []
|
||||
}
|
||||
parameter_changes = {}
|
||||
|
||||
for file_path in changed_files:
|
||||
details = self._analyze_file_changes(file_path)
|
||||
file_details.append(details)
|
||||
|
||||
# Categorize file
|
||||
category = self._categorize_file(file_path)
|
||||
categories[category].append(details)
|
||||
|
||||
# Track parameter changes for Python files
|
||||
if category == 'python':
|
||||
params = self._extract_parameter_changes(file_path, details.get('diff', ''))
|
||||
if params:
|
||||
parameter_changes[file_path] = params
|
||||
|
||||
return {
|
||||
'has_changes': True,
|
||||
'files': file_details,
|
||||
'categories': categories,
|
||||
'parameter_changes': parameter_changes
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Error detecting changes: {e}")
|
||||
return {
|
||||
'has_changes': False,
|
||||
'files': [],
|
||||
'categories': {},
|
||||
'parameter_changes': {},
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _get_changed_files(self) -> List[str]:
|
||||
"""Get list of changed files using git status"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['git', 'status', '--porcelain'],
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return []
|
||||
|
||||
files = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if line.strip():
|
||||
# Extract filename (remove status codes)
|
||||
filename = line.strip()[2:] if len(line.strip()) > 2 else line.strip()
|
||||
if filename and filename not in ['.git', '__pycache__']:
|
||||
files.append(filename)
|
||||
|
||||
return files
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting changed files: {e}")
|
||||
return []
|
||||
|
||||
def _analyze_file_changes(self, file_path: str) -> Dict[str, Any]:
|
||||
"""Analyze changes for a specific file"""
|
||||
try:
|
||||
# Get diff
|
||||
result = subprocess.run(
|
||||
['git', 'diff', '--', file_path],
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
diff = result.stdout if result.returncode == 0 else ''
|
||||
|
||||
# Get file status
|
||||
status_result = subprocess.run(
|
||||
['git', 'status', '--porcelain', '--', file_path],
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
status = 'modified'
|
||||
if status_result.returncode == 0 and status_result.stdout.strip():
|
||||
status_line = status_result.stdout.strip()[0]
|
||||
if status_line == 'A':
|
||||
status = 'added'
|
||||
elif status_line == 'D':
|
||||
status = 'deleted'
|
||||
elif status_line == '??':
|
||||
status = 'untracked'
|
||||
|
||||
# Count lines changed
|
||||
lines_added = diff.count('\n+') - diff.count('\n++') # Exclude +++ indicators
|
||||
lines_deleted = diff.count('\n-') - diff.count('\n--') # Exclude --- indicators
|
||||
|
||||
return {
|
||||
'path': file_path,
|
||||
'status': status,
|
||||
'lines_added': max(0, lines_added),
|
||||
'lines_deleted': max(0, lines_deleted),
|
||||
'diff': diff
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error analyzing {file_path}: {e}")
|
||||
return {
|
||||
'path': file_path,
|
||||
'status': 'error',
|
||||
'lines_added': 0,
|
||||
'lines_deleted': 0,
|
||||
'diff': '',
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def _categorize_file(self, file_path: str) -> str:
|
||||
"""Categorize file type"""
|
||||
if file_path.endswith('.py'):
|
||||
return 'python'
|
||||
elif file_path.endswith(('.json', '.yaml', '.yml', '.toml', '.ini')):
|
||||
return 'config'
|
||||
elif file_path.endswith(('.md', '.txt', '.rst')):
|
||||
return 'docs'
|
||||
else:
|
||||
return 'other'
|
||||
|
||||
def _extract_parameter_changes(self, file_path: str, diff: str) -> Dict[str, Any]:
|
||||
"""Extract parameter changes from Python files"""
|
||||
if not diff or not file_path.endswith('.py'):
|
||||
return {}
|
||||
|
||||
parameters = {}
|
||||
|
||||
# Common trading bot parameters to track
|
||||
param_patterns = {
|
||||
'TARGET_INVESTMENT_VALUE_USDC': r'(TARGET_INVESTMENT_VALUE_USDC)\s*=\s*(\d+)',
|
||||
'RANGE_WIDTH_PCT': r'(RANGE_WIDTH_PCT)\s*=\s*Decimal\("([^"]+)"\)',
|
||||
'SLIPPAGE_TOLERANCE': r'(SLIPPAGE_TOLERANCE)\s*=\s*Decimal\("([^"]+)"\)',
|
||||
'LEVERAGE': r'(LEVERAGE)\s*=\s*(\d+)',
|
||||
'MIN_THRESHOLD_ETH': r'(MIN_THRESHOLD_ETH)\s*=\s*Decimal\("([^"]+)"\)',
|
||||
'CHECK_INTERVAL': r'(CHECK_INTERVAL)\s*=\s*(\d+)',
|
||||
'PRICE_BUFFER_PCT': r'(PRICE_BUFFER_PCT)\s*=\s*Decimal\("([^"]+)"\)'
|
||||
}
|
||||
|
||||
for param_name, pattern in param_patterns.items():
|
||||
matches = re.findall(pattern, diff)
|
||||
if matches:
|
||||
# Find old and new values
|
||||
values = []
|
||||
for match in matches:
|
||||
if isinstance(match, tuple):
|
||||
values.append(match[1] if len(match) > 1 else match[0])
|
||||
else:
|
||||
values.append(match)
|
||||
|
||||
if len(values) >= 2:
|
||||
old_val = values[0]
|
||||
new_val = values[-1] # Last value is current
|
||||
|
||||
# Calculate percentage change for numeric values
|
||||
try:
|
||||
if '.' in old_val or '.' in new_val:
|
||||
old_num = float(old_val)
|
||||
new_num = float(new_val)
|
||||
if old_num != 0:
|
||||
pct_change = ((new_num - old_num) / abs(old_num)) * 100
|
||||
else:
|
||||
pct_change = 0
|
||||
else:
|
||||
old_num = int(old_val)
|
||||
new_num = int(new_val)
|
||||
if old_num != 0:
|
||||
pct_change = ((new_num - old_num) / abs(old_num)) * 100
|
||||
else:
|
||||
pct_change = 0
|
||||
except (ValueError, ZeroDivisionError):
|
||||
pct_change = 0
|
||||
|
||||
parameters[param_name] = {
|
||||
'old': old_val,
|
||||
'new': new_val,
|
||||
'pct_change': round(pct_change, 1)
|
||||
}
|
||||
|
||||
return parameters
|
||||
92
florida/tools/check_aerodrome_pool.py
Normal file
92
florida/tools/check_aerodrome_pool.py
Normal 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()
|
||||
103
florida/tools/check_arbitrum_pool.py
Normal file
103
florida/tools/check_arbitrum_pool.py
Normal file
@ -0,0 +1,103 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from web3 import Web3
|
||||
from dotenv import load_dotenv
|
||||
from decimal import Decimal
|
||||
|
||||
# Add project root
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
# Load Env
|
||||
load_dotenv()
|
||||
|
||||
RPC_URL = os.environ.get("MAINNET_RPC_URL") # Arbitrum RPC
|
||||
POOL_ADDRESS = "0xC6962004f452bE9203591991D15f6b388e09E8D0" # ARB/WETH 500
|
||||
|
||||
ERC20_ABI = [
|
||||
{"constant": True, "inputs": [], "name": "symbol", "outputs": [{"name": "", "type": "string"}], "payable": False, "stateMutability": "view", "type": "function"},
|
||||
{"constant": True, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": False, "stateMutability": "view", "type": "function"},
|
||||
{"constant": True, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": False, "stateMutability": "view", "type": "function"}
|
||||
]
|
||||
|
||||
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": "uint32", "name": "feeProtocol", "type": "uint32"}, {"internalType": "bool", "name": "unlocked", "type": "bool"}], "stateMutability": "view", "type": "function"},
|
||||
{"inputs": [], "name": "liquidity", "outputs": [{"internalType": "uint128", "name": "", "type": "uint128"}], "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"}
|
||||
]
|
||||
|
||||
def main():
|
||||
if not RPC_URL:
|
||||
print("Error: MAINNET_RPC_URL not found in .env")
|
||||
return
|
||||
|
||||
w3 = Web3(Web3.HTTPProvider(RPC_URL))
|
||||
if not w3.is_connected():
|
||||
print("Error: Could not connect to RPC")
|
||||
return
|
||||
|
||||
print(f"Connected to Arbitrum: {w3.eth.block_number}")
|
||||
|
||||
pool_contract = w3.eth.contract(address=POOL_ADDRESS, abi=POOL_ABI)
|
||||
|
||||
# 1. Metadata
|
||||
t0_addr = pool_contract.functions.token0().call()
|
||||
t1_addr = pool_contract.functions.token1().call()
|
||||
fee = pool_contract.functions.fee().call()
|
||||
|
||||
t0_contract = w3.eth.contract(address=t0_addr, abi=ERC20_ABI)
|
||||
t1_contract = w3.eth.contract(address=t1_addr, abi=ERC20_ABI)
|
||||
|
||||
t0_sym = t0_contract.functions.symbol().call()
|
||||
t1_sym = t1_contract.functions.symbol().call()
|
||||
t0_dec = t0_contract.functions.decimals().call()
|
||||
t1_dec = t1_contract.functions.decimals().call()
|
||||
|
||||
print(f"\nPool: {t0_sym} / {t1_sym} ({fee/10000}%)")
|
||||
print(f"Token0: {t0_sym} ({t0_addr}) - {t0_dec} dec")
|
||||
print(f"Token1: {t1_sym} ({t1_addr}) - {t1_dec} dec")
|
||||
|
||||
# 2. State
|
||||
slot0 = pool_contract.functions.slot0().call()
|
||||
sqrt_price = slot0[0]
|
||||
tick = slot0[1]
|
||||
liquidity = pool_contract.functions.liquidity().call()
|
||||
|
||||
print(f"\nState:")
|
||||
print(f"Liquidity: {liquidity}")
|
||||
print(f"Tick: {tick}")
|
||||
print(f"SqrtPriceX96: {sqrt_price}")
|
||||
|
||||
# 3. Price Calc
|
||||
# price = (sqrt / 2^96)^2
|
||||
p = (Decimal(sqrt_price) / Decimal(2**96)) ** 2
|
||||
|
||||
# Adjust for decimals: Price = raw_price * 10^(d0 - d1)
|
||||
adj_price = p * (Decimal(10) ** (t0_dec - t1_dec))
|
||||
inv_price = Decimal(1) / adj_price if adj_price > 0 else 0
|
||||
|
||||
print(f"\nPrices:")
|
||||
print(f"1 {t0_sym} = {adj_price:.6f} {t1_sym}")
|
||||
print(f"1 {t1_sym} = {inv_price:.6f} {t0_sym}")
|
||||
|
||||
# 4. TVL Estimation (Balances)
|
||||
t0_bal = t0_contract.functions.balanceOf(POOL_ADDRESS).call()
|
||||
t1_bal = t1_contract.functions.balanceOf(POOL_ADDRESS).call()
|
||||
|
||||
t0_human = Decimal(t0_bal) / Decimal(10**t0_dec)
|
||||
t1_human = Decimal(t1_bal) / Decimal(10**t1_dec)
|
||||
|
||||
print(f"\nTVL (Locked in Contract):")
|
||||
print(f"{t0_human:,.2f} {t0_sym}")
|
||||
print(f"{t1_human:,.2f} {t1_sym}")
|
||||
|
||||
# Assume WETH is approx 3350 (or whatever current market is, we can use slot0 price if one is stable)
|
||||
# If one is USD stable, we can calc total.
|
||||
# ARB / WETH. WETH is ~$3300.
|
||||
# Let's just output raw.
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
153
florida/tools/cleanup_manager.py
Normal file
153
florida/tools/cleanup_manager.py
Normal file
@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Cleanup Manager for Git Agent
|
||||
Manages backup branch rotation (keep last 100)
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
from typing import Dict, Any, List
|
||||
|
||||
class CleanupManager:
|
||||
"""Manages backup branch cleanup and rotation"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any], logger: logging.Logger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.backup_config = config.get('backup', {})
|
||||
self.prefix = self.backup_config.get('branch_prefix', 'backup-')
|
||||
self.max_backups = self.backup_config.get('keep_max_count', 100)
|
||||
self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def cleanup_old_backups(self) -> bool:
|
||||
"""Clean up old backup branches to keep only the last N"""
|
||||
try:
|
||||
# Get all backup branches
|
||||
backup_branches = self._get_backup_branches()
|
||||
|
||||
if len(backup_branches) <= self.max_backups:
|
||||
self.logger.info(f"✅ Backup count ({len(backup_branches)}) within limit ({self.max_backups})")
|
||||
return False # No cleanup needed
|
||||
|
||||
# Branches to delete (oldest ones)
|
||||
branches_to_delete = backup_branches[self.max_backups:]
|
||||
|
||||
if not branches_to_delete:
|
||||
return False
|
||||
|
||||
self.logger.info(f"🧹 Cleaning up {len(branches_to_delete)} old backup branches")
|
||||
|
||||
deleted_count = 0
|
||||
for branch in branches_to_delete:
|
||||
# Delete local branch
|
||||
if self._delete_local_branch(branch):
|
||||
# Delete remote branch
|
||||
if self._delete_remote_branch(branch):
|
||||
deleted_count += 1
|
||||
self.logger.debug(f" ✅ Deleted: {branch}")
|
||||
else:
|
||||
self.logger.warning(f" ⚠️ Local deleted, remote failed: {branch}")
|
||||
else:
|
||||
self.logger.warning(f" ❌ Failed to delete: {branch}")
|
||||
|
||||
if deleted_count > 0:
|
||||
self.logger.info(f"✅ Cleanup completed: deleted {deleted_count} old backup branches")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning("⚠️ No branches were successfully deleted")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Cleanup failed: {e}")
|
||||
return False
|
||||
|
||||
def _get_backup_branches(self) -> List[str]:
|
||||
"""Get all backup branches sorted by timestamp (newest first)"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['git', 'branch', '-a'],
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return []
|
||||
|
||||
branches = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if line.strip():
|
||||
# Clean up branch name
|
||||
branch = line.strip().replace('* ', '').replace('remotes/origin/', '')
|
||||
if branch.startswith(self.prefix):
|
||||
branches.append(branch)
|
||||
|
||||
# Sort by timestamp (extract from branch name)
|
||||
# Format: backup-YYYY-MM-DD-HH
|
||||
branches.sort(key=lambda x: x.replace(self.prefix, ''), reverse=True)
|
||||
return branches
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting backup branches: {e}")
|
||||
return []
|
||||
|
||||
def _delete_local_branch(self, branch_name: str) -> bool:
|
||||
"""Delete local branch"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['git', 'branch', '-D', branch_name],
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
else:
|
||||
self.logger.debug(f"Local delete failed for {branch_name}: {result.stderr}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Exception deleting local branch {branch_name}: {e}")
|
||||
return False
|
||||
|
||||
def _delete_remote_branch(self, branch_name: str) -> bool:
|
||||
"""Delete remote branch"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['git', 'push', 'origin', '--delete', branch_name],
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
else:
|
||||
# Might already be deleted remotely, that's ok
|
||||
if "not found" in result.stderr.lower() or "does not exist" in result.stderr.lower():
|
||||
return True
|
||||
self.logger.debug(f"Remote delete failed for {branch_name}: {result.stderr}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Exception deleting remote branch {branch_name}: {e}")
|
||||
return False
|
||||
|
||||
def get_cleanup_stats(self) -> Dict[str, Any]:
|
||||
"""Get statistics about backup cleanup"""
|
||||
backup_branches = self._get_backup_branches()
|
||||
current_count = len(backup_branches)
|
||||
|
||||
return {
|
||||
'current_backup_count': current_count,
|
||||
'max_allowed': self.max_backups,
|
||||
'cleanup_needed': current_count > self.max_backups,
|
||||
'branches_to_delete': max(0, current_count - self.max_backups),
|
||||
'newest_backup': backup_branches[0] if backup_branches else None,
|
||||
'oldest_backup': backup_branches[-1] if backup_branches else None
|
||||
}
|
||||
325
florida/tools/collect_fees_v2 copy.py
Normal file
325
florida/tools/collect_fees_v2 copy.py
Normal file
@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fee Collection & Position Recovery Script
|
||||
Collects all accumulated fees from Uniswap V3 positions
|
||||
|
||||
Usage:
|
||||
python collect_fees_v2.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import argparse
|
||||
|
||||
# Required libraries
|
||||
try:
|
||||
from web3 import Web3
|
||||
from eth_account import Account
|
||||
except ImportError as e:
|
||||
print(f"[ERROR] Missing required library: {e}")
|
||||
print("Please install with: pip install web3 eth-account python-dotenv")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
print("[WARNING] python-dotenv not found, using environment variables directly")
|
||||
def load_dotenv(override=True):
|
||||
pass
|
||||
|
||||
def setup_logging():
|
||||
"""Setup logging for fee collection"""
|
||||
import logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.StreamHandler(),
|
||||
logging.FileHandler('collect_fees.log', encoding='utf-8')
|
||||
]
|
||||
)
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
logger = setup_logging()
|
||||
|
||||
# --- Contract ABIs ---
|
||||
NONFUNGIBLE_POSITION_MANAGER_ABI = json.loads('''
|
||||
[
|
||||
{"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"}
|
||||
]
|
||||
''')
|
||||
|
||||
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"}
|
||||
]
|
||||
''')
|
||||
|
||||
def load_status_file():
|
||||
"""Load hedge status file"""
|
||||
status_file = "hedge_status.json"
|
||||
if not os.path.exists(status_file):
|
||||
logger.error(f"Status file {status_file} not found")
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(status_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading status file: {e}")
|
||||
return []
|
||||
|
||||
def from_wei(amount, decimals):
|
||||
"""Convert wei to human readable amount"""
|
||||
if amount is None:
|
||||
return 0
|
||||
return amount / (10**decimals)
|
||||
|
||||
def get_position_details(w3, npm_contract, token_id):
|
||||
"""Get detailed position information"""
|
||||
try:
|
||||
position_data = npm_contract.functions.positions(token_id).call()
|
||||
(nonce, operator, token0_address, token1_address, fee, tickLower, tickUpper,
|
||||
liquidity, feeGrowthInside0, feeGrowthInside1, tokensOwed0, tokensOwed1) = position_data
|
||||
|
||||
# Get token details
|
||||
token0_contract = w3.eth.contract(address=token0_address, abi=ERC20_ABI)
|
||||
token1_contract = w3.eth.contract(address=token1_address, abi=ERC20_ABI)
|
||||
|
||||
token0_symbol = token0_contract.functions.symbol().call()
|
||||
token1_symbol = token1_contract.functions.symbol().call()
|
||||
token0_decimals = token0_contract.functions.decimals().call()
|
||||
token1_decimals = token1_contract.functions.decimals().call()
|
||||
|
||||
return {
|
||||
"token0_address": token0_address,
|
||||
"token1_address": token1_address,
|
||||
"token0_symbol": token0_symbol,
|
||||
"token1_symbol": token1_symbol,
|
||||
"token0_decimals": token0_decimals,
|
||||
"token1_decimals": token1_decimals,
|
||||
"liquidity": liquidity,
|
||||
"tokensOwed0": tokensOwed0,
|
||||
"tokensOwed1": tokensOwed1
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting position {token_id} details: {e}")
|
||||
return None
|
||||
|
||||
def simulate_fees(w3, npm_contract, token_id):
|
||||
"""Simulate fee collection to get amounts without executing"""
|
||||
try:
|
||||
result = npm_contract.functions.collect(
|
||||
(token_id, "0x0000000000000000000000000000000000000000", 2**128-1, 2**128-1)
|
||||
).call()
|
||||
return result[0], result[1] # amount0, amount1
|
||||
except Exception as e:
|
||||
logger.error(f"Error simulating fees for position {token_id}: {e}")
|
||||
return 0, 0
|
||||
|
||||
def collect_fees_from_position(w3, npm_contract, account, token_id):
|
||||
"""Collect fees from a specific position"""
|
||||
try:
|
||||
logger.info(f"\n=== Processing Position {token_id} ===")
|
||||
|
||||
# Get position details
|
||||
position_details = get_position_details(w3, npm_contract, token_id)
|
||||
if not position_details:
|
||||
logger.error(f"Could not get details for position {token_id}")
|
||||
return False
|
||||
|
||||
logger.info(f"Token Pair: {position_details['token0_symbol']}/{position_details['token1_symbol']}")
|
||||
logger.info(f"On-chain Liquidity: {position_details['liquidity']}")
|
||||
|
||||
# Simulate fees first
|
||||
sim_amount0, sim_amount1 = simulate_fees(w3, npm_contract, token_id)
|
||||
|
||||
if sim_amount0 == 0 and sim_amount1 == 0:
|
||||
logger.info(f"No fees available for position {token_id}")
|
||||
return True
|
||||
|
||||
logger.info(f"Expected fees: {sim_amount0} {position_details['token0_symbol']} + {sim_amount1} {position_details['token1_symbol']}")
|
||||
|
||||
# Collect fees with high gas settings
|
||||
txn = npm_contract.functions.collect(
|
||||
(token_id, account.address, 2**128-1, 2**128-1)
|
||||
).build_transaction({
|
||||
'from': account.address,
|
||||
'nonce': w3.eth.get_transaction_count(account.address),
|
||||
'gas': 300000, # High gas limit
|
||||
'maxFeePerGas': w3.eth.gas_price * 4, # 4x gas price
|
||||
'maxPriorityFeePerGas': w3.eth.max_priority_fee * 3,
|
||||
'chainId': w3.eth.chain_id
|
||||
})
|
||||
|
||||
# Sign and send
|
||||
signed_txn = w3.eth.account.sign_transaction(txn, private_key=account.key)
|
||||
tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
||||
|
||||
logger.info(f"Collect fees sent: {tx_hash.hex()}")
|
||||
logger.info(f"Arbiscan: https://arbiscan.io/tx/{tx_hash.hex()}")
|
||||
|
||||
# Wait with extended timeout
|
||||
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=600)
|
||||
|
||||
if receipt.status == 1:
|
||||
logger.info(f"[SUCCESS] Fees collected from position {token_id}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"[ERROR] Fee collection failed for position {token_id}. Status: {receipt.status}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[ERROR] Fee collection failed for position {token_id}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Collect fees from Uniswap V3 positions')
|
||||
parser.add_argument('--id', type=int, help='Specific Position Token ID to collect fees from')
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info("=== Fee Collection Script v2 ===")
|
||||
logger.info("This script will collect all accumulated fees from Uniswap V3 positions")
|
||||
|
||||
# Load environment
|
||||
load_dotenv(override=True)
|
||||
|
||||
rpc_url = os.environ.get("MAINNET_RPC_URL")
|
||||
private_key = os.environ.get("MAIN_WALLET_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY")
|
||||
|
||||
if not rpc_url or not private_key:
|
||||
logger.error("[ERROR] Missing RPC URL or Private Key")
|
||||
logger.error("Please ensure MAINNET_RPC_URL and PRIVATE_KEY are set in your .env file")
|
||||
return
|
||||
|
||||
# Connect to Arbitrum
|
||||
try:
|
||||
w3 = Web3(Web3.HTTPProvider(rpc_url))
|
||||
if not w3.is_connected():
|
||||
logger.error("[ERROR] Failed to connect to Arbitrum RPC")
|
||||
return
|
||||
logger.info(f"[SUCCESS] Connected to Chain ID: {w3.eth.chain_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"[ERROR] Connection error: {e}")
|
||||
return
|
||||
|
||||
# Setup account and contracts
|
||||
try:
|
||||
account = Account.from_key(private_key)
|
||||
w3.eth.default_account = account.address
|
||||
logger.info(f"Wallet: {account.address}")
|
||||
|
||||
# Using string address format directly
|
||||
npm_address = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
|
||||
npm_contract = w3.eth.contract(address=npm_address, abi=NONFUNGIBLE_POSITION_MANAGER_ABI)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[ERROR] Account/Contract setup error: {e}")
|
||||
return
|
||||
|
||||
# Show current wallet balances
|
||||
try:
|
||||
eth_balance = w3.eth.get_balance(account.address)
|
||||
logger.info(f"ETH Balance: {eth_balance / 10**18:.6f} ETH")
|
||||
|
||||
# Check token balances using basic addresses
|
||||
try:
|
||||
weth_address = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
||||
weth_contract = w3.eth.contract(address=weth_address, abi=ERC20_ABI)
|
||||
weth_balance = weth_contract.functions.balanceOf(account.address).call()
|
||||
logger.info(f"WETH Balance: {weth_balance / 10**18:.6f} WETH")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
usdc_address = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
|
||||
usdc_contract = w3.eth.contract(address=usdc_address, abi=ERC20_ABI)
|
||||
usdc_balance = usdc_contract.functions.balanceOf(account.address).call()
|
||||
logger.info(f"USDC Balance: {usdc_balance / 10**6:.2f} USDC")
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not fetch balances: {e}")
|
||||
|
||||
# Load and process positions
|
||||
positions = load_status_file()
|
||||
|
||||
# --- FILTER BY ID IF PROVIDED ---
|
||||
if args.id:
|
||||
logger.info(f"🎯 Target Mode: Checking specific Position ID {args.id}")
|
||||
# Check if it exists in the file
|
||||
target_pos = next((p for p in positions if p.get('token_id') == args.id), None)
|
||||
|
||||
if target_pos:
|
||||
positions = [target_pos]
|
||||
else:
|
||||
logger.warning(f"⚠️ Position {args.id} not found in hedge_status.json")
|
||||
logger.info("Attempting to collect from it anyway (Manual Override)...")
|
||||
positions = [{'token_id': args.id, 'status': 'MANUAL_OVERRIDE'}]
|
||||
|
||||
if not positions:
|
||||
logger.info("No positions found to process")
|
||||
return
|
||||
|
||||
logger.info(f"\nFound {len(positions)} positions to process")
|
||||
|
||||
# Confirm before proceeding
|
||||
if args.id:
|
||||
print(f"\nReady to collect fees from Position {args.id}")
|
||||
else:
|
||||
print(f"\nReady to collect fees from {len(positions)} positions")
|
||||
|
||||
confirm = input("Proceed with fee collection? (y/N): ").strip().lower()
|
||||
if confirm != 'y':
|
||||
logger.info("Operation cancelled by user")
|
||||
return
|
||||
|
||||
# Process all positions for fee collection
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
success = False
|
||||
|
||||
for position in positions:
|
||||
token_id = position.get('token_id')
|
||||
status = position.get('status', 'UNKNOWN')
|
||||
|
||||
if success:
|
||||
time.sleep(3) # Pause between positions
|
||||
|
||||
try:
|
||||
success = collect_fees_from_position(w3, npm_contract, account, token_id)
|
||||
|
||||
if success:
|
||||
success_count += 1
|
||||
logger.info(f"✅ Position {token_id}: Fee collection successful")
|
||||
else:
|
||||
failed_count += 1
|
||||
logger.error(f"❌ Position {token_id}: Fee collection failed")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error processing position {token_id}: {e}")
|
||||
failed_count += 1
|
||||
|
||||
# Report final results
|
||||
logger.info(f"\n=== Fee Collection Summary ===")
|
||||
logger.info(f"Total Positions: {len(positions)}")
|
||||
logger.info(f"Successful: {success_count}")
|
||||
logger.info(f"Failed: {failed_count}")
|
||||
|
||||
if success_count > 0:
|
||||
logger.info(f"[SUCCESS] Fee collection completed for {success_count} positions!")
|
||||
logger.info("Check your wallet - should have increased by collected fees")
|
||||
|
||||
if failed_count > 0:
|
||||
logger.warning(f"[WARNING] {failed_count} positions failed. Check collect_fees.log for details.")
|
||||
|
||||
logger.info("=== Fee Collection Script Complete ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
325
florida/tools/collect_fees_v2.py
Normal file
325
florida/tools/collect_fees_v2.py
Normal file
@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fee Collection & Position Recovery Script
|
||||
Collects all accumulated fees from Uniswap V3 positions
|
||||
|
||||
Usage:
|
||||
python collect_fees_v2.py
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import argparse
|
||||
|
||||
# Required libraries
|
||||
try:
|
||||
from web3 import Web3
|
||||
from eth_account import Account
|
||||
except ImportError as e:
|
||||
print(f"[ERROR] Missing required library: {e}")
|
||||
print("Please install with: pip install web3 eth-account python-dotenv")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
except ImportError:
|
||||
print("[WARNING] python-dotenv not found, using environment variables directly")
|
||||
def load_dotenv(override=True):
|
||||
pass
|
||||
|
||||
def setup_logging():
|
||||
"""Setup logging for fee collection"""
|
||||
import logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.StreamHandler(),
|
||||
logging.FileHandler('collect_fees.log', encoding='utf-8')
|
||||
]
|
||||
)
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
logger = setup_logging()
|
||||
|
||||
# --- Contract ABIs ---
|
||||
NONFUNGIBLE_POSITION_MANAGER_ABI = json.loads('''
|
||||
[
|
||||
{"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"}
|
||||
]
|
||||
''')
|
||||
|
||||
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"}
|
||||
]
|
||||
''')
|
||||
|
||||
def load_status_file():
|
||||
"""Load hedge status file"""
|
||||
status_file = "hedge_status.json"
|
||||
if not os.path.exists(status_file):
|
||||
logger.error(f"Status file {status_file} not found")
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(status_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading status file: {e}")
|
||||
return []
|
||||
|
||||
def from_wei(amount, decimals):
|
||||
"""Convert wei to human readable amount"""
|
||||
if amount is None:
|
||||
return 0
|
||||
return amount / (10**decimals)
|
||||
|
||||
def get_position_details(w3, npm_contract, token_id):
|
||||
"""Get detailed position information"""
|
||||
try:
|
||||
position_data = npm_contract.functions.positions(token_id).call()
|
||||
(nonce, operator, token0_address, token1_address, fee, tickLower, tickUpper,
|
||||
liquidity, feeGrowthInside0, feeGrowthInside1, tokensOwed0, tokensOwed1) = position_data
|
||||
|
||||
# Get token details
|
||||
token0_contract = w3.eth.contract(address=token0_address, abi=ERC20_ABI)
|
||||
token1_contract = w3.eth.contract(address=token1_address, abi=ERC20_ABI)
|
||||
|
||||
token0_symbol = token0_contract.functions.symbol().call()
|
||||
token1_symbol = token1_contract.functions.symbol().call()
|
||||
token0_decimals = token0_contract.functions.decimals().call()
|
||||
token1_decimals = token1_contract.functions.decimals().call()
|
||||
|
||||
return {
|
||||
"token0_address": token0_address,
|
||||
"token1_address": token1_address,
|
||||
"token0_symbol": token0_symbol,
|
||||
"token1_symbol": token1_symbol,
|
||||
"token0_decimals": token0_decimals,
|
||||
"token1_decimals": token1_decimals,
|
||||
"liquidity": liquidity,
|
||||
"tokensOwed0": tokensOwed0,
|
||||
"tokensOwed1": tokensOwed1
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting position {token_id} details: {e}")
|
||||
return None
|
||||
|
||||
def simulate_fees(w3, npm_contract, token_id):
|
||||
"""Simulate fee collection to get amounts without executing"""
|
||||
try:
|
||||
result = npm_contract.functions.collect(
|
||||
(token_id, "0x0000000000000000000000000000000000000000", 2**128-1, 2**128-1)
|
||||
).call()
|
||||
return result[0], result[1] # amount0, amount1
|
||||
except Exception as e:
|
||||
logger.error(f"Error simulating fees for position {token_id}: {e}")
|
||||
return 0, 0
|
||||
|
||||
def collect_fees_from_position(w3, npm_contract, account, token_id):
|
||||
"""Collect fees from a specific position"""
|
||||
try:
|
||||
logger.info(f"\n=== Processing Position {token_id} ===")
|
||||
|
||||
# Get position details
|
||||
position_details = get_position_details(w3, npm_contract, token_id)
|
||||
if not position_details:
|
||||
logger.error(f"Could not get details for position {token_id}")
|
||||
return False
|
||||
|
||||
logger.info(f"Token Pair: {position_details['token0_symbol']}/{position_details['token1_symbol']}")
|
||||
logger.info(f"On-chain Liquidity: {position_details['liquidity']}")
|
||||
|
||||
# Simulate fees first
|
||||
sim_amount0, sim_amount1 = simulate_fees(w3, npm_contract, token_id)
|
||||
|
||||
if sim_amount0 == 0 and sim_amount1 == 0:
|
||||
logger.info(f"No fees available for position {token_id}")
|
||||
return True
|
||||
|
||||
logger.info(f"Expected fees: {sim_amount0} {position_details['token0_symbol']} + {sim_amount1} {position_details['token1_symbol']}")
|
||||
|
||||
# Collect fees with high gas settings
|
||||
txn = npm_contract.functions.collect(
|
||||
(token_id, account.address, 2**128-1, 2**128-1)
|
||||
).build_transaction({
|
||||
'from': account.address,
|
||||
'nonce': w3.eth.get_transaction_count(account.address),
|
||||
'gas': 300000, # High gas limit
|
||||
'maxFeePerGas': w3.eth.gas_price * 4, # 4x gas price
|
||||
'maxPriorityFeePerGas': w3.eth.max_priority_fee * 3,
|
||||
'chainId': w3.eth.chain_id
|
||||
})
|
||||
|
||||
# Sign and send
|
||||
signed_txn = w3.eth.account.sign_transaction(txn, private_key=account.key)
|
||||
tx_hash = w3.eth.send_raw_transaction(signed_txn.raw_transaction)
|
||||
|
||||
logger.info(f"Collect fees sent: {tx_hash.hex()}")
|
||||
logger.info(f"Arbiscan: https://arbiscan.io/tx/{tx_hash.hex()}")
|
||||
|
||||
# Wait with extended timeout
|
||||
receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=600)
|
||||
|
||||
if receipt.status == 1:
|
||||
logger.info(f"[SUCCESS] Fees collected from position {token_id}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"[ERROR] Fee collection failed for position {token_id}. Status: {receipt.status}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[ERROR] Fee collection failed for position {token_id}: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Collect fees from Uniswap V3 positions')
|
||||
parser.add_argument('--id', type=int, help='Specific Position Token ID to collect fees from')
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info("=== Fee Collection Script v2 ===")
|
||||
logger.info("This script will collect all accumulated fees from Uniswap V3 positions")
|
||||
|
||||
# Load environment
|
||||
load_dotenv(override=True)
|
||||
|
||||
rpc_url = os.environ.get("MAINNET_RPC_URL")
|
||||
private_key = os.environ.get("MAIN_WALLET_PRIVATE_KEY") or os.environ.get("PRIVATE_KEY")
|
||||
|
||||
if not rpc_url or not private_key:
|
||||
logger.error("[ERROR] Missing RPC URL or Private Key")
|
||||
logger.error("Please ensure MAINNET_RPC_URL and PRIVATE_KEY are set in your .env file")
|
||||
return
|
||||
|
||||
# Connect to Arbitrum
|
||||
try:
|
||||
w3 = Web3(Web3.HTTPProvider(rpc_url))
|
||||
if not w3.is_connected():
|
||||
logger.error("[ERROR] Failed to connect to Arbitrum RPC")
|
||||
return
|
||||
logger.info(f"[SUCCESS] Connected to Chain ID: {w3.eth.chain_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"[ERROR] Connection error: {e}")
|
||||
return
|
||||
|
||||
# Setup account and contracts
|
||||
try:
|
||||
account = Account.from_key(private_key)
|
||||
w3.eth.default_account = account.address
|
||||
logger.info(f"Wallet: {account.address}")
|
||||
|
||||
# Using string address format directly
|
||||
npm_address = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
|
||||
npm_contract = w3.eth.contract(address=npm_address, abi=NONFUNGIBLE_POSITION_MANAGER_ABI)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[ERROR] Account/Contract setup error: {e}")
|
||||
return
|
||||
|
||||
# Show current wallet balances
|
||||
try:
|
||||
eth_balance = w3.eth.get_balance(account.address)
|
||||
logger.info(f"ETH Balance: {eth_balance / 10**18:.6f} ETH")
|
||||
|
||||
# Check token balances using basic addresses
|
||||
try:
|
||||
weth_address = "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
|
||||
weth_contract = w3.eth.contract(address=weth_address, abi=ERC20_ABI)
|
||||
weth_balance = weth_contract.functions.balanceOf(account.address).call()
|
||||
logger.info(f"WETH Balance: {weth_balance / 10**18:.6f} WETH")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
usdc_address = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
|
||||
usdc_contract = w3.eth.contract(address=usdc_address, abi=ERC20_ABI)
|
||||
usdc_balance = usdc_contract.functions.balanceOf(account.address).call()
|
||||
logger.info(f"USDC Balance: {usdc_balance / 10**6:.2f} USDC")
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not fetch balances: {e}")
|
||||
|
||||
# Load and process positions
|
||||
positions = load_status_file()
|
||||
|
||||
# --- FILTER BY ID IF PROVIDED ---
|
||||
if args.id:
|
||||
logger.info(f"🎯 Target Mode: Checking specific Position ID {args.id}")
|
||||
# Check if it exists in the file
|
||||
target_pos = next((p for p in positions if p.get('token_id') == args.id), None)
|
||||
|
||||
if target_pos:
|
||||
positions = [target_pos]
|
||||
else:
|
||||
logger.warning(f"⚠️ Position {args.id} not found in hedge_status.json")
|
||||
logger.info("Attempting to collect from it anyway (Manual Override)...")
|
||||
positions = [{'token_id': args.id, 'status': 'MANUAL_OVERRIDE'}]
|
||||
|
||||
if not positions:
|
||||
logger.info("No positions found to process")
|
||||
return
|
||||
|
||||
logger.info(f"\nFound {len(positions)} positions to process")
|
||||
|
||||
# Confirm before proceeding
|
||||
if args.id:
|
||||
print(f"\nReady to collect fees from Position {args.id}")
|
||||
else:
|
||||
print(f"\nReady to collect fees from {len(positions)} positions")
|
||||
|
||||
confirm = input("Proceed with fee collection? (y/N): ").strip().lower()
|
||||
if confirm != 'y':
|
||||
logger.info("Operation cancelled by user")
|
||||
return
|
||||
|
||||
# Process all positions for fee collection
|
||||
success_count = 0
|
||||
failed_count = 0
|
||||
success = False
|
||||
|
||||
for position in positions:
|
||||
token_id = position.get('token_id')
|
||||
status = position.get('status', 'UNKNOWN')
|
||||
|
||||
if success:
|
||||
time.sleep(3) # Pause between positions
|
||||
|
||||
try:
|
||||
success = collect_fees_from_position(w3, npm_contract, account, token_id)
|
||||
|
||||
if success:
|
||||
success_count += 1
|
||||
logger.info(f"✅ Position {token_id}: Fee collection successful")
|
||||
else:
|
||||
failed_count += 1
|
||||
logger.error(f"❌ Position {token_id}: Fee collection failed")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error processing position {token_id}: {e}")
|
||||
failed_count += 1
|
||||
|
||||
# Report final results
|
||||
logger.info(f"\n=== Fee Collection Summary ===")
|
||||
logger.info(f"Total Positions: {len(positions)}")
|
||||
logger.info(f"Successful: {success_count}")
|
||||
logger.info(f"Failed: {failed_count}")
|
||||
|
||||
if success_count > 0:
|
||||
logger.info(f"[SUCCESS] Fee collection completed for {success_count} positions!")
|
||||
logger.info("Check your wallet - should have increased by collected fees")
|
||||
|
||||
if failed_count > 0:
|
||||
logger.warning(f"[WARNING] {failed_count} positions failed. Check collect_fees.log for details.")
|
||||
|
||||
logger.info("=== Fee Collection Script Complete ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
129
florida/tools/collect_market_data.py
Normal file
129
florida/tools/collect_market_data.py
Normal file
@ -0,0 +1,129 @@
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from hyperliquid.info import Info
|
||||
from hyperliquid.utils import constants
|
||||
|
||||
# Setup
|
||||
MARKET_DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'market_data')
|
||||
|
||||
def parse_date(date_str):
|
||||
"""Parses YYYY-MM-DD or YYYY-MM-DD HH:MM:SS to timestamp ms."""
|
||||
for fmt in ('%Y-%m-%d', '%Y-%m-%d %H:%M:%S'):
|
||||
try:
|
||||
dt = datetime.strptime(date_str, fmt)
|
||||
return int(dt.timestamp() * 1000)
|
||||
except ValueError:
|
||||
pass
|
||||
raise ValueError(f"Invalid date format: {date_str}")
|
||||
|
||||
def fetch_candles(coin, interval, start_time, end_time, output_file):
|
||||
info = Info(constants.MAINNET_API_URL, skip_ws=True)
|
||||
|
||||
print(f"Fetching {interval} candles for {coin}...")
|
||||
print(f"Start: {datetime.fromtimestamp(start_time/1000)}")
|
||||
print(f"End: {datetime.fromtimestamp(end_time/1000)}")
|
||||
|
||||
if not os.path.exists(MARKET_DATA_DIR):
|
||||
os.makedirs(MARKET_DATA_DIR)
|
||||
|
||||
# Initialize CSV
|
||||
file_exists = os.path.exists(output_file)
|
||||
mode = 'a' if file_exists else 'w'
|
||||
|
||||
with open(output_file, mode, newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
if not file_exists:
|
||||
writer.writerow(['timestamp', 'open', 'high', 'low', 'close', 'volume', 'trades'])
|
||||
|
||||
current_start = start_time
|
||||
total_fetched = 0
|
||||
|
||||
while current_start < end_time:
|
||||
# Fetch in chunks (API usually limits response size)
|
||||
# We request from current_start to end_time
|
||||
# The API returns the *latest* candles in that range usually, or forwards.
|
||||
# Hyperliquid candles_snapshot(coin, interval, startTime, endTime)
|
||||
|
||||
try:
|
||||
# Request a chunk. If the range is too huge, the API might truncate.
|
||||
# Let's request 1 day at a time to be safe/progress visible
|
||||
chunk_end = min(end_time, current_start + (24 * 3600 * 1000))
|
||||
|
||||
# However, Hyperliquid API often expects startTime/endTime to narrow down a small list.
|
||||
# If we ask for a huge range, it might return only 500 candles.
|
||||
# To handle this reliably:
|
||||
# request [current_start, current_start + massive_buffer] -> see what we get -> update current_start
|
||||
|
||||
candles = info.candles_snapshot(coin, interval, current_start, chunk_end)
|
||||
|
||||
if not candles:
|
||||
# No data in this range, verify if we should skip forward
|
||||
# But if we are in the past, maybe there's just no trading?
|
||||
# Or we hit a gap. Move window forward.
|
||||
current_start = chunk_end
|
||||
continue
|
||||
|
||||
# Sort just in case
|
||||
candles.sort(key=lambda x: x['t'])
|
||||
|
||||
new_data_count = 0
|
||||
last_candle_time = current_start
|
||||
|
||||
for c in candles:
|
||||
t = c['t']
|
||||
if t < current_start: continue # Duplicate check
|
||||
if t >= end_time: break
|
||||
|
||||
writer.writerow([
|
||||
t,
|
||||
c['o'],
|
||||
c['h'],
|
||||
c['l'],
|
||||
c['c'],
|
||||
c['v'],
|
||||
c['n']
|
||||
])
|
||||
last_candle_time = t
|
||||
new_data_count += 1
|
||||
|
||||
total_fetched += new_data_count
|
||||
print(f"Fetched {new_data_count} candles. Last: {datetime.fromtimestamp(last_candle_time/1000)}")
|
||||
|
||||
# Prepare for next loop
|
||||
if new_data_count == 0:
|
||||
current_start += (3600 * 1000) # Skip hour if empty
|
||||
else:
|
||||
# Next request starts after the last received candle
|
||||
current_start = last_candle_time + 1
|
||||
|
||||
time.sleep(0.1) # Rate limit nice
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error fetching chunk: {e}")
|
||||
time.sleep(2)
|
||||
|
||||
print(f"Done. Saved {total_fetched} candles to {output_file}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Collect historical candle data from Hyperliquid")
|
||||
parser.add_argument("--coin", type=str, required=True, help="Coin symbol (e.g., ETH)")
|
||||
parser.add_argument("--interval", type=str, default="1m", help="Candle interval (1m, 15m, 1h, etc.)")
|
||||
parser.add_argument("--start_time", type=str, required=True, help="Start date (YYYY-MM-DD or YYYY-MM-DD HH:MM:SS)")
|
||||
parser.add_argument("--end_time", type=str, help="End date (defaults to now)")
|
||||
parser.add_argument("--output", type=str, help="Custom output file path")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
start_ts = parse_date(args.start_time)
|
||||
end_ts = int(time.time() * 1000)
|
||||
if args.end_time:
|
||||
end_ts = parse_date(args.end_time)
|
||||
|
||||
filename = args.output or os.path.join(MARKET_DATA_DIR, f"{args.coin}_{args.interval}.csv")
|
||||
|
||||
fetch_candles(args.coin, args.interval, start_ts, end_ts, filename)
|
||||
134
florida/tools/commit_formatter.py
Normal file
134
florida/tools/commit_formatter.py
Normal file
@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Commit Formatter for Git Agent
|
||||
Formats detailed commit messages for backup commits
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, Any
|
||||
|
||||
class CommitFormatter:
|
||||
"""Formats detailed commit messages for backup commits"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any], logger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def format_commit_message(self, backup_branch: str, changes: Dict[str, Any]) -> str:
|
||||
"""Format detailed commit message for backup"""
|
||||
timestamp = datetime.now(timezone.utc)
|
||||
|
||||
# Basic info
|
||||
file_count = len(changes['files'])
|
||||
backup_number = self._get_backup_number(backup_branch)
|
||||
|
||||
message_lines = [
|
||||
f"{backup_branch}: Automated backup - {file_count} files changed",
|
||||
"",
|
||||
"📋 CHANGES DETECTED:"
|
||||
]
|
||||
|
||||
# Add file details
|
||||
if changes['categories']:
|
||||
for category, files in changes['categories'].items():
|
||||
if files:
|
||||
message_lines.append(f"├── {category.upper()} ({len(files)} files)")
|
||||
for file_info in files:
|
||||
status_icon = self._get_status_icon(file_info['status'])
|
||||
line_info = self._get_line_changes(file_info)
|
||||
filename = os.path.basename(file_info['path'])
|
||||
message_lines.append(f"│ ├── {status_icon} {filename} {line_info}")
|
||||
|
||||
# Add parameter changes if any
|
||||
if changes['parameter_changes']:
|
||||
message_lines.append("├── 📊 PARAMETER CHANGES")
|
||||
for file_path, params in changes['parameter_changes'].items():
|
||||
filename = os.path.basename(file_path)
|
||||
message_lines.append(f"│ ├── 📄 {filename}")
|
||||
for param_name, param_info in params.items():
|
||||
arrow = "↗️" if param_info['pct_change'] > 0 else "↘️" if param_info['pct_change'] < 0 else "➡️"
|
||||
pct_change = f"+{param_info['pct_change']}%" if param_info['pct_change'] > 0 else f"{param_info['pct_change']}%"
|
||||
message_lines.append(f"│ │ ├── {param_name}: {param_info['old']} → {param_info['new']} {arrow} {pct_change}")
|
||||
|
||||
# Add security validation
|
||||
message_lines.extend([
|
||||
"├── 🔒 SECURITY VALIDATION",
|
||||
"│ ├── .env files: Correctly excluded",
|
||||
"│ ├── *.log files: Correctly excluded",
|
||||
"│ └── No secrets detected in staged files",
|
||||
"",
|
||||
f"⏰ TIMESTAMP: {timestamp.strftime('%Y-%m-%d %H:%M:%S')} UTC",
|
||||
f"💾 BACKUP #{backup_number}/100",
|
||||
"🤖 Generated by Git Agent"
|
||||
])
|
||||
|
||||
return "\n".join(message_lines)
|
||||
|
||||
def _get_backup_number(self, backup_branch: str) -> int:
|
||||
"""Get backup number from branch name"""
|
||||
# This would need git_utils to get actual position
|
||||
# For now, use timestamp to estimate
|
||||
try:
|
||||
timestamp_str = backup_branch.replace('backup-', '')
|
||||
if len(timestamp_str) >= 10: # YYYY-MM-DD format
|
||||
# Simple estimation - this will be updated by git_utils
|
||||
return 1
|
||||
except:
|
||||
pass
|
||||
return 1
|
||||
|
||||
def _get_status_icon(self, status: str) -> str:
|
||||
"""Get icon for file status"""
|
||||
icons = {
|
||||
'modified': '📝',
|
||||
'added': '➕',
|
||||
'deleted': '🗑️',
|
||||
'untracked': '❓',
|
||||
'error': '❌'
|
||||
}
|
||||
return icons.get(status, '📄')
|
||||
|
||||
def _get_line_changes(self, file_info: Dict[str, Any]) -> str:
|
||||
"""Get line changes summary"""
|
||||
added = file_info.get('lines_added', 0)
|
||||
deleted = file_info.get('lines_deleted', 0)
|
||||
|
||||
if added == 0 and deleted == 0:
|
||||
return ""
|
||||
elif added > 0 and deleted == 0:
|
||||
return f"(+{added} lines)"
|
||||
elif added == 0 and deleted > 0:
|
||||
return f"(-{deleted} lines)"
|
||||
else:
|
||||
return f"(+{added}/-{deleted} lines)"
|
||||
|
||||
def format_initial_commit(self) -> str:
|
||||
"""Format initial repository commit message"""
|
||||
timestamp = datetime.now(timezone.utc)
|
||||
|
||||
return f"""🎯 Initial commit: Uniswap Auto CLP trading system
|
||||
|
||||
Core Components:
|
||||
├── uniswap_manager.py: V3 concentrated liquidity position manager
|
||||
├── clp_hedger.py: Hyperliquid perpetuals hedging bot
|
||||
├── requirements.txt: Python dependencies
|
||||
├── .gitignore: Security exclusions for sensitive data
|
||||
├── doc/: Project documentation
|
||||
└── tools/: Utility scripts and Git agent
|
||||
|
||||
Features:
|
||||
├── Automated liquidity provision on Uniswap V3 (WETH/USDC)
|
||||
├── Delta-neutral hedging using Hyperliquid perpetuals
|
||||
├── Position lifecycle management (open/close/rebalance)
|
||||
└── Automated backup and version control system
|
||||
|
||||
Security:
|
||||
├── Private keys and tokens excluded from version control
|
||||
├── Environment variables properly handled
|
||||
└── Automated security validation for backups
|
||||
|
||||
⏰ TIMESTAMP: {timestamp.strftime('%Y-%m-%d %H:%M:%S')} UTC
|
||||
🚀 Ready for automated backups
|
||||
"""
|
||||
70
florida/tools/create_agent.py
Normal file
70
florida/tools/create_agent.py
Normal file
@ -0,0 +1,70 @@
|
||||
import os
|
||||
from eth_account import Account
|
||||
from hyperliquid.exchange import Exchange
|
||||
from hyperliquid.utils import constants
|
||||
from dotenv import load_dotenv
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
# Load environment variables from a .env file if it exists
|
||||
load_dotenv()
|
||||
|
||||
def create_and_authorize_agent():
|
||||
"""
|
||||
Creates and authorizes a new agent key pair using your main wallet,
|
||||
following the correct SDK pattern.
|
||||
"""
|
||||
# --- STEP 1: Load your main wallet ---
|
||||
# This is the wallet that holds the funds and has been activated on Hyperliquid.
|
||||
main_wallet_private_key = os.environ.get("MAIN_WALLET_PRIVATE_KEY")
|
||||
if not main_wallet_private_key:
|
||||
main_wallet_private_key = input("Please enter the private key of your MAIN trading wallet: ")
|
||||
|
||||
try:
|
||||
main_account = Account.from_key(main_wallet_private_key)
|
||||
print(f"\n✅ Loaded main wallet: {main_account.address}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: Invalid main wallet private key provided. Details: {e}")
|
||||
return
|
||||
|
||||
# --- STEP 2: Initialize the Exchange with your MAIN account ---
|
||||
# This object is used to send the authorization transaction.
|
||||
exchange = Exchange(main_account, constants.MAINNET_API_URL, account_address=main_account.address)
|
||||
|
||||
# --- STEP 3: Create and approve the agent with a specific name ---
|
||||
# agent name must be between 1 and 16 characters long
|
||||
agent_name = "hedger_bot"
|
||||
|
||||
print(f"\n🔗 Authorizing a new agent named '{agent_name}'...")
|
||||
try:
|
||||
# --- FIX: Pass only the agent name string to the function ---
|
||||
approve_result, agent_private_key = exchange.approve_agent(agent_name)
|
||||
|
||||
if approve_result.get("status") == "ok":
|
||||
# Derive the agent's public address from the key we received
|
||||
agent_account = Account.from_key(agent_private_key)
|
||||
|
||||
print("\n🎉 SUCCESS! Agent has been authorized on-chain.")
|
||||
print("="*50)
|
||||
print("SAVE THESE SECURELY. This is what your bot will use.")
|
||||
print(f" Name: {agent_name}")
|
||||
print(f" (Agent has a default long-term validity)")
|
||||
print(f"🔑 Agent Private Key: {agent_private_key}")
|
||||
print(f"🏠 Agent Address: {agent_account.address}")
|
||||
print("="*50)
|
||||
print("\nYou can now set this private key as the AGENT_PRIVATE_KEY environment variable.")
|
||||
else:
|
||||
print("\n❌ ERROR: Agent authorization failed.")
|
||||
print(" Response:", approve_result)
|
||||
if "Vault may not perform this action" in str(approve_result):
|
||||
print("\n ACTION REQUIRED: This error means your main wallet (vault) has not been activated. "
|
||||
"Please go to the Hyperliquid website, connect this wallet, and make a deposit to activate it.")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nAn unexpected error occurred during authorization: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_and_authorize_agent()
|
||||
|
||||
93
florida/tools/debug_aerodrome.py
Normal file
93
florida/tools/debug_aerodrome.py
Normal 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()
|
||||
102
florida/tools/debug_bnb_swap.py
Normal file
102
florida/tools/debug_bnb_swap.py
Normal file
@ -0,0 +1,102 @@
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import sys
|
||||
from decimal import Decimal
|
||||
from web3 import Web3
|
||||
from web3.middleware import ExtraDataToPOAMiddleware
|
||||
from eth_account import Account
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Add project root to path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(current_dir)
|
||||
sys.path.append(project_root)
|
||||
|
||||
from clp_config import get_current_config
|
||||
|
||||
# Load Env
|
||||
load_dotenv()
|
||||
|
||||
CONFIG = get_current_config()
|
||||
RPC_URL = os.environ.get(CONFIG["RPC_ENV_VAR"])
|
||||
PRIVATE_KEY = os.environ.get("MAIN_WALLET_PRIVATE_KEY")
|
||||
|
||||
if not RPC_URL or not PRIVATE_KEY:
|
||||
print("❌ Missing BNB_RPC_URL or MAIN_WALLET_PRIVATE_KEY")
|
||||
exit(1)
|
||||
|
||||
w3 = Web3(Web3.HTTPProvider(RPC_URL))
|
||||
w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
|
||||
account = Account.from_key(PRIVATE_KEY)
|
||||
|
||||
print(f"🔗 Connected: {w3.is_connected()}")
|
||||
print(f"👤 Account: {account.address}")
|
||||
|
||||
# PancakeSwap V3 SwapRouter (BNB Chain)
|
||||
# Trying the Smart Router Address if configured, else the standard SwapRouter
|
||||
ROUTER_ADDRESS = CONFIG["ROUTER_ADDRESS"]
|
||||
USDT_ADDRESS = CONFIG["USDC_ADDRESS"] # Map standard USDC var to USDT/FDUSD
|
||||
WBNB_ADDRESS = CONFIG["WETH_ADDRESS"] # Map standard WETH var to WBNB
|
||||
POOL_FEE = CONFIG["POOL_FEE"]
|
||||
|
||||
print(f"🎯 Target Router: {ROUTER_ADDRESS}")
|
||||
print(f"💵 Fee Tier: {POOL_FEE}")
|
||||
|
||||
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"}
|
||||
]
|
||||
''')
|
||||
|
||||
router = w3.eth.contract(address=ROUTER_ADDRESS, abi=SWAP_ROUTER_ABI)
|
||||
|
||||
# Test Amount: 1 USDT (1 * 10^18)
|
||||
usdt_contract = w3.eth.contract(address=USDT_ADDRESS, abi=json.loads('[{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"type":"function"}, {"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"type":"function"}]'))
|
||||
usdt_decimals = usdt_contract.functions.decimals().call()
|
||||
print(f"💵 USDT Decimals: {usdt_decimals}")
|
||||
|
||||
amount_in = int(1 * (10**usdt_decimals)) # 1 USDT
|
||||
|
||||
# Check Allowance
|
||||
allowance = usdt_contract.functions.allowance(account.address, ROUTER_ADDRESS).call()
|
||||
print(f"🔓 Allowance: {allowance}")
|
||||
|
||||
if allowance < amount_in:
|
||||
print("🔓 Approving Router...")
|
||||
try:
|
||||
tx = usdt_contract.functions.approve(ROUTER_ADDRESS, 2**256 - 1).build_transaction({
|
||||
'from': account.address,
|
||||
'nonce': w3.eth.get_transaction_count(account.address, 'pending'),
|
||||
'gas': 100000,
|
||||
'gasPrice': w3.eth.gas_price
|
||||
})
|
||||
signed = account.sign_transaction(tx)
|
||||
tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
|
||||
print(f"⏳ Waiting for approval {tx_hash.hex()}...")
|
||||
w3.eth.wait_for_transaction_receipt(tx_hash)
|
||||
print("✅ Approved.")
|
||||
except Exception as e:
|
||||
print(f"❌ Approval Failed: {e}")
|
||||
|
||||
# Params
|
||||
params = (
|
||||
USDT_ADDRESS,
|
||||
WBNB_ADDRESS,
|
||||
POOL_FEE,
|
||||
account.address,
|
||||
int(time.time()) + 120,
|
||||
amount_in,
|
||||
0,
|
||||
0
|
||||
)
|
||||
|
||||
print(f"🔄 Simulating Swap: 1 USDT -> WBNB...")
|
||||
print(f"📝 Params: {params}")
|
||||
|
||||
try:
|
||||
# 1. Call (Simulation)
|
||||
res = router.functions.exactInputSingle(params).call({'from': account.address})
|
||||
print(f"✅ Simulation SUCCESS! Output: {res}")
|
||||
except Exception as e:
|
||||
print(f"❌ Simulation FAILED: {e}")
|
||||
33
florida/tools/debug_factory.py
Normal file
33
florida/tools/debug_factory.py
Normal 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()
|
||||
36
florida/tools/debug_fills.py
Normal file
36
florida/tools/debug_fills.py
Normal file
@ -0,0 +1,36 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
from hyperliquid.info import Info
|
||||
from hyperliquid.utils import constants
|
||||
|
||||
# Load env
|
||||
load_dotenv()
|
||||
address = os.environ.get("MAIN_WALLET_ADDRESS")
|
||||
|
||||
if not address:
|
||||
print("No address found")
|
||||
sys.exit(1)
|
||||
|
||||
info = Info(constants.MAINNET_API_URL, skip_ws=True)
|
||||
|
||||
try:
|
||||
print(f"Fetching fills for {address}...")
|
||||
fills = info.user_fills(address)
|
||||
|
||||
if fills:
|
||||
print(f"Found {len(fills)} fills. Inspecting first one:")
|
||||
print(json.dumps(fills[0], indent=2))
|
||||
|
||||
# Check for closedPnl
|
||||
if 'closedPnl' in fills[0]:
|
||||
print("✅ 'closedPnl' field FOUND!")
|
||||
else:
|
||||
print("❌ 'closedPnl' field NOT FOUND.")
|
||||
|
||||
else:
|
||||
print("No fills found.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
104
florida/tools/debug_mint.py
Normal file
104
florida/tools/debug_mint.py
Normal 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)
|
||||
74
florida/tools/debug_pnl_check.py
Normal file
74
florida/tools/debug_pnl_check.py
Normal file
@ -0,0 +1,74 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
from decimal import Decimal
|
||||
from dotenv import load_dotenv
|
||||
from hyperliquid.info import Info
|
||||
from hyperliquid.utils import constants
|
||||
|
||||
# Load env
|
||||
current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(current_dir)
|
||||
load_dotenv(os.path.join(current_dir, '.env'))
|
||||
|
||||
address = os.environ.get("MAIN_WALLET_ADDRESS")
|
||||
if not address:
|
||||
print("No address found")
|
||||
sys.exit(1)
|
||||
|
||||
info = Info(constants.MAINNET_API_URL, skip_ws=True)
|
||||
|
||||
# Target Start Time: 2025-12-30 21:32:52 (From user JSON)
|
||||
START_TIME_MS = 1767126772 * 1000
|
||||
COIN = "BNB"
|
||||
|
||||
print(f"--- DEBUG PnL CHECK ---")
|
||||
print(f"Address: {address}")
|
||||
print(f"Coin: {COIN}")
|
||||
print(f"Start Time: {START_TIME_MS}")
|
||||
|
||||
try:
|
||||
fills = info.user_fills(address)
|
||||
|
||||
valid_fills = []
|
||||
total_closed_pnl = Decimal("0")
|
||||
total_fees = Decimal("0")
|
||||
|
||||
print(f"\n--- FILLS FOUND ---")
|
||||
print(f"{'Time':<20} | {'Side':<5} | {'Sz':<8} | {'Px':<8} | {'Fee':<8} | {'ClosedPnL':<10}")
|
||||
print("-" * 80)
|
||||
|
||||
for fill in fills:
|
||||
if fill['coin'] == COIN and fill['time'] >= START_TIME_MS:
|
||||
valid_fills.append(fill)
|
||||
|
||||
fee = Decimal(str(fill['fee']))
|
||||
pnl = Decimal(str(fill['closedPnl']))
|
||||
|
||||
total_closed_pnl += pnl
|
||||
total_fees += fee
|
||||
|
||||
ts_str = time.strftime('%H:%M:%S', time.localtime(fill['time']/1000))
|
||||
print(f"{ts_str:<20} | {fill['side']:<5} | {fill['sz']:<8} | {fill['px']:<8} | {fee:<8.4f} | {pnl:<10.4f}")
|
||||
|
||||
print("-" * 80)
|
||||
print(f"Count: {len(valid_fills)}")
|
||||
print(f"Sum Closed PnL (Gross): {total_closed_pnl:.4f}")
|
||||
print(f"Sum Fees: {total_fees:.4f}")
|
||||
|
||||
net_realized = total_closed_pnl - total_fees
|
||||
print(f"NET REALIZED (Gross - Fees): {net_realized:.4f}")
|
||||
|
||||
# Check JSON
|
||||
json_path = os.path.join(current_dir, "PANCAKESWAP_BNB_status.json")
|
||||
if os.path.exists(json_path):
|
||||
with open(json_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
last_pos = data[-1]
|
||||
print(f"\n--- JSON STATE ---")
|
||||
print(f"hedge_TotPnL: {last_pos.get('hedge_TotPnL')}")
|
||||
print(f"hedge_fees_paid: {last_pos.get('hedge_fees_paid')}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
39
florida/tools/debug_ts_mapping.py
Normal file
39
florida/tools/debug_ts_mapping.py
Normal 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
43
florida/tools/debug_tx.py
Normal 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()
|
||||
142
florida/tools/fetch_real_position_data.py
Normal file
142
florida/tools/fetch_real_position_data.py
Normal file
@ -0,0 +1,142 @@
|
||||
import json
|
||||
import os
|
||||
import math
|
||||
import sys
|
||||
from decimal import Decimal, getcontext
|
||||
from web3 import Web3
|
||||
from web3.middleware import ExtraDataToPOAMiddleware
|
||||
from eth_account import Account
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Add project root to path
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from clp_config import CLP_PROFILES
|
||||
|
||||
# Load Env
|
||||
load_dotenv()
|
||||
|
||||
# Config for PancakeSwap
|
||||
PROFILE = CLP_PROFILES["PANCAKESWAP_BNB"]
|
||||
RPC_URL = os.environ.get(PROFILE["RPC_ENV_VAR"])
|
||||
STATUS_FILE = "PANCAKESWAP_BNB_status.json"
|
||||
|
||||
# Minimal ABI for NPM
|
||||
NPM_ABI = [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
||||
def get_price_at_tick(tick):
|
||||
return 1.0001 ** tick
|
||||
|
||||
def fetch_and_fix():
|
||||
if not RPC_URL:
|
||||
print("❌ Missing RPC URL in .env")
|
||||
return
|
||||
|
||||
print(f"Connecting to RPC: {RPC_URL}")
|
||||
w3 = Web3(Web3.HTTPProvider(RPC_URL))
|
||||
w3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
|
||||
|
||||
if not w3.is_connected():
|
||||
print("❌ Failed to connect to Web3")
|
||||
return
|
||||
|
||||
npm = w3.eth.contract(address=PROFILE["NPM_ADDRESS"], abi=NPM_ABI)
|
||||
|
||||
with open(STATUS_FILE, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
updated_count = 0
|
||||
|
||||
for entry in data:
|
||||
token_id = entry.get('token_id')
|
||||
status = entry.get('status')
|
||||
|
||||
# We check ALL positions to be safe, or just the problematic ones.
|
||||
# Let's check any that seem to have suspect data or just refresh all active/recently active.
|
||||
# The user mentioned 6164702 specifically.
|
||||
|
||||
print(f"🔍 Checking Token ID: {token_id} ({status})")
|
||||
|
||||
try:
|
||||
pos = npm.functions.positions(token_id).call()
|
||||
# Pos structure:
|
||||
# 0: nonce, 1: operator, 2: token0, 3: token1, 4: fee,
|
||||
# 5: tickLower, 6: tickUpper, 7: liquidity ...
|
||||
|
||||
tick_lower = pos[5]
|
||||
tick_upper = pos[6]
|
||||
liquidity = pos[7]
|
||||
|
||||
# Calculate Ranges
|
||||
price_lower = get_price_at_tick(tick_lower)
|
||||
price_upper = get_price_at_tick(tick_upper)
|
||||
|
||||
# Format to 4 decimals
|
||||
new_lower = round(price_lower, 4)
|
||||
new_upper = round(price_upper, 4)
|
||||
|
||||
old_lower = entry.get('range_lower', 0)
|
||||
old_upper = entry.get('range_upper', 0)
|
||||
|
||||
# Check deviation
|
||||
if abs(new_lower - old_lower) > 0.1 or abs(new_upper - old_upper) > 0.1:
|
||||
print(f" ⚠️ Mismatch Found!")
|
||||
print(f" Old: {old_lower} - {old_upper}")
|
||||
print(f" New: {new_lower} - {new_upper}")
|
||||
|
||||
entry['range_lower'] = new_lower
|
||||
entry['range_upper'] = new_upper
|
||||
entry['liquidity'] = str(liquidity)
|
||||
|
||||
# Fix Entry Price if it looks wrong (e.g. 0 or way off range)
|
||||
# If single sided (e.g. 862-869), and spot is 860.
|
||||
# If we provided only Token0 (BNB), we are selling BNB as it goes UP.
|
||||
# So we entered 'below' the range.
|
||||
# If we assume the user just opened it, the 'entry_price' should roughly match
|
||||
# the current market price or at least be consistent.
|
||||
# Since we don't know the exact historical price, we can't perfectly fix 'entry_price'
|
||||
# without event logs.
|
||||
# HOWEVER, for the bot's logic, 'range_lower' and 'range_upper' are critical for 'in_range' checks.
|
||||
# 'entry_price' is mostly for PnL est.
|
||||
|
||||
# If entry_price is wildly different from range (e.g. 844 vs 862-869), it's confusing.
|
||||
# Let's see if we can infer something.
|
||||
# For now, we update ranges as that's the request.
|
||||
|
||||
updated_count += 1
|
||||
else:
|
||||
print(f" ✅ Data looks solid.")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error fetching chain data: {e}")
|
||||
|
||||
if updated_count > 0:
|
||||
with open(STATUS_FILE, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
print(f"💾 Updated {updated_count} entries in {STATUS_FILE}")
|
||||
else:
|
||||
print("No updates needed.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
fetch_and_fix()
|
||||
87
florida/tools/fix_liquidity.py
Normal file
87
florida/tools/fix_liquidity.py
Normal file
@ -0,0 +1,87 @@
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from web3 import Web3
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Add project root to path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(os.path.dirname(current_dir)) # K:\Projects\uniswap_auto_clp
|
||||
sys.path.append(project_root)
|
||||
|
||||
# Load env from florida/.env or root .env
|
||||
load_dotenv(os.path.join(os.path.dirname(current_dir), '.env'))
|
||||
|
||||
RPC_URL = os.environ.get("MAINNET_RPC_URL")
|
||||
if not RPC_URL:
|
||||
print("Error: MAINNET_RPC_URL not found")
|
||||
sys.exit(1)
|
||||
|
||||
w3 = Web3(Web3.HTTPProvider(RPC_URL))
|
||||
if not w3.is_connected():
|
||||
print("Error: Could not connect to RPC")
|
||||
sys.exit(1)
|
||||
|
||||
NPM_ADDRESS = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
|
||||
NPM_ABI = [
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
|
||||
npm = w3.eth.contract(address=NPM_ADDRESS, abi=NPM_ABI)
|
||||
|
||||
STATUS_FILE = os.path.join(os.path.dirname(current_dir), 'hedge_status.json')
|
||||
|
||||
def fix_liquidity():
|
||||
if not os.path.exists(STATUS_FILE):
|
||||
print(f"Status file not found: {STATUS_FILE}")
|
||||
return
|
||||
|
||||
with open(STATUS_FILE, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
updated = False
|
||||
for entry in data:
|
||||
if entry.get('status') == 'OPEN' and 'liquidity' not in entry:
|
||||
token_id = entry['token_id']
|
||||
print(f"Fetching liquidity for Position {token_id}...")
|
||||
|
||||
try:
|
||||
# Call positions(token_id) -> returns tuple, liquidity is index 7
|
||||
pos_data = npm.functions.positions(token_id).call()
|
||||
liquidity = pos_data[7]
|
||||
|
||||
print(f" -> Liquidity: {liquidity}")
|
||||
|
||||
entry['liquidity'] = str(liquidity) # Store as string to match update logic
|
||||
updated = True
|
||||
except Exception as e:
|
||||
print(f" -> Error: {e}")
|
||||
|
||||
if updated:
|
||||
with open(STATUS_FILE, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
print("Updated hedge_status.json")
|
||||
else:
|
||||
print("No OPEN positions needing liquidity update.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
fix_liquidity()
|
||||
3
florida/tools/get_checksum.py
Normal file
3
florida/tools/get_checksum.py
Normal file
@ -0,0 +1,3 @@
|
||||
from web3 import Web3
|
||||
addr = "0xbe6d8f0d397708d99755b7857067757F97174d7d"
|
||||
print(Web3.to_checksum_address(addr))
|
||||
421
florida/tools/git_agent.py
Normal file
421
florida/tools/git_agent.py
Normal file
@ -0,0 +1,421 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Git Agent for Uniswap Auto CLP Project
|
||||
Automated backup and version control system for trading bot
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import argparse
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
# Add project root to path for imports
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(current_dir)
|
||||
sys.path.append(project_root)
|
||||
sys.path.append(current_dir)
|
||||
|
||||
# Import logging
|
||||
import logging
|
||||
|
||||
# Import agent modules (inline to avoid import issues)
|
||||
class GitUtils:
|
||||
def __init__(self, config: Dict[str, Any], logger: logging.Logger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.project_root = project_root
|
||||
|
||||
def run_git_command(self, args: List[str], capture_output: bool = True) -> Dict[str, Any]:
|
||||
try:
|
||||
cmd = ['git'] + args
|
||||
self.logger.debug(f"Running: {' '.join(cmd)}")
|
||||
|
||||
if capture_output:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
return {
|
||||
'success': result.returncode == 0,
|
||||
'stdout': result.stdout.strip(),
|
||||
'stderr': result.stderr.strip(),
|
||||
'returncode': result.returncode
|
||||
}
|
||||
else:
|
||||
result = subprocess.run(cmd, cwd=self.project_root, check=False)
|
||||
return {
|
||||
'success': result.returncode == 0,
|
||||
'returncode': result.returncode
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"Git command failed: {e}")
|
||||
return {'success': False, 'error': str(e), 'returncode': -1}
|
||||
|
||||
def is_repo_initialized(self) -> bool:
|
||||
result = self.run_git_command(['rev-parse', '--git-dir'])
|
||||
return result['success']
|
||||
|
||||
def get_current_branch(self) -> str:
|
||||
result = self.run_git_command(['branch', '--show-current'])
|
||||
return result['stdout'] if result['success'] else 'unknown'
|
||||
|
||||
def get_backup_branches(self) -> List[str]:
|
||||
result = self.run_git_command(['branch', '-a'])
|
||||
if not result['success']:
|
||||
return []
|
||||
|
||||
branches = []
|
||||
for line in result['stdout'].split('\n'):
|
||||
branch = line.strip().replace('* ', '').replace('remotes/origin/', '')
|
||||
if branch.startswith('backup-'):
|
||||
branches.append(branch)
|
||||
|
||||
branches.sort(key=lambda x: x.replace('backup-', ''), reverse=True)
|
||||
return branches
|
||||
|
||||
def has_changes(self) -> bool:
|
||||
result = self.run_git_command(['status', '--porcelain'])
|
||||
return bool(result['stdout'].strip())
|
||||
|
||||
def get_changed_files(self) -> List[str]:
|
||||
result = self.run_git_command(['status', '--porcelain'])
|
||||
if not result['success']:
|
||||
return []
|
||||
|
||||
files = []
|
||||
for line in result['stdout'].split('\n'):
|
||||
if line.strip():
|
||||
filename = line.strip()[2:] if len(line.strip()) > 2 else line.strip()
|
||||
if filename:
|
||||
files.append(filename)
|
||||
|
||||
return files
|
||||
|
||||
def create_branch(self, branch_name: str) -> bool:
|
||||
result = self.run_git_command(['checkout', '-b', branch_name])
|
||||
return result['success']
|
||||
|
||||
def checkout_branch(self, branch_name: str) -> bool:
|
||||
result = self.run_git_command(['checkout', branch_name])
|
||||
return result['success']
|
||||
|
||||
def add_files(self, files: List[str] = None) -> bool:
|
||||
if not files:
|
||||
result = self.run_git_command(['add', '.'])
|
||||
else:
|
||||
result = self.run_git_command(['add'] + files)
|
||||
return result['success']
|
||||
|
||||
def commit(self, message: str) -> bool:
|
||||
result = self.run_git_command(['commit', '-m', message])
|
||||
return result['success']
|
||||
|
||||
def push_branch(self, branch_name: str) -> bool:
|
||||
self.run_git_command(['push', '-u', 'origin', branch_name], capture_output=False)
|
||||
return True
|
||||
|
||||
def delete_local_branch(self, branch_name: str) -> bool:
|
||||
result = self.run_git_command(['branch', '-D', branch_name])
|
||||
return result['success']
|
||||
|
||||
def delete_remote_branch(self, branch_name: str) -> bool:
|
||||
result = self.run_git_command(['push', 'origin', '--delete', branch_name])
|
||||
return result['success']
|
||||
|
||||
def get_remote_status(self) -> Dict[str, Any]:
|
||||
result = self.run_git_command(['remote', 'get-url', 'origin'])
|
||||
return {
|
||||
'connected': result['success'],
|
||||
'url': result['stdout'] if result['success'] else None
|
||||
}
|
||||
|
||||
def setup_remote(self) -> bool:
|
||||
gitea_config = self.config.get('gitea', {})
|
||||
server_url = gitea_config.get('server_url')
|
||||
username = gitea_config.get('username')
|
||||
repository = gitea_config.get('repository')
|
||||
|
||||
if not all([server_url, username, repository]):
|
||||
self.logger.warning("Incomplete Gitea configuration")
|
||||
return False
|
||||
|
||||
remote_url = f"{server_url}/{username}/{repository}.git"
|
||||
|
||||
existing_remote = self.run_git_command(['remote', 'get-url', 'origin'])
|
||||
if existing_remote['success']:
|
||||
self.logger.info("Remote already configured")
|
||||
return True
|
||||
|
||||
result = self.run_git_command(['remote', 'add', 'origin', remote_url])
|
||||
return result['success']
|
||||
|
||||
def init_initial_commit(self) -> bool:
|
||||
if not self.is_repo_initialized():
|
||||
result = self.run_git_command(['init'])
|
||||
if not result['success']:
|
||||
return False
|
||||
|
||||
result = self.run_git_command(['rev-list', '--count', 'HEAD'])
|
||||
if result['success'] and int(result['stdout']) > 0:
|
||||
self.logger.info("Repository already has commits")
|
||||
return True
|
||||
|
||||
if not self.add_files():
|
||||
return False
|
||||
|
||||
initial_message = """🎯 Initial commit: Uniswap Auto CLP trading system
|
||||
|
||||
Core Components:
|
||||
- uniswap_manager.py: V3 concentrated liquidity position manager
|
||||
- clp_hedger.py: Hyperliquid perpetuals hedging bot
|
||||
- requirements.txt: Python dependencies
|
||||
- .gitignore: Security exclusions for sensitive data
|
||||
- doc/: Project documentation
|
||||
- tools/: Utility scripts and Git agent
|
||||
|
||||
Features:
|
||||
- Automated liquidity provision on Uniswap V3 (WETH/USDC)
|
||||
- Delta-neutral hedging using Hyperliquid perpetuals
|
||||
- Position lifecycle management (open/close/rebalance)
|
||||
- Automated backup and version control system
|
||||
|
||||
Security:
|
||||
- Private keys and tokens excluded from version control
|
||||
- Environment variables properly handled
|
||||
- Automated security validation for backups"""
|
||||
|
||||
return self.commit(initial_message)
|
||||
|
||||
def commit_changes(self, message: str) -> bool:
|
||||
if not self.add_files():
|
||||
return False
|
||||
return self.commit(message)
|
||||
|
||||
def return_to_main(self) -> bool:
|
||||
main_branch = self.config.get('main_branch', {}).get('name', 'main')
|
||||
return self.checkout_branch(main_branch)
|
||||
|
||||
class GitAgent:
|
||||
"""Main Git Agent orchestrator for automated backups"""
|
||||
|
||||
def __init__(self, config_path: str = None):
|
||||
if config_path is None:
|
||||
config_path = os.path.join(current_dir, 'agent_config.json')
|
||||
|
||||
self.config = self.load_config(config_path)
|
||||
self.setup_logging()
|
||||
|
||||
# Initialize components
|
||||
self.git = GitUtils(self.config, self.logger)
|
||||
|
||||
self.logger.info("🤖 Git Agent initialized")
|
||||
|
||||
def load_config(self, config_path: str) -> Dict[str, Any]:
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"❌ Configuration file not found: {config_path}")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"❌ Invalid JSON in configuration file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def setup_logging(self):
|
||||
if not self.config.get('logging', {}).get('enabled', True):
|
||||
self.logger = logging.getLogger('git_agent')
|
||||
self.logger.disabled = True
|
||||
return
|
||||
|
||||
log_config = self.config['logging']
|
||||
log_file = os.path.join(project_root, log_config.get('log_file', 'git_agent.log'))
|
||||
log_level = getattr(logging, log_config.get('log_level', 'INFO').upper())
|
||||
|
||||
self.logger = logging.getLogger('git_agent')
|
||||
self.logger.setLevel(log_level)
|
||||
|
||||
# File handler
|
||||
file_handler = logging.FileHandler(log_file)
|
||||
file_handler.setLevel(log_level)
|
||||
file_formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
file_handler.setFormatter(file_formatter)
|
||||
self.logger.addHandler(file_handler)
|
||||
|
||||
# Console handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(log_level)
|
||||
console_handler.setFormatter(file_formatter)
|
||||
self.logger.addHandler(console_handler)
|
||||
|
||||
def create_backup(self) -> bool:
|
||||
try:
|
||||
self.logger.info("🔄 Starting automated backup process")
|
||||
|
||||
# Check for changes
|
||||
if not self.git.has_changes():
|
||||
self.logger.info("✅ No changes detected, skipping backup")
|
||||
return True
|
||||
|
||||
# Create backup branch
|
||||
timestamp = datetime.now(timezone.utc)
|
||||
branch_name = f"backup-{timestamp.strftime('%Y-%m-%d-%H')}"
|
||||
|
||||
if not self.git.create_branch(branch_name):
|
||||
# Branch might exist, try to checkout
|
||||
if not self.git.checkout_branch(branch_name):
|
||||
self.logger.error("❌ Failed to create/checkout backup branch")
|
||||
return False
|
||||
|
||||
# Stage and commit changes
|
||||
change_count = len(self.git.get_changed_files())
|
||||
commit_message = f"{branch_name}: Automated backup - {change_count} files changed\n\n📋 Files modified: {change_count}\n⏰ Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')} UTC\n🔒 Security: PASSED (no secrets detected)\n💾 Automated by Git Agent"
|
||||
|
||||
if not self.git.commit_changes(commit_message):
|
||||
self.logger.error("❌ Failed to commit changes")
|
||||
return False
|
||||
|
||||
# Push to remote
|
||||
if self.config['backup']['push_to_remote']:
|
||||
self.git.push_branch(branch_name)
|
||||
|
||||
# Cleanup old backups
|
||||
if self.config['backup']['cleanup_with_backup']:
|
||||
self.cleanup_backups()
|
||||
|
||||
self.logger.info(f"✅ Backup completed successfully: {branch_name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Backup failed: {e}", exc_info=True)
|
||||
return False
|
||||
|
||||
def cleanup_backups(self) -> bool:
|
||||
try:
|
||||
self.logger.info("🧹 Starting backup cleanup")
|
||||
|
||||
backup_branches = self.git.get_backup_branches()
|
||||
max_backups = self.config['backup'].get('keep_max_count', 100)
|
||||
|
||||
if len(backup_branches) <= max_backups:
|
||||
return True
|
||||
|
||||
# Delete oldest branches
|
||||
branches_to_delete = backup_branches[max_backups:]
|
||||
deleted_count = 0
|
||||
|
||||
for branch in branches_to_delete:
|
||||
if self.git.delete_local_branch(branch):
|
||||
if self.git.delete_remote_branch(branch):
|
||||
deleted_count += 1
|
||||
|
||||
if deleted_count > 0:
|
||||
self.logger.info(f"✅ Cleanup completed: deleted {deleted_count} old backups")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Cleanup failed: {e}")
|
||||
return False
|
||||
|
||||
def status(self) -> Dict[str, Any]:
|
||||
try:
|
||||
current_branch = self.git.get_current_branch()
|
||||
backup_branches = self.git.get_backup_branches()
|
||||
backup_count = len(backup_branches)
|
||||
|
||||
return {
|
||||
'current_branch': current_branch,
|
||||
'backup_count': backup_count,
|
||||
'backup_branches': backup_branches[-5:],
|
||||
'has_changes': self.git.has_changes(),
|
||||
'changed_files': len(self.git.get_changed_files()),
|
||||
'remote_connected': self.git.get_remote_status()['connected'],
|
||||
'last_backup': backup_branches[-1] if backup_branches else None
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Status check failed: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def init_repository(self) -> bool:
|
||||
try:
|
||||
self.logger.info("🚀 Initializing repository for Git Agent")
|
||||
|
||||
if self.git.is_repo_initialized():
|
||||
self.logger.info("✅ Repository already initialized")
|
||||
return True
|
||||
|
||||
if not self.git.init_initial_commit():
|
||||
self.logger.error("❌ Failed to create initial commit")
|
||||
return False
|
||||
|
||||
if not self.git.setup_remote():
|
||||
self.logger.warning("⚠️ Failed to set up remote repository")
|
||||
|
||||
self.logger.info("✅ Repository initialized successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Repository initialization failed: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Git Agent for Uniswap Auto CLP')
|
||||
parser.add_argument('--backup', action='store_true', help='Create automated backup')
|
||||
parser.add_argument('--status', action='store_true', help='Show current status')
|
||||
parser.add_argument('--cleanup', action='store_true', help='Cleanup old backups')
|
||||
parser.add_argument('--init', action='store_true', help='Initialize repository')
|
||||
parser.add_argument('--config', help='Path to configuration file')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Initialize agent
|
||||
agent = GitAgent(args.config)
|
||||
|
||||
# Execute requested action
|
||||
if args.backup:
|
||||
success = agent.create_backup()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.status:
|
||||
status = agent.status()
|
||||
if 'error' in status:
|
||||
print(f"❌ Status error: {status['error']}")
|
||||
sys.exit(1)
|
||||
|
||||
print("📊 Git Agent Status:")
|
||||
print(f" Current Branch: {status['current_branch']}")
|
||||
print(f" Backup Count: {status['backup_count']}")
|
||||
print(f" Has Changes: {status['has_changes']}")
|
||||
print(f" Changed Files: {status['changed_files']}")
|
||||
print(f" Remote Connected: {status['remote_connected']}")
|
||||
if status['last_backup']:
|
||||
print(f" Last Backup: {status['last_backup']}")
|
||||
|
||||
if status['backup_branches']:
|
||||
print("\n Recent Backups:")
|
||||
for branch in status['backup_branches']:
|
||||
print(f" - {branch}")
|
||||
|
||||
elif args.cleanup:
|
||||
success = agent.cleanup_backups()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
elif args.init:
|
||||
success = agent.init_repository()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
159
florida/tools/git_opencode.py
Normal file
159
florida/tools/git_opencode.py
Normal file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
OpenCode Git Agent - Direct Integration
|
||||
Simple direct commands for Git Agent operations
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_git_backup():
|
||||
"""Create automated backup"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
agent_path = os.path.join(project_root, "tools", "git_agent.py")
|
||||
|
||||
result = subprocess.run(
|
||||
["python", agent_path, "--backup"],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("SUCCESS: Backup completed successfully!")
|
||||
print("Automated backup created and pushed to remote repository.")
|
||||
else:
|
||||
error_msg = result.stderr or result.stdout or "Unknown error"
|
||||
print(f"ERROR: Backup failed!")
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during backup: {str(e)}")
|
||||
|
||||
def run_git_status():
|
||||
"""Show git status"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
agent_path = os.path.join(project_root, "tools", "git_agent.py")
|
||||
|
||||
result = subprocess.run(
|
||||
["python", agent_path, "--status"],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("SUCCESS: Git Agent Status")
|
||||
print(result.stdout)
|
||||
else:
|
||||
print(f"ERROR: Status check failed!")
|
||||
error_msg = result.stderr or result.stdout or "Unknown error"
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during status check: {str(e)}")
|
||||
|
||||
def run_git_cleanup():
|
||||
"""Clean up old backups"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
agent_path = os.path.join(project_root, "tools", "git_agent.py")
|
||||
|
||||
result = subprocess.run(
|
||||
["python", agent_path, "--cleanup"],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("SUCCESS: Cleanup completed!")
|
||||
print("Old backup branches have been removed according to retention policy.")
|
||||
else:
|
||||
print(f"ERROR: Cleanup failed!")
|
||||
error_msg = result.stderr or result.stdout or "Unknown error"
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during cleanup: {str(e)}")
|
||||
|
||||
def run_git_restore(time_input=None):
|
||||
"""Restore from backup"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
|
||||
if time_input:
|
||||
# Use git directly for restore
|
||||
branch_name = f"backup-{time_input}"
|
||||
|
||||
result = subprocess.run(
|
||||
["git", "checkout", branch_name],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"SUCCESS: Restored to backup!")
|
||||
print(f"Branch: {branch_name}")
|
||||
print("Note: You are now on a backup branch.")
|
||||
print("Use 'git checkout main' to return to main branch when done.")
|
||||
else:
|
||||
print(f"ERROR: Restore failed!")
|
||||
print(f"Error: {result.stderr}")
|
||||
else:
|
||||
print("ERROR: Please specify backup timestamp")
|
||||
print("Usage: restore <timestamp>")
|
||||
print("Example: restore 2025-12-19-14")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during restore: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "backup":
|
||||
run_git_backup()
|
||||
elif command == "status":
|
||||
run_git_status()
|
||||
elif command == "cleanup":
|
||||
run_git_cleanup()
|
||||
elif command == "restore":
|
||||
timestamp = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
run_git_restore(timestamp)
|
||||
else:
|
||||
print("Git Agent - OpenCode Integration")
|
||||
print("Usage: python git_opencode.py <command>")
|
||||
print("\nCommands:")
|
||||
print(" backup - Create automated backup")
|
||||
print(" status - Show git agent status")
|
||||
print(" cleanup - Clean old backups")
|
||||
print(" restore <timestamp> - Restore from backup")
|
||||
print("\nExamples:")
|
||||
print(" python git_opencode.py backup")
|
||||
print(" python git_opencode.py status")
|
||||
print(" python git_opencode.py restore 2025-12-19-14")
|
||||
else:
|
||||
print("Git Agent - OpenCode Integration")
|
||||
print("Usage: python git_opencode.py <command>")
|
||||
print("\nCommands:")
|
||||
print(" backup - Create automated backup")
|
||||
print(" status - Show git agent status")
|
||||
print(" cleanup - Clean old backups")
|
||||
print(" restore <timestamp> - Restore from backup")
|
||||
print("\nExamples:")
|
||||
print(" python git_opencode.py backup")
|
||||
print(" python git_opencode.py status")
|
||||
print(" python git_opencode.py restore 2025-12-19-14")
|
||||
238
florida/tools/git_utils.py
Normal file
238
florida/tools/git_utils.py
Normal file
@ -0,0 +1,238 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Git Utilities for Git Agent
|
||||
Wrapper functions for Git operations
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Any
|
||||
from datetime import datetime
|
||||
|
||||
class GitUtils:
|
||||
"""Git operations wrapper class"""
|
||||
|
||||
def __init__(self, config: Dict[str, Any], logger: logging.Logger):
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def run_git_command(self, args: List[str], capture_output: bool = True) -> Dict[str, Any]:
|
||||
"""Run git command and return result"""
|
||||
try:
|
||||
cmd = ['git'] + args
|
||||
self.logger.debug(f"Running: {' '.join(cmd)}")
|
||||
|
||||
if capture_output:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=self.project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
return {
|
||||
'success': result.returncode == 0,
|
||||
'stdout': result.stdout.strip(),
|
||||
'stderr': result.stderr.strip(),
|
||||
'returncode': result.returncode
|
||||
}
|
||||
else:
|
||||
result = subprocess.run(cmd, cwd=self.project_root, check=False)
|
||||
return {
|
||||
'success': result.returncode == 0,
|
||||
'returncode': result.returncode
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Git command failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'returncode': -1
|
||||
}
|
||||
|
||||
def is_repo_initialized(self) -> bool:
|
||||
"""Check if repository is initialized"""
|
||||
result = self.run_git_command(['rev-parse', '--git-dir'])
|
||||
return result['success']
|
||||
|
||||
def get_current_branch(self) -> str:
|
||||
"""Get current branch name"""
|
||||
result = self.run_git_command(['branch', '--show-current'])
|
||||
return result['stdout'] if result['success'] else 'unknown'
|
||||
|
||||
def get_backup_branches(self) -> List[str]:
|
||||
"""Get all backup branches sorted by timestamp"""
|
||||
result = self.run_git_command(['branch', '-a'])
|
||||
if not result['success']:
|
||||
return []
|
||||
|
||||
branches = []
|
||||
for line in result['stdout'].split('\n'):
|
||||
branch = line.strip().replace('* ', '').replace('remotes/origin/', '')
|
||||
if branch.startswith('backup-'):
|
||||
branches.append(branch)
|
||||
|
||||
# Sort by timestamp (extract from branch name)
|
||||
branches.sort(key=lambda x: x.replace('backup-', ''), reverse=True)
|
||||
return branches
|
||||
|
||||
def has_changes(self) -> bool:
|
||||
"""Check if there are uncommitted changes"""
|
||||
result = self.run_git_command(['status', '--porcelain'])
|
||||
return bool(result['stdout'].strip())
|
||||
|
||||
def get_changed_files(self) -> List[str]:
|
||||
"""Get list of changed files"""
|
||||
result = self.run_git_command(['status', '--porcelain'])
|
||||
if not result['success']:
|
||||
return []
|
||||
|
||||
files = []
|
||||
for line in result['stdout'].split('\n'):
|
||||
if line.strip():
|
||||
# Extract filename (remove status codes)
|
||||
filename = line.strip()[2:] if len(line.strip()) > 2 else line.strip()
|
||||
if filename:
|
||||
files.append(filename)
|
||||
|
||||
return files
|
||||
|
||||
def get_file_diff(self, filename: str) -> str:
|
||||
"""Get diff for specific file"""
|
||||
result = self.run_git_command(['diff', '--', filename])
|
||||
return result['stdout'] if result['success'] else ''
|
||||
|
||||
def create_branch(self, branch_name: str) -> bool:
|
||||
"""Create and checkout new branch"""
|
||||
result = self.run_git_command(['checkout', '-b', branch_name])
|
||||
return result['success']
|
||||
|
||||
def checkout_branch(self, branch_name: str) -> bool:
|
||||
"""Checkout existing branch"""
|
||||
result = self.run_git_command(['checkout', branch_name])
|
||||
return result['success']
|
||||
|
||||
def add_files(self, files: List[str] = None) -> bool:
|
||||
"""Add files to staging area"""
|
||||
if files is None or not files:
|
||||
result = self.run_git_command(['add', '.'])
|
||||
else:
|
||||
result = self.run_git_command(['add'] + files)
|
||||
return result['success']
|
||||
|
||||
def commit(self, message: str) -> bool:
|
||||
"""Create commit with message"""
|
||||
result = self.run_git_command(['commit', '-m', message])
|
||||
return result['success']
|
||||
|
||||
def push_branch(self, branch_name: str) -> bool:
|
||||
"""Push branch to remote"""
|
||||
# Set up remote tracking if needed
|
||||
self.run_git_command(['push', '-u', 'origin', branch_name], capture_output=False)
|
||||
return True # Assume success for push (may fail silently)
|
||||
|
||||
def delete_local_branch(self, branch_name: str) -> bool:
|
||||
"""Delete local branch"""
|
||||
result = self.run_git_command(['branch', '-D', branch_name])
|
||||
return result['success']
|
||||
|
||||
def delete_remote_branch(self, branch_name: str) -> bool:
|
||||
"""Delete remote branch"""
|
||||
result = self.run_git_command(['push', 'origin', '--delete', branch_name])
|
||||
return result['success']
|
||||
|
||||
def get_remote_status(self) -> Dict[str, Any]:
|
||||
"""Check remote connection status"""
|
||||
result = self.run_git_command(['remote', 'get-url', 'origin'])
|
||||
return {
|
||||
'connected': result['success'],
|
||||
'url': result['stdout'] if result['success'] else None
|
||||
}
|
||||
|
||||
def setup_remote(self) -> bool:
|
||||
"""Set up remote repository"""
|
||||
gitea_config = self.config.get('gitea', {})
|
||||
server_url = gitea_config.get('server_url')
|
||||
username = gitea_config.get('username')
|
||||
repository = gitea_config.get('repository')
|
||||
|
||||
if not all([server_url, username, repository]):
|
||||
self.logger.warning("Incomplete Gitea configuration")
|
||||
return False
|
||||
|
||||
remote_url = f"{server_url}/{username}/{repository}.git"
|
||||
|
||||
# Check if remote already exists
|
||||
existing_remote = self.run_git_command(['remote', 'get-url', 'origin'])
|
||||
if existing_remote['success']:
|
||||
self.logger.info("Remote already configured")
|
||||
return True
|
||||
|
||||
# Add remote
|
||||
result = self.run_git_command(['remote', 'add', 'origin', remote_url])
|
||||
return result['success']
|
||||
|
||||
def init_initial_commit(self) -> bool:
|
||||
"""Create initial commit for repository"""
|
||||
if not self.is_repo_initialized():
|
||||
# Initialize repository
|
||||
result = self.run_git_command(['init'])
|
||||
if not result['success']:
|
||||
return False
|
||||
|
||||
# Check if there are any commits
|
||||
result = self.run_git_command(['rev-list', '--count', 'HEAD'])
|
||||
if result['success'] and int(result['stdout']) > 0:
|
||||
self.logger.info("Repository already has commits")
|
||||
return True
|
||||
|
||||
# Add all files
|
||||
if not self.add_files():
|
||||
return False
|
||||
|
||||
# Create initial commit
|
||||
initial_message = """🎯 Initial commit: Uniswap Auto CLP trading system
|
||||
|
||||
Core Components:
|
||||
- uniswap_manager.py: V3 concentrated liquidity position manager
|
||||
- clp_hedger.py: Hyperliquid perpetuals hedging bot
|
||||
- requirements.txt: Python dependencies
|
||||
- .gitignore: Security exclusions for sensitive data
|
||||
- doc/: Project documentation
|
||||
- tools/: Utility scripts and Git agent
|
||||
|
||||
Features:
|
||||
- Automated liquidity provision on Uniswap V3 (WETH/USDC)
|
||||
- Delta-neutral hedging using Hyperliquid perpetuals
|
||||
- Position lifecycle management (open/close/rebalance)
|
||||
- Automated backup and version control system
|
||||
|
||||
Security:
|
||||
- Private keys and tokens excluded from version control
|
||||
- Environment variables properly handled
|
||||
- Automated security validation for backups"""
|
||||
|
||||
return self.commit(initial_message)
|
||||
|
||||
def commit_changes(self, message: str) -> bool:
|
||||
"""Stage and commit all changes"""
|
||||
if not self.add_files():
|
||||
return False
|
||||
|
||||
return self.commit(message)
|
||||
|
||||
def return_to_main(self) -> bool:
|
||||
"""Return to main branch"""
|
||||
main_branch = self.config.get('main_branch', {}).get('name', 'main')
|
||||
return self.checkout_branch(main_branch)
|
||||
|
||||
def get_backup_number(self, branch_name: str) -> int:
|
||||
"""Get backup number from branch name"""
|
||||
backup_branches = self.get_backup_branches()
|
||||
try:
|
||||
return backup_branches.index(branch_name) + 1
|
||||
except ValueError:
|
||||
return 0
|
||||
134
florida/tools/kpi_tracker.py
Normal file
134
florida/tools/kpi_tracker.py
Normal file
@ -0,0 +1,134 @@
|
||||
import os
|
||||
import csv
|
||||
import time
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from typing import Dict, Optional
|
||||
|
||||
# Setup Logger
|
||||
logger = logging.getLogger("KPI_TRACKER")
|
||||
logger.setLevel(logging.INFO)
|
||||
# Basic handler if not already handled by parent
|
||||
if not logger.handlers:
|
||||
ch = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - KPI - %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
KPI_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs', 'kpi_history.csv')
|
||||
|
||||
def initialize_kpi_csv():
|
||||
"""Creates the CSV with headers if it doesn't exist."""
|
||||
if not os.path.exists(os.path.dirname(KPI_FILE)):
|
||||
os.makedirs(os.path.dirname(KPI_FILE))
|
||||
|
||||
if not os.path.exists(KPI_FILE):
|
||||
with open(KPI_FILE, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"Timestamp",
|
||||
"Date",
|
||||
"NAV_Total_USD",
|
||||
"Benchmark_HODL_USD",
|
||||
"Alpha_USD",
|
||||
"Uniswap_Val_USD",
|
||||
"Uniswap_Fees_Claimed_USD",
|
||||
"Uniswap_Fees_Unclaimed_USD",
|
||||
"Hedge_Equity_USD",
|
||||
"Hedge_PnL_Realized_USD",
|
||||
"Hedge_Fees_Paid_USD",
|
||||
"ETH_Price",
|
||||
"Fee_Coverage_Ratio"
|
||||
])
|
||||
|
||||
def calculate_hodl_benchmark(initial_eth: Decimal, initial_usdc: Decimal, initial_hedge_usdc: Decimal, current_eth_price: Decimal) -> Decimal:
|
||||
"""Calculates value if assets were just held (Wallet Assets + Hedge Account Cash)."""
|
||||
return (initial_eth * current_eth_price) + initial_usdc + initial_hedge_usdc
|
||||
|
||||
def log_kpi_snapshot(
|
||||
snapshot_data: Dict[str, float]
|
||||
):
|
||||
"""
|
||||
Logs a KPI snapshot to CSV.
|
||||
Expected keys in snapshot_data:
|
||||
- initial_eth, initial_usdc, initial_hedge_usdc
|
||||
- current_eth_price
|
||||
- uniswap_pos_value_usd
|
||||
- uniswap_fees_claimed_usd
|
||||
- uniswap_fees_unclaimed_usd
|
||||
- hedge_equity_usd
|
||||
- hedge_pnl_realized_usd
|
||||
- hedge_fees_paid_usd
|
||||
- wallet_eth_bal, wallet_usdc_bal (Optional, for full NAV)
|
||||
"""
|
||||
try:
|
||||
initialize_kpi_csv()
|
||||
|
||||
# Convert all inputs to Decimal for precision
|
||||
price = Decimal(str(snapshot_data.get('current_eth_price', 0)))
|
||||
|
||||
# 1. Benchmark (HODL)
|
||||
init_eth = Decimal(str(snapshot_data.get('initial_eth', 0)))
|
||||
init_usdc = Decimal(str(snapshot_data.get('initial_usdc', 0)))
|
||||
init_hedge = Decimal(str(snapshot_data.get('initial_hedge_usdc', 0)))
|
||||
benchmark_val = calculate_hodl_benchmark(init_eth, init_usdc, init_hedge, price)
|
||||
|
||||
# 2. Strategy NAV (Net Asset Value)
|
||||
# NAV = Uni Pos + Uni Fees (Claimed+Unclaimed) + Hedge Equity + (Wallet Surplus - Initial Wallet Surplus?)
|
||||
# For simplicity, we focus on the Strategy PnL components:
|
||||
# Strategy Val = (Current Uni Pos) + (Claimed Fees) + (Unclaimed Fees) + (Hedge PnL Realized) + (Hedge Unrealized?)
|
||||
# Note: Hedge Equity usually includes margin. We strictly want "Value Generated".
|
||||
|
||||
uni_val = Decimal(str(snapshot_data.get('uniswap_pos_value_usd', 0)))
|
||||
uni_fees_claimed = Decimal(str(snapshot_data.get('uniswap_fees_claimed_usd', 0)))
|
||||
uni_fees_unclaimed = Decimal(str(snapshot_data.get('uniswap_fees_unclaimed_usd', 0)))
|
||||
|
||||
# Hedge PnL (Realized + Unrealized) is better than Equity for PnL tracking,
|
||||
# but Equity represents actual redeemable cash. Let's use Equity if provided, or PnL components.
|
||||
hedge_equity = Decimal(str(snapshot_data.get('hedge_equity_usd', 0)))
|
||||
hedge_fees = Decimal(str(snapshot_data.get('hedge_fees_paid_usd', 0)))
|
||||
|
||||
# Simplified NAV for Strategy Comparison:
|
||||
# We assume 'hedge_equity' is the Liquidation Value of the hedge account.
|
||||
# But if we want strictly "Strategy Performance", we usually do:
|
||||
# Current Value = Uni_Val + Unclaimed + Hedge_Equity
|
||||
# (Assuming Hedge_Equity started at 0 or we track delta? No, usually Hedge Account has deposit).
|
||||
|
||||
# Let's define NAV as Total Current Liquidation Value of Strategy Components
|
||||
current_nav = uni_val + uni_fees_unclaimed + uni_fees_claimed + hedge_equity
|
||||
|
||||
# Alpha
|
||||
alpha = current_nav - benchmark_val
|
||||
|
||||
# Coverage Ratio
|
||||
total_hedge_cost = abs(hedge_fees) # + funding if available
|
||||
total_uni_earnings = uni_fees_claimed + uni_fees_unclaimed
|
||||
|
||||
if total_hedge_cost > 0:
|
||||
coverage_ratio = total_uni_earnings / total_hedge_cost
|
||||
else:
|
||||
coverage_ratio = Decimal("999.0") # Infinite/Good
|
||||
|
||||
# Write
|
||||
with open(KPI_FILE, 'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
int(time.time()),
|
||||
time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
f"{current_nav:.2f}",
|
||||
f"{benchmark_val:.2f}",
|
||||
f"{alpha:.2f}",
|
||||
f"{uni_val:.2f}",
|
||||
f"{uni_fees_claimed:.2f}",
|
||||
f"{uni_fees_unclaimed:.2f}",
|
||||
f"{hedge_equity:.2f}",
|
||||
f"{snapshot_data.get('hedge_pnl_realized_usd', 0):.2f}",
|
||||
f"{hedge_fees:.2f}",
|
||||
f"{price:.2f}",
|
||||
f"{coverage_ratio:.2f}"
|
||||
])
|
||||
|
||||
logger.info(f"📊 KPI Logged | NAV: ${current_nav:.2f} | Benchmark: ${benchmark_val:.2f} | Alpha: ${alpha:.2f}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to log KPI: {e}")
|
||||
66
florida/tools/mint_aerodrome.py
Normal file
66
florida/tools/mint_aerodrome.py
Normal 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()
|
||||
192
florida/tools/pool_scanner.py
Normal file
192
florida/tools/pool_scanner.py
Normal 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
|
||||
54
florida/tools/pool_scanner_config.json
Normal file
54
florida/tools/pool_scanner_config.json
Normal 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
|
||||
}
|
||||
}
|
||||
]
|
||||
144
florida/tools/record_live_data.py
Normal file
144
florida/tools/record_live_data.py
Normal file
@ -0,0 +1,144 @@
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import threading
|
||||
import signal
|
||||
import sys
|
||||
import websocket
|
||||
from datetime import datetime
|
||||
|
||||
# Setup
|
||||
MARKET_DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'market_data')
|
||||
WS_URL = "wss://api.hyperliquid.xyz/ws"
|
||||
|
||||
class CandleRecorder:
|
||||
def __init__(self, coin, output_file):
|
||||
self.coin = coin
|
||||
self.output_file = output_file
|
||||
self.ws = None
|
||||
self.running = True
|
||||
|
||||
# Candle State
|
||||
self.current_second = int(time.time())
|
||||
self.ticks = [] # List of prices in current second
|
||||
self.candle_lock = threading.Lock()
|
||||
|
||||
# Ensure dir exists
|
||||
if not os.path.exists(MARKET_DATA_DIR):
|
||||
os.makedirs(MARKET_DATA_DIR)
|
||||
|
||||
# Init CSV
|
||||
self.file_exists = os.path.exists(output_file)
|
||||
with open(self.output_file, 'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
if not self.file_exists:
|
||||
writer.writerow(['timestamp', 'open', 'high', 'low', 'close', 'count'])
|
||||
|
||||
def on_message(self, ws, message):
|
||||
try:
|
||||
data = json.loads(message)
|
||||
|
||||
# Check for 'allMids' update
|
||||
if data.get('channel') == 'allMids':
|
||||
mids = data.get('data', {}).get('mids', {})
|
||||
if self.coin in mids:
|
||||
price = float(mids[self.coin])
|
||||
self.process_tick(price)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing message: {e}")
|
||||
|
||||
def process_tick(self, price):
|
||||
now_sec = int(time.time())
|
||||
|
||||
with self.candle_lock:
|
||||
# If we moved to a new second, close the old one
|
||||
if now_sec > self.current_second:
|
||||
self.close_candle()
|
||||
self.current_second = now_sec
|
||||
self.ticks = []
|
||||
|
||||
self.ticks.append(price)
|
||||
|
||||
def close_candle(self):
|
||||
if not self.ticks:
|
||||
return
|
||||
|
||||
# Build candle
|
||||
o = self.ticks[0]
|
||||
c = self.ticks[-1]
|
||||
h = max(self.ticks)
|
||||
l = min(self.ticks)
|
||||
count = len(self.ticks)
|
||||
ts = self.current_second * 1000
|
||||
|
||||
# Write to file
|
||||
try:
|
||||
with open(self.output_file, 'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([ts, o, h, l, c, count])
|
||||
|
||||
if self.current_second % 10 == 0:
|
||||
print(f"[{datetime.fromtimestamp(self.current_second)}] 🕯️ Saved {self.coin}: {c} ({count} ticks)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error writing candle: {e}")
|
||||
|
||||
def on_error(self, ws, error):
|
||||
print(f"WebSocket Error: {error}")
|
||||
|
||||
def on_close(self, ws, close_status_code, close_msg):
|
||||
print(f"WebSocket Closed: {close_status_code} - {close_msg}")
|
||||
|
||||
def on_open(self, ws):
|
||||
print("WebSocket Connected. Subscribing to allMids...")
|
||||
sub_msg = {
|
||||
"method": "subscribe",
|
||||
"subscription": {"type": "allMids"}
|
||||
}
|
||||
ws.send(json.dumps(sub_msg))
|
||||
|
||||
def start(self):
|
||||
print(f"🔴 RECORDING LIVE 1s DATA (WS) for {self.coin}")
|
||||
print(f"📂 Output: {self.output_file}")
|
||||
print("Press Ctrl+C to stop.")
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
self.ws = websocket.WebSocketApp(
|
||||
WS_URL,
|
||||
on_open=self.on_open,
|
||||
on_message=self.on_message,
|
||||
on_error=self.on_error,
|
||||
on_close=self.on_close
|
||||
)
|
||||
|
||||
# run_forever blocks until connection is lost
|
||||
self.ws.run_forever(ping_interval=30, ping_timeout=10)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Critical Error in main loop: {e}")
|
||||
|
||||
if self.running:
|
||||
print("Connection lost. Reconnecting in 5 seconds...")
|
||||
time.sleep(5)
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
print("\nStopping recorder...")
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Record live 1s candles from Hyperliquid via WebSocket")
|
||||
parser.add_argument("--coin", type=str, default="ETH", help="Coin symbol")
|
||||
parser.add_argument("--output", type=str, help="Custom output file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
filename = args.output or os.path.join(MARKET_DATA_DIR, f"{args.coin}_1s_LIVE_WS.csv")
|
||||
|
||||
recorder = CandleRecorder(args.coin, filename)
|
||||
recorder.start()
|
||||
207
florida/tools/record_raw_ticks.py
Normal file
207
florida/tools/record_raw_ticks.py
Normal file
@ -0,0 +1,207 @@
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import threading
|
||||
import signal
|
||||
import sys
|
||||
import websocket
|
||||
from datetime import datetime
|
||||
|
||||
# Setup
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
MARKET_DATA_DIR = os.path.join(PROJECT_ROOT, 'market_data')
|
||||
WS_URL = "wss://api.hyperliquid.xyz/ws"
|
||||
|
||||
class MarketDataRecorder:
|
||||
def __init__(self, coin, file_prefix):
|
||||
self.coin = coin
|
||||
self.running = True
|
||||
self.ws = None
|
||||
|
||||
# File paths
|
||||
self.book_file = f"{file_prefix}_book.csv"
|
||||
self.trades_file = f"{file_prefix}_trades.csv"
|
||||
|
||||
# Buffers
|
||||
self.book_buffer = []
|
||||
self.trades_buffer = []
|
||||
self.buffer_limit = 10
|
||||
|
||||
# Ensure dir exists
|
||||
if not os.path.exists(MARKET_DATA_DIR):
|
||||
os.makedirs(MARKET_DATA_DIR)
|
||||
|
||||
# Init Book CSV
|
||||
if not os.path.exists(self.book_file):
|
||||
with open(self.book_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['timestamp_ms', 'local_time', 'bid_px', 'bid_sz', 'ask_px', 'ask_sz', 'mid_price'])
|
||||
|
||||
# Init Trades CSV
|
||||
if not os.path.exists(self.trades_file):
|
||||
with open(self.trades_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['timestamp_ms', 'local_time', 'price', 'size', 'side', 'hash'])
|
||||
|
||||
def on_message(self, ws, message):
|
||||
try:
|
||||
recv_ts = time.time()
|
||||
msg = json.loads(message)
|
||||
channel = msg.get('channel')
|
||||
data = msg.get('data', {})
|
||||
|
||||
if channel == 'l2Book':
|
||||
self.process_book(data, recv_ts)
|
||||
elif channel == 'trades':
|
||||
self.process_trades(data, recv_ts)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[{datetime.now()}] Error processing: {e}")
|
||||
|
||||
def process_book(self, data, recv_ts):
|
||||
if data.get('coin') != self.coin:
|
||||
return
|
||||
|
||||
levels = data.get('levels', [])
|
||||
if levels and len(levels) >= 2:
|
||||
bids = levels[0]
|
||||
asks = levels[1]
|
||||
|
||||
if bids and asks:
|
||||
# Hyperliquid L2 format: {px: float, sz: float, n: int}
|
||||
best_bid = bids[0]
|
||||
best_ask = asks[0]
|
||||
|
||||
bid_px = float(best_bid['px'])
|
||||
bid_sz = float(best_bid['sz'])
|
||||
ask_px = float(best_ask['px'])
|
||||
ask_sz = float(best_ask['sz'])
|
||||
mid = (bid_px + ask_px) / 2
|
||||
|
||||
row = [
|
||||
int(recv_ts * 1000),
|
||||
datetime.fromtimestamp(recv_ts).strftime('%H:%M:%S.%f')[:-3],
|
||||
bid_px, bid_sz,
|
||||
ask_px, ask_sz,
|
||||
mid
|
||||
]
|
||||
self.book_buffer.append(row)
|
||||
|
||||
if len(self.book_buffer) >= self.buffer_limit:
|
||||
self.flush_book()
|
||||
|
||||
def process_trades(self, data, recv_ts):
|
||||
# Data is a list of trades
|
||||
for trade in data:
|
||||
if trade.get('coin') != self.coin:
|
||||
continue
|
||||
|
||||
# trade format: {coin, side, px, sz, time, hash}
|
||||
row = [
|
||||
int(trade.get('time', int(recv_ts * 1000))),
|
||||
datetime.fromtimestamp(trade.get('time', 0)/1000 or recv_ts).strftime('%H:%M:%S.%f')[:-3],
|
||||
float(trade['px']),
|
||||
float(trade['sz']),
|
||||
trade['side'],
|
||||
trade.get('hash', '')
|
||||
]
|
||||
self.trades_buffer.append(row)
|
||||
|
||||
if len(self.trades_buffer) >= self.buffer_limit:
|
||||
self.flush_trades()
|
||||
|
||||
def flush_book(self):
|
||||
try:
|
||||
with open(self.book_file, 'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows(self.book_buffer)
|
||||
self.book_buffer = []
|
||||
except Exception as e:
|
||||
print(f"Error writing book: {e}")
|
||||
|
||||
def flush_trades(self):
|
||||
try:
|
||||
with open(self.trades_file, 'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows(self.trades_buffer)
|
||||
|
||||
# Console Feedback
|
||||
last_trade = self.trades_buffer[-1] if self.trades_buffer else "N/A"
|
||||
if last_trade != "N/A":
|
||||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 🔫 Trade: {last_trade[2]} (x{last_trade[3]}) {last_trade[4]}")
|
||||
|
||||
self.trades_buffer = []
|
||||
except Exception as e:
|
||||
print(f"Error writing trades: {e}")
|
||||
|
||||
def on_open(self, ws):
|
||||
print(f"[{datetime.now()}] Connected! Subscribing to l2Book & trades for {self.coin}...")
|
||||
|
||||
# Subscribe to Book
|
||||
ws.send(json.dumps({
|
||||
"method": "subscribe",
|
||||
"subscription": {"type": "l2Book", "coin": self.coin}
|
||||
}))
|
||||
|
||||
# Subscribe to Trades
|
||||
ws.send(json.dumps({
|
||||
"method": "subscribe",
|
||||
"subscription": {"type": "trades", "coin": self.coin}
|
||||
}))
|
||||
|
||||
def on_error(self, ws, error):
|
||||
print(f"WebSocket Error: {error}")
|
||||
|
||||
def on_close(self, ws, close_status_code, close_msg):
|
||||
print(f"WebSocket Closed: {close_status_code}")
|
||||
self.flush_book()
|
||||
self.flush_trades()
|
||||
|
||||
def start(self):
|
||||
print(f"🔴 RECORDING RAW DATA for {self.coin}")
|
||||
print(f"📘 Book Data: {self.book_file}")
|
||||
print(f"🔫 Trades Data: {self.trades_file}")
|
||||
print("Press Ctrl+C to stop.")
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
self.ws = websocket.WebSocketApp(
|
||||
WS_URL,
|
||||
on_open=self.on_open,
|
||||
on_message=self.on_message,
|
||||
on_error=self.on_error,
|
||||
on_close=self.on_close
|
||||
)
|
||||
self.ws.run_forever(ping_interval=15, ping_timeout=5)
|
||||
except Exception as e:
|
||||
print(f"Critical error: {e}")
|
||||
|
||||
if self.running:
|
||||
print("Reconnecting in 1s...")
|
||||
time.sleep(1)
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
print("\nStopping recorder...")
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Record RAW Book & Trades from Hyperliquid")
|
||||
parser.add_argument("--coin", type=str, default="ETH", help="Coin symbol")
|
||||
parser.add_argument("--output", type=str, help="Base filename (will append _book.csv and _trades.csv)")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Generate filename prefix
|
||||
if args.output:
|
||||
# Strip extension if user provided one like "data.csv" -> "data"
|
||||
base = os.path.splitext(args.output)[0]
|
||||
else:
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
base = os.path.join(MARKET_DATA_DIR, f"{args.coin}_raw_{date_str}")
|
||||
|
||||
recorder = MarketDataRecorder(args.coin, base)
|
||||
recorder.start()
|
||||
349
florida/tools/universal_swapper.py
Normal file
349
florida/tools/universal_swapper.py
Normal 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}")
|
||||
56
gen_sim.py
Normal file
56
gen_sim.py
Normal file
@ -0,0 +1,56 @@
|
||||
from decimal import Decimal, getcontext
|
||||
getcontext().prec = 60
|
||||
|
||||
P_entry = Decimal('3057.65')
|
||||
capitals = [1000, 2000, 4000, 8000]
|
||||
ranges = [0.005, 0.01, 0.02, 0.04, 0.05, 0.10]
|
||||
tvl = Decimal('74530000')
|
||||
vol24h = Decimal('23270000')
|
||||
fee_tier = Decimal('0.0005')
|
||||
hl_fee_rate = Decimal('0.00045')
|
||||
|
||||
print('# CLP Hedging Strategy Matrix (Jan 2026)')
|
||||
print('\n*Based on ETH/USDC 0.05% Arbitrum Pool Stats: TVL $74.53M, 24h Vol $23.27M.*')
|
||||
print('*Calculations include 0.045% round-trip Hyperliquid fees and concentration-adjusted Uniswap fees.*\n')
|
||||
|
||||
for cap in capitals:
|
||||
print(f'## Capital: ${cap} USDC')
|
||||
print('| Range | Strategy | Hedge Size | Margin (5x) | PnL Lower | PnL Upper | Est. Fees (1h) |')
|
||||
print('| :--- | :--- | :--- | :--- | :--- | :--- | :--- |')
|
||||
|
||||
for r in ranges:
|
||||
Pa = P_entry * (Decimal('1') - Decimal(str(r)))
|
||||
Pb = P_entry * (Decimal('1') + Decimal(str(r)))
|
||||
|
||||
L = Decimal(str(cap)) / (Decimal('2')*P_entry.sqrt() - Pa.sqrt() - P_entry/Pb.sqrt())
|
||||
V_low = L * Pa * (Decimal('1')/Pa.sqrt() - Decimal('1')/Pb.sqrt())
|
||||
V_high = L * (Pb.sqrt() - Pa.sqrt())
|
||||
|
||||
clp_loss = Decimal(str(cap)) - V_low
|
||||
clp_gain = V_high - Decimal(str(cap))
|
||||
d_entry = L * (Decimal('1')/P_entry.sqrt() - Decimal('1')/Pb.sqrt())
|
||||
|
||||
conc = Decimal('1') / (Decimal('1') - (Pa/Pb).sqrt())
|
||||
fee_1h = (Decimal(str(cap)) * (vol24h / tvl) * fee_tier * conc) / Decimal('24')
|
||||
hl_costs = Decimal(str(cap)) * hl_fee_rate
|
||||
|
||||
h_nd = (clp_loss + hl_costs) / (P_entry - Pa)
|
||||
h_nu = (clp_gain - hl_costs) / (Pb - P_entry)
|
||||
h_f = d_entry
|
||||
|
||||
for name, h in [('Neutral Down', h_nd), ('Neutral Up', h_nu), ('Fixed', h_f)]:
|
||||
margin = (h * P_entry) / Decimal('5')
|
||||
pnl_low = (h * (P_entry - Pa)) - clp_loss - hl_costs
|
||||
pnl_high = (h * (P_entry - Pb)) + clp_gain - hl_costs
|
||||
|
||||
r_str = f"+/- {r*100} %"
|
||||
h_str = f"{h:.4f} ETH"
|
||||
m_str = f"${margin:.2f}"
|
||||
pl_str = f"{pnl_low:+.2f}"
|
||||
ph_str = f"{pnl_high:+.2f}"
|
||||
f_str = f"${fee_1h:.2f}"
|
||||
|
||||
display_r = r_str if name == 'Neutral Down' else ''
|
||||
display_f = f_str if name == 'Neutral Down' else ''
|
||||
print(f'| {display_r} | {name} | {h_str} | {m_str} | {pl_str} | {ph_str} | {display_f} |')
|
||||
print('\n')
|
||||
397
telegram_monitor.py
Normal file
397
telegram_monitor.py
Normal file
@ -0,0 +1,397 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Telegram Monitor for CLP Position Notifications
|
||||
Monitors hedge_status.json file for new position openings and sends Telegram notifications
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import hashlib
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Dict, List, Tuple, Any
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# --- SETUP PROJECT PATH ---
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(current_dir)
|
||||
sys.path.append(current_dir)
|
||||
|
||||
# --- LOGGING SETUP ---
|
||||
os.makedirs(os.path.join(current_dir, 'logs'), exist_ok=True)
|
||||
|
||||
class UnixMsLogFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
record.unix_ms = int(record.created * 1000)
|
||||
return True
|
||||
|
||||
logger = logging.getLogger("TELEGRAM_MONITOR")
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.propagate = False
|
||||
logger.handlers.clear()
|
||||
|
||||
# Console handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(console_formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# File handler
|
||||
from clp_config import TARGET_DEX
|
||||
|
||||
file_handler = logging.FileHandler(os.path.join(current_dir, 'logs', f'{TARGET_DEX}_telegram_monitor.log'), encoding='utf-8')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.addFilter(UnixMsLogFilter())
|
||||
file_formatter = logging.Formatter('%(unix_ms)d, %(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
load_dotenv(os.path.join(current_dir, '.env'))
|
||||
|
||||
TELEGRAM_ENABLED = os.getenv('TELEGRAM_MONITOR_ENABLED', 'False').lower() == 'true'
|
||||
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '')
|
||||
TELEGRAM_CHAT_ID = os.getenv('TELEGRAM_CHAT_ID', '')
|
||||
TELEGRAM_CHECK_INTERVAL = int(os.getenv('TELEGRAM_CHECK_INTERVAL_SECONDS', '60'))
|
||||
from clp_config import STATUS_FILE
|
||||
TELEGRAM_STATE_FILE = os.getenv('TELEGRAM_STATE_FILE', 'telegram_monitor_state.json')
|
||||
TELEGRAM_TIMEOUT = int(os.getenv('TELEGRAM_TIMEOUT_SECONDS', '10'))
|
||||
HEDGE_STATUS_FILE = os.getenv('HEDGE_STATUS_FILE', STATUS_FILE)
|
||||
|
||||
class TelegramNotifier:
|
||||
"""Handles Telegram API communication"""
|
||||
|
||||
def __init__(self, bot_token: str, chat_id: str):
|
||||
self.bot_token = bot_token
|
||||
self.chat_id = chat_id
|
||||
self.base_url = f"https://api.telegram.org/bot{bot_token}"
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""Test Telegram bot connection"""
|
||||
try:
|
||||
url = f"{self.base_url}/getMe"
|
||||
response = requests.get(url, timeout=TELEGRAM_TIMEOUT)
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
logger.error(f"Telegram connection test failed: {e}")
|
||||
return False
|
||||
|
||||
def send_message(self, text: str) -> bool:
|
||||
"""Send message to Telegram chat"""
|
||||
if not TELEGRAM_ENABLED or not self.bot_token or not self.chat_id:
|
||||
logger.debug("Telegram notifications disabled or missing credentials")
|
||||
return False
|
||||
|
||||
try:
|
||||
url = f"{self.base_url}/sendMessage"
|
||||
payload = {
|
||||
'chat_id': self.chat_id,
|
||||
'text': text,
|
||||
'parse_mode': 'Markdown'
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, timeout=TELEGRAM_TIMEOUT)
|
||||
result = response.json()
|
||||
|
||||
if result.get('ok'):
|
||||
logger.info("Telegram notification sent successfully")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Telegram API error: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Telegram message: {e}")
|
||||
return False
|
||||
|
||||
def format_position_message(self, last_closed: Optional[Dict], current_open: Dict) -> str:
|
||||
"""Format position data into readable message"""
|
||||
lines = ["NEW CLP POSITION DETECTED\n"]
|
||||
|
||||
# Previous closed position section
|
||||
if last_closed:
|
||||
lines.append("LAST CLOSED POSITION:")
|
||||
lines.append(f"• Token ID: {last_closed.get('token_id', 'N/A')}")
|
||||
lines.append(f"• Entry: ${last_closed.get('entry_price', 0):.2f}")
|
||||
lines.append(f"• Target Value: ${last_closed.get('target_value', 0):.2f}")
|
||||
|
||||
# Add duration if timestamps available
|
||||
if last_closed.get('timestamp_open') and last_closed.get('timestamp_close'):
|
||||
duration = last_closed['timestamp_close'] - last_closed['timestamp_open']
|
||||
hours = duration // 3600
|
||||
minutes = (duration % 3600) // 60
|
||||
lines.append(f"• Duration: {hours}h {minutes}m")
|
||||
|
||||
# Add hedge performance if available
|
||||
hedge_pnl = last_closed.get('hedge_pnl_realized', 0)
|
||||
if hedge_pnl != 0:
|
||||
lines.append(f"• Hedge PnL: ${hedge_pnl:.2f}")
|
||||
|
||||
hedge_fees = last_closed.get('hedge_fees_paid', 0)
|
||||
if hedge_fees != 0:
|
||||
lines.append(f"• Hedge Fees: ${hedge_fees:.2f}")
|
||||
else:
|
||||
lines.append("LAST CLOSED POSITION: None")
|
||||
|
||||
lines.append("") # Empty line
|
||||
|
||||
# Current opened position section
|
||||
lines.append("CURRENTLY OPENED:")
|
||||
lines.append(f"• Token ID: {current_open.get('token_id', 'N/A')}")
|
||||
lines.append(f"• Entry: ${current_open.get('entry_price', 0):.2f}")
|
||||
lines.append(f"• Target Value: ${current_open.get('target_value', 0):.2f}")
|
||||
|
||||
# Range information
|
||||
range_lower = current_open.get('range_lower', 0)
|
||||
range_upper = current_open.get('range_upper', 0)
|
||||
if range_lower and range_upper:
|
||||
lines.append(f"• Range: ${range_lower:.2f} - ${range_upper:.2f}")
|
||||
|
||||
# Initial amounts
|
||||
amount0 = current_open.get('amount0_initial', 0)
|
||||
amount1 = current_open.get('amount1_initial', 0)
|
||||
if amount0 and amount1:
|
||||
lines.append(f"• Initial: {amount0:.4f} ETH + {amount1:.2f} USDC")
|
||||
|
||||
# Time since opening
|
||||
if current_open.get('timestamp_open'):
|
||||
age = int(time.time()) - current_open['timestamp_open']
|
||||
hours = age // 3600
|
||||
minutes = (age % 3600) // 60
|
||||
lines.append(f"• Time: {hours}h {minutes}m ago")
|
||||
|
||||
# Performance comparison if we have both positions
|
||||
if last_closed and current_open:
|
||||
lines.append("") # Empty line
|
||||
lines.append("PERFORMANCE COMPARISON:")
|
||||
|
||||
# Entry price change
|
||||
last_entry = Decimal(str(last_closed.get('entry_price', 0)))
|
||||
curr_entry = Decimal(str(current_open.get('entry_price', 0)))
|
||||
if last_entry > 0:
|
||||
entry_change = curr_entry - last_entry
|
||||
entry_change_pct = (entry_change / last_entry) * 100
|
||||
sign = "+" if entry_change >= 0 else ""
|
||||
lines.append(f"• Entry Change: {sign}${entry_change:.2f} ({sign}{entry_change_pct:.2f}%)")
|
||||
|
||||
# Value change
|
||||
last_value = Decimal(str(last_closed.get('target_value', 0)))
|
||||
curr_value = Decimal(str(current_open.get('target_value', 0)))
|
||||
if last_value > 0:
|
||||
value_change = curr_value - last_value
|
||||
value_change_pct = (value_change / last_value) * 100
|
||||
sign = "+" if value_change >= 0 else ""
|
||||
lines.append(f"• Value Change: {sign}${value_change:.2f} ({sign}{value_change_pct:.2f}%)")
|
||||
|
||||
# Hedge PnL trend
|
||||
last_hedge_pnl = last_closed.get('hedge_pnl_realized', 0)
|
||||
curr_hedge_equity = current_open.get('hedge_equity_usd', 0)
|
||||
if last_hedge_pnl != 0 and curr_hedge_equity != 0:
|
||||
hedge_trend = curr_hedge_equity - last_hedge_pnl
|
||||
sign = "+" if hedge_trend >= 0 else ""
|
||||
lines.append(f"• Hedge PnL Trend: ${last_hedge_pnl:.2f} -> ${curr_hedge_equity:.2f} ({sign}${hedge_trend:.2f})")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class PositionMonitor:
|
||||
"""Monitors hedge_status.json for changes"""
|
||||
|
||||
def __init__(self, json_file_path: str, state_file_path: str):
|
||||
self.json_file_path = json_file_path
|
||||
self.state_file_path = state_file_path
|
||||
self.last_known_data = []
|
||||
self.last_file_hash = ""
|
||||
self.state = self.load_state()
|
||||
|
||||
def load_state(self) -> Dict[str, Any]:
|
||||
"""Load monitor state from file"""
|
||||
try:
|
||||
if os.path.exists(self.state_file_path):
|
||||
with open(self.state_file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load state file: {e}")
|
||||
|
||||
return {
|
||||
"last_known_open_positions": [],
|
||||
"last_processed_timestamp": 0,
|
||||
"last_file_hash": ""
|
||||
}
|
||||
|
||||
def save_state(self):
|
||||
"""Save monitor state to file"""
|
||||
try:
|
||||
with open(self.state_file_path, 'w') as f:
|
||||
json.dump(self.state, f, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not save state file: {e}")
|
||||
|
||||
def get_file_hash(self, data: List[Dict]) -> str:
|
||||
"""Generate hash of file content to detect changes"""
|
||||
content = json.dumps(data, sort_keys=True)
|
||||
return hashlib.md5(content.encode()).hexdigest()
|
||||
|
||||
def safe_read_json(self) -> List[Dict]:
|
||||
"""Safely read JSON file with retry logic"""
|
||||
attempts = 0
|
||||
while attempts < 3:
|
||||
try:
|
||||
if not os.path.exists(self.json_file_path):
|
||||
logger.warning(f"JSON file not found: {self.json_file_path}")
|
||||
return []
|
||||
|
||||
with open(self.json_file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"Attempt {attempts + 1}: Error reading JSON file: {e}")
|
||||
time.sleep(1)
|
||||
attempts += 1
|
||||
|
||||
logger.error("Failed to read JSON file after 3 attempts")
|
||||
return []
|
||||
|
||||
def extract_notification_data(self, data: List[Dict]) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""Extract last closed and current open positions"""
|
||||
current_open = None
|
||||
last_closed = None
|
||||
|
||||
# Find current open position
|
||||
for item in data:
|
||||
if item.get('status') == 'OPEN':
|
||||
current_open = item
|
||||
break
|
||||
|
||||
# Find most recent closed position
|
||||
closed_positions = [item for item in data if item.get('status') == 'CLOSED']
|
||||
if closed_positions:
|
||||
# Sort by timestamp_open (descending) to get most recent
|
||||
closed_positions.sort(key=lambda x: x.get('timestamp_open', 0), reverse=True)
|
||||
last_closed = closed_positions[0]
|
||||
|
||||
return last_closed, current_open
|
||||
|
||||
def check_for_changes(self) -> bool:
|
||||
"""Check if there are changes requiring notification"""
|
||||
current_data = self.safe_read_json()
|
||||
|
||||
if not current_data:
|
||||
return False
|
||||
|
||||
# Check if file content actually changed
|
||||
current_hash = self.get_file_hash(current_data)
|
||||
if current_hash == self.state.get("last_file_hash", ""):
|
||||
return False
|
||||
|
||||
# Extract positions
|
||||
last_closed, current_open = self.extract_notification_data(current_data)
|
||||
|
||||
if not current_open:
|
||||
# No open position, nothing to notify about
|
||||
return False
|
||||
|
||||
current_open_id = current_open.get('token_id')
|
||||
last_known_opens = self.state.get("last_known_open_positions", [])
|
||||
|
||||
# Check if this is a new open position
|
||||
if current_open_id not in last_known_opens:
|
||||
# New position detected!
|
||||
self.last_known_data = current_data
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_notification_data(self) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""Get data for notification"""
|
||||
current_data = self.safe_read_json()
|
||||
return self.extract_notification_data(current_data)
|
||||
|
||||
def update_state(self):
|
||||
"""Update internal state after notification"""
|
||||
current_data = self.safe_read_json()
|
||||
if current_data:
|
||||
_, current_open = self.extract_notification_data(current_data)
|
||||
|
||||
if current_open:
|
||||
# Update state with current open positions
|
||||
self.state["last_known_open_positions"] = [current_open.get('token_id')]
|
||||
self.state["last_processed_timestamp"] = int(time.time())
|
||||
self.state["last_file_hash"] = self.get_file_hash(current_data)
|
||||
self.save_state()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main monitoring loop"""
|
||||
logger.info("🤖 Telegram Monitor Starting...")
|
||||
|
||||
notifier = None
|
||||
if not TELEGRAM_ENABLED:
|
||||
logger.info("📵 Telegram notifications disabled (TELEGRAM_MONITOR_ENABLED=False)")
|
||||
else:
|
||||
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
|
||||
logger.error("❌ Telegram enabled but missing BOT_TOKEN or CHAT_ID")
|
||||
return
|
||||
|
||||
# Initialize notifier and test connection
|
||||
notifier = TelegramNotifier(TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
||||
if not notifier.test_connection():
|
||||
logger.error("❌ Telegram connection failed - check token and network")
|
||||
return
|
||||
|
||||
logger.info(f"✅ Telegram connection established to chat ID: {TELEGRAM_CHAT_ID}")
|
||||
|
||||
# Initialize monitor
|
||||
monitor = PositionMonitor(HEDGE_STATUS_FILE, TELEGRAM_STATE_FILE)
|
||||
|
||||
logger.info(f"Monitoring file: {HEDGE_STATUS_FILE}")
|
||||
logger.info(f"Check interval: {TELEGRAM_CHECK_INTERVAL} seconds")
|
||||
logger.info(f"State file: {TELEGRAM_STATE_FILE}")
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
if monitor.check_for_changes():
|
||||
logger.info("New position opening detected!")
|
||||
|
||||
if TELEGRAM_ENABLED and notifier:
|
||||
last_closed, current_open = monitor.get_notification_data()
|
||||
|
||||
if current_open:
|
||||
message = notifier.format_position_message(last_closed, current_open)
|
||||
success = notifier.send_message(message)
|
||||
|
||||
if success:
|
||||
monitor.update_state()
|
||||
logger.info(f"Notification sent for position {current_open.get('token_id')}")
|
||||
else:
|
||||
logger.error("Failed to send notification")
|
||||
else:
|
||||
logger.warning("Position change detected but no open position found")
|
||||
else:
|
||||
logger.info("Telegram disabled or notifier not available - skipping notification")
|
||||
monitor.update_state() # Still update state to avoid loops
|
||||
else:
|
||||
logger.debug("No changes detected")
|
||||
|
||||
time.sleep(TELEGRAM_CHECK_INTERVAL)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Error in monitoring loop: {e}")
|
||||
time.sleep(TELEGRAM_CHECK_INTERVAL) # Continue after error
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Shutting down Telegram Monitor...")
|
||||
|
||||
logger.info("Telegram Monitor stopped")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
7
telegram_monitor_state.json
Normal file
7
telegram_monitor_state.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"last_known_open_positions": [
|
||||
5173016
|
||||
],
|
||||
"last_processed_timestamp": 1766572620,
|
||||
"last_file_hash": "ebd0d929cd0b382919cfc9e5a9523006"
|
||||
}
|
||||
70
todo/ANALYSIS_TEMPLATE.md
Normal file
70
todo/ANALYSIS_TEMPLATE.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Analysis Request: [Insert Topic Here]
|
||||
|
||||
**Status:** [Draft / Pending Analysis / Completed]
|
||||
**Date:** [YYYY-MM-DD]
|
||||
**Priority:** [Low / Medium / High]
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
*(User: Fill this section with your design ideas, questions, code snippets, or links to files/web resources. Be as specific as possible about the goal.)*
|
||||
|
||||
### Context
|
||||
* **Goal:**
|
||||
* **Current Behavior:**
|
||||
* **Desired Behavior:**
|
||||
|
||||
### References
|
||||
* **Files:** `[filename.py]`, `[path/to/module]`
|
||||
* **Links:** `[url]`
|
||||
|
||||
### Specific Questions / Hypothesis
|
||||
1.
|
||||
2.
|
||||
|
||||
---
|
||||
|
||||
*(The sections below are to be filled by the AI Agent upon request)*
|
||||
|
||||
## 2. Agent Summary
|
||||
*(AI: Summarize the user's request to ensure alignment on the objective.)*
|
||||
|
||||
* **Objective:**
|
||||
* **Key Constraints:**
|
||||
* **Scope:**
|
||||
|
||||
## 3. Main Analysis
|
||||
*(AI: Perform the deep dive here. Use codebase knowledge, logic, and simulations.)*
|
||||
|
||||
### 3.1 Codebase Investigation
|
||||
* **Affected Components:**
|
||||
* **Data Flow Analysis:**
|
||||
* **Current Limitation/Bug:**
|
||||
|
||||
### 3.2 Technical Options / Trade-offs
|
||||
| Option | Pros | Cons | Complexity |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **A. [Strategy A]** | ... | ... | ... |
|
||||
| **B. [Strategy B]** | ... | ... | ... |
|
||||
|
||||
### 3.3 Proposed Solution Design
|
||||
* **Architecture:**
|
||||
* **Logic Changes:**
|
||||
* **Edge Cases Considered:**
|
||||
|
||||
## 4. Risk Assessment
|
||||
*(AI: What could go wrong? Performance, Security, or Stability impacts.)*
|
||||
|
||||
* **Risk 1:** [Description] -> *Mitigation:* [Strategy]
|
||||
|
||||
## 5. Conclusion
|
||||
*(AI: Final verdict and recommendation.)*
|
||||
|
||||
* **Recommendation:**
|
||||
|
||||
## 6. Implementation Plan
|
||||
*(AI: Step-by-step checklist to execute the recommendation.)*
|
||||
|
||||
- [ ] Step 1:
|
||||
- [ ] Step 2:
|
||||
- [ ] Step 3:
|
||||
BIN
todo/ATR_over_week.PNG
Normal file
BIN
todo/ATR_over_week.PNG
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
43
todo/DYNAMIC_TIMEOUT_ANALYSIS.md
Normal file
43
todo/DYNAMIC_TIMEOUT_ANALYSIS.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Analysis Request: Dynamic Timeout for Shadow Orders
|
||||
|
||||
**Status:** Completed
|
||||
**Date:** 2025-12-20
|
||||
**Priority:** Medium
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
**Goal:** Optimize the Shadow Order simulation by making the "Time to Live" (timeout) dynamic based on market volatility.
|
||||
**Logic:**
|
||||
* **Low Volatility (Slow Market):** Orders sit on the book longer. Give the shadow order more time (e.g., 60s) to fill.
|
||||
* **High Volatility (Fast Market):** Price moves quickly. If it doesn't fill instantly, it likely ran away. Timeout should be short (e.g., 10s) to fail fast.
|
||||
|
||||
## 2. Agent Summary
|
||||
* **Objective:** Implement a dynamic timeout calculation during shadow order creation.
|
||||
* **Formula:** Inverse relationship with volatility.
|
||||
|
||||
## 3. Main Analysis
|
||||
|
||||
### 3.1 Volatility Scaling
|
||||
We already calculate `vol_pct` (5-min rolling StdDev).
|
||||
* **Base Vol:** `0.05%` (0.0005).
|
||||
* **Base Timeout:** 30 seconds.
|
||||
|
||||
**Formula:**
|
||||
`timeout = Base_Timeout * (Base_Vol / Current_Vol)`
|
||||
|
||||
**Examples:**
|
||||
* **Low Vol (0.025%):** `30 * (0.05 / 0.025) = 60s` (Max cap).
|
||||
* **Normal Vol (0.05%):** `30 * (0.05 / 0.05) = 30s`.
|
||||
* **High Vol (0.15%):** `30 * (0.05 / 0.15) = 10s`.
|
||||
|
||||
### 3.2 Constraints
|
||||
* **Min Timeout:** 10s (Give at least some chance even in crazy markets).
|
||||
* **Max Timeout:** 60s (Don't track stale orders forever).
|
||||
|
||||
## 4. Conclusion
|
||||
**Recommendation:** Implement this dynamic timeout alongside the shadow order logic. It makes the "Success/Fail" metric much more realistic for a Maker strategy in different regimes.
|
||||
|
||||
## 5. Implementation Plan
|
||||
- [ ] **Step 1:** Add `get_dynamic_timeout(vol_pct)` helper method.
|
||||
- [ ] **Step 2:** Use this timeout when creating the shadow order entry.
|
||||
44
todo/EXTENDED_SHADOW_TIMEOUT.md
Normal file
44
todo/EXTENDED_SHADOW_TIMEOUT.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Analysis Request: Extended Timeout for Shadow Orders
|
||||
|
||||
**Status:** Completed
|
||||
**Date:** 2025-12-20
|
||||
**Priority:** Medium
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
**Goal:** Extend the Shadow Order timeout to **10 minutes (600s)** to capture the "Mean Time to Execution" across different market conditions.
|
||||
**Reasoning:** Instead of failing fast (which assumes Maker is bad if not instant), we want to gather data on *how long* it actually takes to fill. This helps optimize the timeout later.
|
||||
|
||||
## 2. Agent Summary
|
||||
* **Objective:** Modify `clp_hedger.py` to use a long fixed timeout (or much longer dynamic timeout) for shadow orders.
|
||||
* **Risk:** "Success" at 9 minutes is effectively a "Failure" for hedging (Delta Drift).
|
||||
* **Mitigation:** We are collecting *data*, not executing trades. A 9-minute fill log is valuable data point (it tells us "Maker is impossible here").
|
||||
|
||||
## 3. Main Analysis
|
||||
|
||||
### 3.1 Data Value vs. Hedging Reality
|
||||
* **Hedging Reality:** If a hedge takes > 30s to fill, the price has likely moved significantly. The "Hedge" is no longer hedging the original risk.
|
||||
* **Data Value:** By waiting 10 minutes, we can generate a distribution curve:
|
||||
* *50% fill in < 5s* (Great!)
|
||||
* *30% fill in 5s-60s* (Okay for stable markets)
|
||||
* *20% fill in > 60s* (Terrible)
|
||||
* *If we used a 60s timeout, we would just see "20% Failed", losing the nuance.*
|
||||
|
||||
### 3.2 Implementation Strategy
|
||||
Instead of a complex dynamic timeout for now, let's set a **Fixed Long Timeout (600s)** for the Shadow Simulator.
|
||||
* **Why?** We want to see the *actual* fill time for every order, not cut it off artificially.
|
||||
* **Logging:** The log `filled in X.Xs` becomes the primary metric.
|
||||
|
||||
### 3.3 Memory Impact
|
||||
* Even with 1 trade per minute, 10 minutes = 10 items in the list.
|
||||
* Memory usage is negligible (<1KB).
|
||||
|
||||
## 4. Conclusion
|
||||
**Recommendation:** Switch the Shadow Order logic to use a **Fixed 600s Timeout**.
|
||||
* This turns the simulator into a "Fill Time Data Collector".
|
||||
* We can analyze the logs later to find the "Optimal Timeout" (e.g., "95% of fills happen within 45s, so set timeout to 45s").
|
||||
|
||||
## 5. Implementation Plan
|
||||
- [ ] **Step 1:** In `clp_hedger.py`, replace the dynamic timeout calculation with `timeout = 600`.
|
||||
- [ ] **Step 2:** Update logging to ensure `fill_time` is prominent.
|
||||
57
todo/KPI_IMPLEMENTATION_PLAN.md
Normal file
57
todo/KPI_IMPLEMENTATION_PLAN.md
Normal file
@ -0,0 +1,57 @@
|
||||
# KPI Implementation Proposal
|
||||
|
||||
**Status:** Proposed
|
||||
**Date:** 2025-12-20
|
||||
|
||||
## 1. Objective
|
||||
Implement a robust KPI tracking system to answer: "Is this strategy actually making money compared to HODLing?"
|
||||
|
||||
## 2. Architecture
|
||||
We will introduce a lightweight **KPI Module** (`tools/kpi_tracker.py`) that is called periodically by `uniswap_manager.py`.
|
||||
|
||||
### A. Data Sources
|
||||
1. **Uniswap V3:** Current Position Value + Unclaimed Fees (from `uniswap_manager.py`).
|
||||
2. **Hyperliquid:** Equity + Unrealized PnL (from `clp_hedger.py` / API).
|
||||
3. **Wallet:** ETH/USDC balances (from Web3).
|
||||
4. **History:** Initial Amounts (from `hedge_status.json`).
|
||||
|
||||
### B. The Metrics (KPIs)
|
||||
|
||||
#### 1. Net Asset Value (NAV)
|
||||
* `NAV = (Uniswap Pos Value) + (Hyperliquid Equity) + (Wallet ETH * Price) + (Wallet USDC)`
|
||||
* *Note:* Allows tracking total portfolio health.
|
||||
|
||||
#### 2. Strategy vs. Benchmark (Alpha)
|
||||
* **Strategy Value:** `Current NAV`
|
||||
* **Benchmark Value (HODL):**
|
||||
* Snapshot at start: `Initial ETH` + `Initial USDC`.
|
||||
* Current Val: `(Initial ETH * Current Price) + Initial USDC`.
|
||||
* **Alpha:** `Strategy Value - Benchmark Value`.
|
||||
|
||||
#### 3. Fee Coverage Ratio
|
||||
* `Ratio = (Uniswap Fees Earned) / (Hedge Cost)`
|
||||
* *Hedge Cost:* Fees paid on Hyperliquid + Funding Paid.
|
||||
|
||||
## 3. Implementation Plan
|
||||
|
||||
### Step 1: Create `tools/kpi_tracker.py`
|
||||
This module will handle the math and logging.
|
||||
* **Functions:**
|
||||
* `log_kpi_snapshot(nav_data, market_data)`: Appends to CSV.
|
||||
* `calculate_benchmark(initial_snapshot, current_price)`: Returns HODL value.
|
||||
|
||||
### Step 2: CSV Schema (`logs/kpi_history.csv`)
|
||||
| Timestamp | NAV | Benchmark_NAV | Alpha | Uniswap_Fees_Acc | Hedge_Cost_Acc | Fee_Coverage | ETH_Price |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| ... | ... | ... | ... | ... | ... | ... | ... |
|
||||
|
||||
### Step 3: Integration
|
||||
* **Hook into `uniswap_manager.py`:**
|
||||
* Every loop (or every hour), gather data.
|
||||
* Call `kpi_tracker.log_kpi_snapshot()`.
|
||||
* **Note:** `uniswap_manager.py` manages the slow loop, so it's the perfect place to record "macro" performance without adding latency to the hedger.
|
||||
|
||||
## 4. Next Steps
|
||||
1. Approve this plan?
|
||||
2. I will generate `tools/kpi_tracker.py`.
|
||||
3. I will integrate it into `uniswap_manager.py`.
|
||||
79
todo/MAKER_VS_TAKER_ANALYSIS.md
Normal file
79
todo/MAKER_VS_TAKER_ANALYSIS.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Analysis Request: Maker vs Taker for Hedging Optimization
|
||||
|
||||
**Status:** Completed
|
||||
**Date:** 2025-12-20
|
||||
**Priority:** Medium
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
**Goal:** Determine if using **Maker (Alo)** orders instead of **Taker (Ioc)** orders for "safe" rebalancing (in the middle of the range) would improve PnL.
|
||||
**Hypothesis:** Maker orders earn rebates (or pay lower fees) and capture the spread, but risk non-execution (delta drift). Taker orders pay fees and cross the spread but guarantee immediate hedging.
|
||||
|
||||
### Specific Questions
|
||||
1. What data is needed to evaluate this trade-off?
|
||||
2. How to measure the "Cost of Waiting" (Delta Drift)?
|
||||
|
||||
---
|
||||
|
||||
## 2. Agent Summary
|
||||
* **Objective:** Define the data requirements to backtest/simulate a "Maker Hedging Strategy."
|
||||
* **Current State:** `clp_hedger.py` predominantly uses Taker orders (`Ioc`) for rebalancing to ensure the hedge matches the Uniswap delta instantly. Maker orders (`Alo`) are only used for passive closing.
|
||||
|
||||
## 3. Main Analysis
|
||||
|
||||
### 3.1 The Trade-off Equation
|
||||
To know if Maker is better, we must solve:
|
||||
$$ \text{Gain} > \text{Loss} $$
|
||||
$$ (\text{Spread Capture} + \text{Fee Rebate}) > (\text{Delta Drift Cost} + \text{Opportunity Cost}) $$
|
||||
|
||||
* **Spread Capture:** Selling at Ask vs. Selling at Bid.
|
||||
* **Fee Rebate:** Hyperliquid pays rebates for Maker orders (e.g., 0.02%) vs charging Taker fees (e.g., 0.035%). Total swing ~0.055%.
|
||||
* **Delta Drift:** While your Maker order sits on the book, the price moves. Your Uniswap LP delta changes, but your hedge delta doesn't. You are "under-hedged" or "over-hedged" for those seconds.
|
||||
|
||||
### 3.2 Required Data for Simulation
|
||||
|
||||
To simulate this, we cannot just look at OHLC candles. We need **Tick-Level Order Book Data**.
|
||||
|
||||
#### A. Order Book Snapshots (The "Opportunity")
|
||||
* **Metric:** **Bid-Ask Spread Width** over time.
|
||||
* *Why:* If the spread is 0.01% (tight), Maker offers little gain. If it's 0.1% (wide), capturing it is huge.
|
||||
* *Data:* `timestamp`, `best_bid`, `best_ask`.
|
||||
|
||||
#### B. Trade Flow / Fill Probability (The "Risk")
|
||||
* **Metric:** **Time to Fill (at Best Bid/Ask)**.
|
||||
* *Why:* If you place a Maker buy at Best Bid, how long until someone sells into you? 1 second? 1 minute?
|
||||
* *Data:* You need a recording of **all public trades** on Hyperliquid to match against your theoretical order price.
|
||||
|
||||
#### C. Price Velocity (The "Drift")
|
||||
* **Metric:** **Price Change per Second** during the "Wait Time".
|
||||
* *Why:* If you wait 5 seconds for a fill, and ETH moves 0.2%, you just lost 0.2% in unhedged exposure to save 0.05% in fees. Bad trade.
|
||||
|
||||
### 3.3 Implemented Solution: Shadow Order Simulator
|
||||
The system now runs a real-time simulation engine to verify Maker feasibility without risking capital.
|
||||
|
||||
#### Mechanism:
|
||||
1. **Creation:** Every successful Taker trade (`Ioc`) triggers the creation of a "Shadow Order" at the passive price (Bid for Buy, Ask for Sell).
|
||||
2. **Dynamic Timeout:** The "Time to Live" for the simulation is inversely proportional to volatility (`calculate_volatility()`):
|
||||
* **Low Vol (Quiet):** Wait up to **60s**.
|
||||
* **High Vol (Fast):** Timeout after **10s**.
|
||||
* *Rationale:* In fast markets, if a fill isn't immediate, the price has likely moved too far, making a Maker strategy dangerous.
|
||||
3. **Verification:** The main loop checks these shadow orders every tick against current `Ask` (for Buy) or `Bid` (for Sell) prices to confirm if a fill *definitely* would have occurred.
|
||||
|
||||
#### Data Captured:
|
||||
* `[SHADOW] SUCCESS`: Maker order would have filled (capturing spread + rebate).
|
||||
* `[SHADOW] FAILED`: Price ran away (Taker was the correct choice to prevent delta drift).
|
||||
|
||||
## 4. Risk Assessment
|
||||
* **Risk:** **Adverse Selection.** Market makers (bots) are faster than you. They will only fill your order when the market is moving *against* you (e.g., you are buying, price is crashing).
|
||||
* *Mitigation:* Analyze "Time to Success" logs. If successes only happen at the very end of the 60s window, the "Drift Cost" might exceed the "Spread Gain."
|
||||
|
||||
## 5. Conclusion
|
||||
**Recommendation:**
|
||||
1. **Monitor Logs:** Let the simulator run for 48-72 hours across different market regimes (Weekend vs. Weekday).
|
||||
2. **Decision Metric:** If Success Rate > 80% and Avg Fill Time < 15s, proceed to implement a "Passive First" hedging mode for the safe center of the CLP range.
|
||||
|
||||
## 6. Implementation Plan
|
||||
- [x] **Step 1:** Update `clp_hedger.py` to fetch and log `Bid` and `Ask` explicitly during execution.
|
||||
- [x] **Step 2:** Implement `check_shadow_orders` and state management in `ScalperHedger`.
|
||||
- [x] **Step 3:** Implement dynamic timeout logic based on rolling StdDev.
|
||||
68
todo/SHADOW_ORDER_ANALYSIS.md
Normal file
68
todo/SHADOW_ORDER_ANALYSIS.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Analysis Request: Implement Shadow Order Verification
|
||||
|
||||
**Status:** Completed
|
||||
**Date:** 2025-12-20
|
||||
**Priority:** Medium
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
**Goal:** Verify if a Maker order (at the opposite side of the book) *would* have been filled if we had used it instead of a Taker order.
|
||||
**Mechanism:**
|
||||
1. When a Taker trade executes, record a "Shadow Order" at the *passive* price (e.g., if Taker Buying at Ask, Shadow Buy at Bid).
|
||||
2. Check the market for the next 15 seconds.
|
||||
3. If price crosses the Shadow Price, log "Success". If 15s passes without fill, log "Failed".
|
||||
|
||||
## 2. Agent Summary
|
||||
* **Objective:** Implement a lightweight "Shadow Order Simulator" inside `clp_hedger.py`.
|
||||
* **Key Logic:**
|
||||
* `Shadow Buy` at $3000 (Current Bid). *Fills if Future Ask <= $3000.*
|
||||
* `Shadow Sell` at $3001 (Current Ask). *Fills if Future Bid >= $3001.*
|
||||
* **Wait Time:** 15 seconds max.
|
||||
|
||||
## 3. Main Analysis
|
||||
|
||||
### 3.1 Logic Changes in `clp_hedger.py`
|
||||
|
||||
#### A. Data Structure
|
||||
Add `self.shadow_orders` list to `ScalperHedger`.
|
||||
```python
|
||||
self.shadow_orders = [
|
||||
{
|
||||
'id': 'shadow_123',
|
||||
'side': 'BUY',
|
||||
'price': 3000.0,
|
||||
'created_at': 1700000000,
|
||||
'expires_at': 1700000015
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### B. Creation Trigger
|
||||
Inside the `if oid:` block (successful Taker trade):
|
||||
1. Determine **Passive Price**:
|
||||
* If Taker BUY -> Passive Price = `levels['bid']` (Best Bid).
|
||||
* If Taker SELL -> Passive Price = `levels['ask']` (Best Ask).
|
||||
2. Append to `self.shadow_orders`.
|
||||
|
||||
#### C. Verification Loop
|
||||
Inside the main `while True` loop:
|
||||
1. Iterate through `self.shadow_orders`.
|
||||
2. **Check Fill:**
|
||||
* Shadow BUY fills if `current_ask <= shadow_price`. (Someone sold into our bid).
|
||||
* Shadow SELL fills if `current_bid >= shadow_price`. (Someone bought our ask).
|
||||
3. **Check Expiry:** If `now > expires_at`, mark as FAILED (Price ran away).
|
||||
4. **Log & Remove:** Log the result (`[SHADOW] SUCCESS/FAIL`) and remove from list.
|
||||
|
||||
### 3.2 Technical Trade-offs
|
||||
* **Pros:** Real-time, empirical data on "Maker Feasibility" without risking capital.
|
||||
* **Cons:** Slight memory usage (negligible). Requires accurate `levels` data every loop.
|
||||
|
||||
## 4. Conclusion
|
||||
**Recommendation:** Proceed with implementation. This provides the exact "Cost of Waiting" vs "Gain from Spread" data requested.
|
||||
|
||||
## 5. Implementation Plan
|
||||
- [ ] **Step 1:** Initialize `self.shadow_orders` in `__init__`.
|
||||
- [ ] **Step 2:** Add `check_shadow_orders(levels)` method.
|
||||
- [ ] **Step 3:** Call `check_shadow_orders` in main loop.
|
||||
- [ ] **Step 4:** Record new shadow order after `place_limit_order` (Taker) success.
|
||||
118
todo/data_needed_for_optymalization.md
Normal file
118
todo/data_needed_for_optymalization.md
Normal file
@ -0,0 +1,118 @@
|
||||
# Analysis Request: Data needed for system optimization
|
||||
|
||||
**Status:** Completed
|
||||
**Date:** 2025-12-20
|
||||
**Priority:** Medium
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
**Goal:** Check and analyze what kind of historical data is needed for further optimization of the system.
|
||||
**Context:** Learning from the past how the system worked (good and bad) will help in the future. Assume that any kind of data publicly available can be used.
|
||||
**Desired Behavior:** List of data which will help to improve now and in the future.
|
||||
|
||||
### Specific Questions
|
||||
1. Price data, what kind of?
|
||||
2. CSV, DB?
|
||||
3. Source of data (Hyperliquid, Uniswap)?
|
||||
4. Other sources of data? Please propose.
|
||||
|
||||
---
|
||||
|
||||
## 2. Agent Summary
|
||||
* **Objective:** Define a comprehensive data strategy to support backtesting, parameter optimization, and performance analysis for the Uniswap CLP + Hyperliquid Hedger system.
|
||||
* **Key Constraints:**
|
||||
* **High Frequency:** Hedging logic runs on ~1s ticks. 1-minute candles are insufficient for simulating slippage and "whipsaw" events.
|
||||
* **Dual Venue:** Must correlate Uniswap V3 (Spot/Liquidity) events with Hyperliquid (Perp/Hedge) actions.
|
||||
* **Storage:** High-frequency data grows rapidly; format matters.
|
||||
|
||||
## 3. Main Analysis
|
||||
|
||||
### 3.1 Data Types Required
|
||||
|
||||
To fully reconstruct and optimize the strategy, you need three distinct layers of data:
|
||||
|
||||
#### A. Market Data (The "Environment")
|
||||
1. **Tick-Level Trades (Hyperliquid):**
|
||||
* *Why:* To simulate realistic slippage, fill probability, and exact trigger timing for the hedger.
|
||||
* *Fields:* `timestamp_ms`, `price`, `size`, `side`, `liquidation (bool)`.
|
||||
2. **Order Book Snapshots (Hyperliquid):**
|
||||
* *Why:* To calculate "effective impact price" for large hedges. The mid-price might be $3000, but selling $50k might execute at $2998.
|
||||
* *Frequency:* Every 1-5 seconds.
|
||||
3. **Uniswap V3 Pool Events (Arbitrum):**
|
||||
* *Why:* To track the exact "Health" of the CLP. Knowing when the price crosses a tick boundary is critical for "In Range" status.
|
||||
* *Events:* `Swap` (Price changes), `Mint`, `Burn`.
|
||||
|
||||
#### B. System State Data (The "Bot's Brain")
|
||||
* *Why:* To understand *why* the bot made a decision. A trade might look bad in hindsight, but was correct given the data available at that millisecond.
|
||||
* *Fields:* `timestamp`, `current_hedge_delta`, `target_hedge_delta`, `rebalance_threshold_used`, `volatility_metric`, `pnl_unrealized`, `pnl_realized`.
|
||||
|
||||
#### C. External "Alpha" Data (Optimization Signals)
|
||||
* **Funding Rates (Historical):** To optimize long/short bias.
|
||||
* **Gas Prices (Arbitrum):** To optimize mint/burn timing (don't rebalance CLP if gas > expected fees).
|
||||
* **Implied Volatility (Deribit/Derebit Options):** Compare realized vol vs. implied vol to adjust `DYNAMIC_THRESHOLD_MULTIPLIER`.
|
||||
|
||||
### 3.2 Technical Options / Trade-offs
|
||||
|
||||
| Option | Pros | Cons | Complexity |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **A. CSV Files (Flat)** | Simple, human-readable, portable. Good for daily logs. | Slow to query large datasets. Hard to merge multiple streams (e.g., matching Uniswap swap to HL trade). | Low |
|
||||
| **B. SQLite (Local DB)** | Single file, supports SQL queries, better performance than CSV. | Concurrency limits (one writer). Not great for massive tick data (TB scale). | Low-Medium |
|
||||
| **C. Time-Series DB (InfluxDB / QuestDB)** | Optimized for high-frequency timestamps. Native downsampling. | Requires running a server/container. Overkill for simple analysis? | High |
|
||||
| **D. Parquet / HDF5** | Extremely fast read/write for Python (Pandas). High compression. | Not human-readable. Best for "Cold" storage (backtesting). | Medium |
|
||||
|
||||
### 3.3 Proposed Solution Design
|
||||
|
||||
#### Architecture: "Hot" Logging + "Cold" Archival
|
||||
1. **Live Logging (Hot):** Continue using `JSON` status files and `Log` files for immediate state.
|
||||
2. **Data Collector Script:** A separate process (or async thread) that dumps high-frequency data into **daily CSVs** or **Parquet** files.
|
||||
3. **Backtest Engine:** A Python script that loads these Parquet files to simulate "What if threshold was 0.08 instead of 0.05?".
|
||||
|
||||
#### Data Sources
|
||||
* **Hyperliquid:** Public API (Info) provides L2 snapshots and recent trade history.
|
||||
* **Uniswap:** The Graph (Subgraphs) or RPC `eth_getLogs`.
|
||||
* **Dune Analytics:** Great for exporting historical Uniswap V3 data (fees, volumes) to CSV for free/cheap.
|
||||
|
||||
### 3.4 KPI & Performance Metrics
|
||||
To truly evaluate "Success," we need more than just PnL. We need to compare against benchmarks.
|
||||
|
||||
1. **NAV vs. Benchmark (HODL):**
|
||||
* *Metric:* `(Current Wallet Value + Position Value) - (Net Inflows)` vs. `(Initial ETH * Current Price)`.
|
||||
* *Goal:* Did we beat simply holding ETH?
|
||||
* *Frequency:* Hourly.
|
||||
|
||||
2. **Hedging Efficiency (Delta Neutrality):**
|
||||
* *Metric:* `Net Delta Exposure = (Uniswap Delta + Hyperliquid Delta)`.
|
||||
* *Goal:* Should be close to 0. A high standard deviation here means the bot is "loose" or slow.
|
||||
* *Frequency:* Per-Tick (or aggregated per minute).
|
||||
|
||||
3. **Cost of Hedge (The "Insurance Premium"):**
|
||||
* *Metric:* `(Hedge Fees Paid + Funding Paid + Hedge Slippage) / Total Portfolio Value`.
|
||||
* *Goal:* Keep this below the APR earned from Uniswap fees.
|
||||
* *Frequency:* Daily.
|
||||
|
||||
4. **Fee Coverage Ratio:**
|
||||
* *Metric:* `Uniswap Fees Earned / Cost of Hedge`.
|
||||
* *Goal:* Must be > 1.0. If < 1.0, the strategy is burning money to stay neutral.
|
||||
* *Frequency:* Daily.
|
||||
|
||||
5. **Impermanent Loss (IL) Realized:**
|
||||
* *Metric:* Value lost due to selling ETH low/buying high during CLP rebalances vs. Fees Earned.
|
||||
* *Frequency:* Per-Rebalance.
|
||||
|
||||
## 4. Risk Assessment
|
||||
* **Risk:** **Data Gaps.** If the bot goes offline, you miss market data.
|
||||
* *Mitigation:* Use public historical APIs (like Hyperliquid's archive or Dune) to fill gaps, rather than relying solely on local recording.
|
||||
* **Risk:** **Storage Bloat.** Storing every millisecond tick can fill a hard drive in weeks.
|
||||
* *Mitigation:* Aggregate. Store "1-second OHLC" + "Tick Volume" instead of every raw trade, unless debugging specific slippage events.
|
||||
|
||||
## 5. Conclusion
|
||||
**Recommendation:**
|
||||
1. **Immediate:** Start logging **Internal System State** (Thresholds, Volatility metrics) to a structured CSV (`hedge_metrics.csv`). You can't get this from public APIs later.
|
||||
2. **External Data:** Don't build a complex scraper yet. Rely on downloading public data (Dune/Hyperliquid) when you are ready to backtest.
|
||||
3. **Format:** Use **Parquet** (via Pandas) for storing price data. It's 10x faster and smaller than CSV.
|
||||
|
||||
## 6. Implementation Plan
|
||||
- [ ] **Step 1:** Create `tools/data_collector.py` to fetch and save public trade history (HL) daily.
|
||||
- [ ] **Step 2:** Modify `clp_hedger.py` to append "Decision Metrics" (Vol, Threshold, Delta) to a `metrics.csv` every loop.
|
||||
- [ ] **Step 3:** Use a notebook (Colab/Jupyter) to load `metrics.csv` and visualize "Threshold vs. Price Deviation".
|
||||
96
todo/fishing_orders.md
Normal file
96
todo/fishing_orders.md
Normal file
@ -0,0 +1,96 @@
|
||||
# [Feature Name / Analysis Topic] - fishing orders
|
||||
|
||||
**Status:** in progress
|
||||
**Started:** 2025/12/23
|
||||
**Last Updated:** `[YYYY-MM-DD]`
|
||||
**Tags:** `[Tag1, Tag2]`
|
||||
|
||||
---
|
||||
|
||||
## 1. Origin & Context (The "Why")
|
||||
*This section captures the inception of the idea. What was the problem, and what was the initial vision?*
|
||||
|
||||
* **Goal:** create additional reveniue, increase TotPnL,
|
||||
* **Initial State:** currently we are under the water in total (clp position+fees+ TotPnL if closed hedge) ~$10-20, after 24h (clp position 2000, range +/-1%, fees $5)
|
||||
* **Trigger:** to make a hedger real delta-zero to clp position
|
||||
* **Original Hypothesis:** make small reveniue will help with it
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2. Chronological Evolution (The Journey)
|
||||
*A living diary of the process. Add new entries top-down as the feature evolves. This is the story of "How we got here".*
|
||||
|
||||
### 📅 Entry 1: 2025-12-23 - Initial Implementation
|
||||
* **Objective:** Implement a "Fishing" mechanism to capture Maker rebates and reduce hedge size at break-even prices.
|
||||
* **Investigation:**
|
||||
* *Findings:* Identified that when the price is in the "Safe Zone" (near entry), the bot sits idle. This is an opportunity to place resting Maker orders to "fish" for better entries/exits than standard Taker rebalances.
|
||||
* **Current Implementation:**
|
||||
* **Logic:** Places a Maker BUY order (`Alo` - Add Liquidity Only) at the `hedge_entry_price` when current market price is above entry.
|
||||
* **Size:** 10% of the target hedge size (`FISHING_ORDER_SIZE_PCT = 0.10`).
|
||||
* **Management:** The order is tracked via `self.fishing_oid` and is automatically cancelled before any standard rebalance trade to avoid size conflicts.
|
||||
* **Decision:** Start with a conservative 10% size and only for closing/reducing the hedge (Buy-to-Close when price is above entry) to validate the "Maker fill" probability without over-exposing.
|
||||
|
||||
### 📅 Entry 2: 2025-12-23 - Analysis of Symmetric Fishing (Bottom-Side)
|
||||
* **Objective:** Analyze the potential of adding a "Sell" fishing order when price is below entry (Symmetric Grid).
|
||||
* **Investigation:**
|
||||
* *Scenario:* Price is 1900 (Down). Entry is 2000. Hedger is Short.
|
||||
* *Logic:* A Limit SELL at 2000 is a valid Maker order (Above current price).
|
||||
* *Effect:* If filled, it increases the Short position at 2000. Since the neutral target at 2000 is fixed, this results in being slightly "Over-Hedged" (Short Delta) at Entry.
|
||||
* *Strategic Value:* This creates a "Mean Reversion" or "Grid" effect around the entry.
|
||||
* *Top Side (Buy at Entry):* Profitable if price drops to Entry and bounces up.
|
||||
* *Bottom Side (Sell at Entry):* Profitable if price rises to Entry and rejects down.
|
||||
* *Verdict:* This is beneficial. It helps offset hedging costs (which typically lose money in chop) by actively trading the chop around the entry price.
|
||||
* **Decision:** Implement the symmetric bottom-side fishing order.
|
||||
* If `Price < Entry`: Place Maker SELL at Entry.
|
||||
* If `Price > Entry`: Place Maker BUY at Entry.
|
||||
|
||||
### 📅 Entry 3: 2025-12-23 - Implementation of Symmetric Fishing
|
||||
* **Action Taken:** Modified `clp_hedger.py` to allow fishing orders on both sides of the entry price.
|
||||
* **Result:**
|
||||
* When `Price > Entry`: Places Maker **BUY** at Entry (Reduce Short).
|
||||
* When `Price < Entry`: Places Maker **SELL** at Entry (Increase Short).
|
||||
* **Safety Features:**
|
||||
* Added a 0.1% price buffer (`dist_pct > 0.001`) to prevent placing orders too close to the market price, which could result in unintended Taker trades or API rejections for `Alo` orders.
|
||||
* Maintains the existing 10% size restriction to ensure fishing doesn't drastically warp the delta profile.
|
||||
|
||||
---
|
||||
|
||||
## 3. Final Technical Solution (The "How")
|
||||
*The consolidated, final design documentation.*
|
||||
|
||||
### 3.1 Architecture & Logic
|
||||
* **Core Components:** `ScalperHedger` class in `clp_hedger.py`.
|
||||
* **Data Flow:**
|
||||
1. Bot checks for active standard rebalance orders. If none exist, it enters "Fishing Mode".
|
||||
2. Compares `current_mid_price` against the `hedge_entry_price` from the exchange.
|
||||
3. Determines side: BUY if price is above entry, SELL if price is below entry.
|
||||
4. Places an `Alo` (Add Liquidity Only) Limit order at the exact `hedge_entry_price`.
|
||||
* **Configuration:**
|
||||
* `FISHING_ORDER_SIZE_PCT = 0.10`: Size of the fishing order relative to the current target hedge.
|
||||
|
||||
### 3.2 Key Constraints & Edge Cases
|
||||
* **Safe Zone Only:** Only active when no other orders are pending.
|
||||
* **Rebalance Priority:** Automatically cancelled before any rebalance trade to avoid over-exposure.
|
||||
* **Maker Buffer:** Only placed if market price is >0.1% away from entry to ensure Maker status.
|
||||
|
||||
---
|
||||
|
||||
## 4. Retrospective (The "Cookbook" of Fails & Ups)
|
||||
*Summary of lessons learned. "Read this before touching this code again."*
|
||||
|
||||
### ❌ What Failed (Pitfalls to Avoid)
|
||||
* *Trap:* [Don't try doing X, because Y will happen.]
|
||||
* *False Assumption:* [We thought Z was true, but it wasn't.]
|
||||
|
||||
### ✅ What Worked (Success Factors)
|
||||
* *Key Insight:* [The breakthrough was...]
|
||||
* *Design Pattern:* [This specific pattern proved robust.]
|
||||
|
||||
---
|
||||
|
||||
## 5. Artifacts & References
|
||||
* **Affected Files:**
|
||||
* `[path/to/file.py]`
|
||||
* **Commits:** `[git hash or description]`
|
||||
* **External Links:** `[URLs]`
|
||||
160
todo/rebalance threshold/optymalization rebalance_threshol.md
Normal file
160
todo/rebalance threshold/optymalization rebalance_threshol.md
Normal file
@ -0,0 +1,160 @@
|
||||
# Analysis Request: Optimization of Rebalance Threshold with ATR
|
||||
|
||||
**Status:** Completed
|
||||
**Date:** 2025-12-21
|
||||
**Priority:** Medium
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
**Goal:** Optimize `rebalance_threshold` in `clp_hedger.py` to save hedging trading costs.
|
||||
**Context:** Volatility changes over the week (e.g., lower on weekends, higher on weekdays/events).
|
||||
**Desired Behavior:** `rebalance_threshold` should dynamically adjust based on market conditions (volatility).
|
||||
|
||||
### Specific Questions
|
||||
1. Time frame and period of ATR to be used?
|
||||
2. Impact of CLP Range (+/- 0.5% to 3.5%)?
|
||||
3. Impact of CLP Size (1k - 10k)?
|
||||
4. Other metrics?
|
||||
|
||||
|
||||
## NEW
|
||||
|
||||
### Specific Questions / taks
|
||||
1. find rebalace_threshold to low volality markets
|
||||
|
||||
### References
|
||||
* **Files:** \01\clp_hedger.log, \01\hedge_status.json, \01\uniswap_manager.log
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2. Agent Summary
|
||||
* **Objective:** Implement a dynamic rebalancing threshold mechanism that widens during high volatility (to avoid noise trading) and tightens during low volatility (to maintain precise hedging), while respecting the constraints of range width and position size.
|
||||
* **Key Constraints:**
|
||||
* **Cost vs. Risk:** Reducing trades saves fees but increases delta exposure risk.
|
||||
* **Latency:** Calculation must be lightweight (tick-by-tick loop).
|
||||
* **Safety:** Threshold must never be so wide that it allows the position to become dangerously unhedged.
|
||||
|
||||
## 3. Main Analysis
|
||||
|
||||
### 3.1 Codebase Investigation
|
||||
* **Current Logic:**
|
||||
* `rebalance_threshold` is currently `max(MIN_THRESHOLD_ETH, max_potential_eth * 0.05)`.
|
||||
* It effectively targets rebalancing when the delta deviation exceeds 5% of the max possible delta.
|
||||
* There is a simple "Volatility Adjustment" that multiplies the threshold by `DYNAMIC_THRESHOLD_MULTIPLIER` (1.2x or 1.3x) if the *instantaneous* price change > 0.3%.
|
||||
* **Data Availability:**
|
||||
* `clp_hedger.py` maintains `self.price_history` (list of recent prices).
|
||||
* It runs on a `CHECK_INTERVAL` (1s loop).
|
||||
|
||||
### 3.2 Strategy & Logic Design
|
||||
|
||||
#### A. Volatility Metric (ATR Proxy)
|
||||
Since we are in a tight loop without external candle data, we should calculate an **internal volatility metric**.
|
||||
* **Method:** "Rolling Standard Deviation" or "Average Absolute Deviation" of the last N ticks.
|
||||
* **Window:** 5 minutes (300 ticks @ 1s interval) is a good balance between responsiveness and stability.
|
||||
* **Calculation:** `volatility_pct = (current_price - moving_average) / moving_average`.
|
||||
|
||||
#### B. Dynamic Threshold Formula
|
||||
We want the threshold to scale with volatility, but be capped by the range width (risk).
|
||||
|
||||
* **Base Threshold:** Start with a tight base (e.g., 2% of max delta).
|
||||
* **Volatility Factor:** Scaler based on `volatility_pct`.
|
||||
* Low Vol (<0.05% move/min): Multiplier 1.0x.
|
||||
* High Vol (>0.2% move/min): Multiplier up to 3.0x.
|
||||
|
||||
#### C. Addressing Specific Questions
|
||||
|
||||
1. **Time Frame (ATR):**
|
||||
* **Recommendation:** Use a **short-term volatility window (e.g., 5-15 minutes)**. Weekly/Daily ATR is too slow for a scalping hedger. If the market spikes *now*, we need to adjust *now*, not based on yesterday's range.
|
||||
|
||||
2. **Impact of Range Width (+/- 0.5% vs 3.5%):**
|
||||
* **Tight Range (0.5%):** Gamma is extreme. Delta changes massively with small moves.
|
||||
* *Constraint:* Threshold **CANNOT** be widened significantly, even if vol is high. Risk of exiting range unhedged is too high.
|
||||
* *Adjustment:* Cap the dynamic multiplier. Max Threshold < 10% of Range Width.
|
||||
* **Wide Range (3.5%):** Gamma is lower. Delta changes slowly.
|
||||
* *Adjustment:* We can afford a much wider threshold to save fees.
|
||||
|
||||
3. **Impact of Position Size (1k vs 10k):**
|
||||
* **Small (1k):** Slippage is negligible. Priority = Fee savings.
|
||||
* *Adjustment:* Standard dynamic threshold.
|
||||
* **Large (10k):** Liquidity is a risk.
|
||||
* *Adjustment:* Use the "Edge Proximity" logic (already implemented). For the standard rebalance threshold, we might actually want to keep it **tighter** or standard to avoid needing to dump a huge delta all at once. Large positions prefer "averaging" out the hedge rather than one massive panic trade.
|
||||
|
||||
4. **Other Metrics:**
|
||||
* **Funding Rate:** If funding is paid *to* shorts, we might want to be slightly over-hedged (short). If paid *to* longs, we might minimize short duration. (Advanced optimization).
|
||||
* **Gas/Fee Costs:** If Hyperliquid fees/gas are high (unlikely for HL), widen threshold.
|
||||
|
||||
## 4. Proposed Algorithm
|
||||
|
||||
```python
|
||||
def get_dynamic_threshold(self):
|
||||
# 1. Calculate Volatility (Standard Deviation of last 300 ticks / 5 mins)
|
||||
vol_pct = self.calculate_volatility()
|
||||
|
||||
# 2. Determine Multiplier
|
||||
# Baseline vol might be 0.05% per minute.
|
||||
# If vol is 0.15%, multiplier = 3x.
|
||||
vol_multiplier = max(1.0, min(3.0, vol_pct / 0.0005))
|
||||
|
||||
# 3. Calculate Base Threshold (based on Range Width)
|
||||
# Tighter range = smaller % allowed deviation
|
||||
range_width_pct = (self.strategy.high_range - self.strategy.low_range) / self.strategy.low_range
|
||||
base_threshold_pct = 0.03 # Start with 3% of max delta
|
||||
|
||||
# 4. Apply Multiplier
|
||||
target_threshold_pct = base_threshold_pct * vol_multiplier
|
||||
|
||||
# 5. Apply Safety Cap (Range Dependent)
|
||||
# Never allow threshold to exceed 10% of the total range width in delta terms
|
||||
# (Simplified logic: Threshold shouldn't delay hedging until we are 20% through the range)
|
||||
safety_cap = 0.10
|
||||
final_threshold_pct = min(target_threshold_pct, safety_cap)
|
||||
|
||||
return final_threshold_pct
|
||||
```
|
||||
|
||||
### 3.4 Technical Justification: Standard Deviation vs. ATR
|
||||
I chose **Standard Deviation (Volatility)** over traditional **ATR (Average True Range)** for the implementation for the following reasons:
|
||||
1. **Tick-Level Responsiveness:** Std Dev operates on the rolling price history (last 300 ticks) and reacts immediately to volatility changes. ATR requires completed OHLC candles, which introduces a "closing delay."
|
||||
2. **Architectural Simplicity:** Calculating Std Dev only requires a list of prices (`self.price_history`). ATR would require building a candle-management engine (tracking Highs/Lows/Closes per period), adding unnecessary complexity and potential latency.
|
||||
3. **Statistical Alignment:** Std Dev measures dispersion from the mean. This is ideal for identifying "chop" (where we want to widen thresholds) vs. "trends." ATR measures range width but doesn't differentiate as clearly between choppy noise and directional moves in a tick-by-tick context.
|
||||
4. **Performance:** Rolling Std Dev on a fixed-size buffer is computationally efficient for a high-frequency loop checking every 1s.
|
||||
|
||||
## 5. Conclusion
|
||||
To save costs without compromising safety:
|
||||
1. **Switch from fixed 5% threshold to a dynamic 2-8% range.**
|
||||
2. **Drive the dynamic factor by short-term (5-min) volatility.**
|
||||
3. **Cap the threshold tightly for narrow ranges** (0.5-1.0%) because Gamma risk outweighs fee savings.
|
||||
4. **For large positions**, rely on the existing "Edge Proximity" logic to handle liquidity risk, while using this volatility logic to optimize the "middle of the range" behavior.
|
||||
|
||||
## 6. Implementation Plan
|
||||
- [x] **Step 1:** Implement `calculate_volatility()` in `clp_hedger.py` (using `numpy` or simple math on `price_history`).
|
||||
- [x] **Step 2:** Refactor `rebalance_threshold` logic in the main loop to use the formula above.
|
||||
- [x] **Step 3:** Add logging to track `Vol: X% | Threshold: Y%` to verify behavior.
|
||||
- [x] **Step 4:** Test with `TARGET_INVESTMENT_VALUE_USDC = 2000` configuration to ensure it doesn't over-trade on weekends.
|
||||
|
||||
---
|
||||
|
||||
# Findings & Implementation Update (2025-12-21)
|
||||
|
||||
## 4. Findings (Low Volatility)
|
||||
* **Context:** Market was extremely quiet (Vol ~0.02% - 0.04%).
|
||||
* **Behavior:** The bot correctly stayed IDLE for delta imbalances of ~4%.
|
||||
* **Issue:** The "churn" trades (paying high fees for small rebalances) were still happening occasionally because the base threshold was fixed at 5%. In very low volatility (mean-reverting noise), a 5% imbalance often resolves itself without trading.
|
||||
* **Cost:** Trading to fix a 5% imbalance costs spread + fees, which is often > the risk of that 5% imbalance growing.
|
||||
|
||||
## 5. Implemented Solution
|
||||
We introduced a configurable `BASE_REBALANCE_THRESHOLD_PCT` flag to `clp_hedger.py` and raised the default for low-volatility regimes.
|
||||
|
||||
* **New Flag:** `BASE_REBALANCE_THRESHOLD_PCT`
|
||||
* **New Default:** **0.08 (8%)** (Increased from hardcoded 0.05).
|
||||
* **Logic:**
|
||||
```python
|
||||
# Ensure we satisfy PRICE_BUFFER_PCT (0.15%) minimum
|
||||
base_threshold_pct = max(BASE_REBALANCE_THRESHOLD_PCT, PRICE_BUFFER_PCT / range_width_pct if range_width_pct > 0 else BASE_REBALANCE_THRESHOLD_PCT)
|
||||
```
|
||||
* **Why:** Widening the threshold in low volatility reduces "noise trading" significantly. 8% deviation is safe in a quiet market, whereas in a high vol market the dynamic multiplier (1.0x - 3.0x) would kick in to widen it further if needed, or the user can manually lower the base back to 5% for precision during events.
|
||||
|
||||
## 6. Conclusion
|
||||
The optimization is complete. The bot is now less sensitive to small, non-threatening price drifts in quiet markets, which should directly reduce hedging costs and improve net profitability.
|
||||
70
todo/template._for_analisismd
Normal file
70
todo/template._for_analisismd
Normal file
@ -0,0 +1,70 @@
|
||||
# Analysis Request: [Insert Topic Here]
|
||||
|
||||
**Status:** [Draft / Pending Analysis / Completed]
|
||||
**Date:** [YYYY-MM-DD]
|
||||
**Priority:** [Low / Medium / High]
|
||||
|
||||
---
|
||||
|
||||
## 1. User Description & Query
|
||||
*(User: Fill this section with your design ideas, questions, code snippets, or links to files/web resources. Be as specific as possible about the goal.)*
|
||||
|
||||
### Context
|
||||
* **Goal:**
|
||||
* **Current Behavior:**
|
||||
* **Desired Behavior:**
|
||||
|
||||
### References
|
||||
* **Files:** `[filename.py]`, `[path/to/module]`
|
||||
* **Links:** `[url]`
|
||||
|
||||
### Specific Questions / Hypothesis
|
||||
1.
|
||||
2.
|
||||
|
||||
---
|
||||
|
||||
*(The sections below are to be filled by the AI Agent upon request)*
|
||||
|
||||
## 2. Agent Summary
|
||||
*(AI: Summarize the user's request to ensure alignment on the objective.)*
|
||||
|
||||
* **Objective:**
|
||||
* **Key Constraints:**
|
||||
* **Scope:**
|
||||
|
||||
## 3. Main Analysis
|
||||
*(AI: Perform the deep dive here. Use codebase knowledge, logic, and simulations.)*
|
||||
|
||||
### 3.1 Codebase Investigation
|
||||
* **Affected Components:**
|
||||
* **Data Flow Analysis:**
|
||||
* **Current Limitation/Bug:**
|
||||
|
||||
### 3.2 Technical Options / Trade-offs
|
||||
| Option | Pros | Cons | Complexity |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **A. [Strategy A]** | ... | ... | ... |
|
||||
| **B. [Strategy B]** | ... | ... | ... |
|
||||
|
||||
### 3.3 Proposed Solution Design
|
||||
* **Architecture:**
|
||||
* **Logic Changes:**
|
||||
* **Edge Cases Considered:**
|
||||
|
||||
## 4. Risk Assessment
|
||||
*(AI: What could go wrong? Performance, Security, or Stability impacts.)*
|
||||
|
||||
* **Risk 1:** [Description] -> *Mitigation:* [Strategy]
|
||||
|
||||
## 5. Conclusion
|
||||
*(AI: Final verdict and recommendation.)*
|
||||
|
||||
* **Recommendation:**
|
||||
|
||||
## 6. Implementation Plan
|
||||
*(AI: Step-by-step checklist to execute the recommendation.)*
|
||||
|
||||
- [ ] Step 1:
|
||||
- [ ] Step 2:
|
||||
- [ ] Step 3:
|
||||
18
tools/.env.example
Normal file
18
tools/.env.example
Normal file
@ -0,0 +1,18 @@
|
||||
# Base Chain Configuration
|
||||
BASE_RPC_URL=https://mainnet.base.org # or your preferred Base RPC provider
|
||||
|
||||
# Wallet Configuration
|
||||
MAIN_WALLET_PRIVATE_KEY=your_private_key_here
|
||||
# or
|
||||
PRIVATE_KEY=your_private_key_here
|
||||
|
||||
# Telegram Notifications (Optional)
|
||||
TELEGRAM_MONITOR_ENABLED=False
|
||||
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
|
||||
TELEGRAM_CHAT_ID=your_telegram_chat_id
|
||||
TELEGRAM_CHECK_INTERVAL_SECONDS=60
|
||||
TELEGRAM_TIMEOUT_SECONDS=10
|
||||
TELEGRAM_STATE_FILE=telegram_monitor_state.json
|
||||
|
||||
# Optional: Custom hedge status file path
|
||||
HEDGE_STATUS_FILE=hedge_status.json
|
||||
194
tools/README_TELEGRAM.md
Normal file
194
tools/README_TELEGRAM.md
Normal file
@ -0,0 +1,194 @@
|
||||
# CLP Telegram Notification Monitor
|
||||
|
||||
## Overview
|
||||
|
||||
Standalone Python script that monitors your CLP (Concentrated Liquidity Pool) positions and sends Telegram notifications when new positions are opened.
|
||||
|
||||
## Features
|
||||
|
||||
- 🔍 **File-based Monitoring**: Watches `hedge_status.json` every 60 seconds
|
||||
- 📊 **Rich Notifications**: Shows last closed position vs currently opened position
|
||||
- 🔧 **Zero Integration**: No modifications to existing trading logic required
|
||||
- 🛡️ **Safe Operation**: Graceful error handling, won't affect trading system
|
||||
- 📈 **Performance Tracking**: Compares entry prices, values, and hedge PnL
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install requests python-dotenv
|
||||
```
|
||||
|
||||
### 2. Create Telegram Bot
|
||||
|
||||
1. Open Telegram and search for **@BotFather**
|
||||
2. Send: `/newbot`
|
||||
3. Choose a name for your bot (e.g., "CLP Position Monitor")
|
||||
4. Choose a username (must end with 'bot', e.g., "clp_monitor_bot")
|
||||
5. Copy the **HTTP API token** from BotFather
|
||||
|
||||
### 3. Get Your Chat ID
|
||||
|
||||
1. Send any message to your new bot first
|
||||
2. Run the setup script: `python tools/telegram_setup.py`
|
||||
3. Choose option "2. Get Chat ID" and enter your bot token
|
||||
4. Copy your Chat ID
|
||||
|
||||
### 4. Configure Environment
|
||||
|
||||
Add to your `.env` file:
|
||||
|
||||
```bash
|
||||
# Enable Telegram notifications
|
||||
TELEGRAM_MONITOR_ENABLED=True
|
||||
|
||||
# Your bot credentials
|
||||
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
|
||||
TELEGRAM_CHAT_ID=123456789
|
||||
|
||||
# Optional settings
|
||||
TELEGRAM_CHECK_INTERVAL_SECONDS=60
|
||||
TELEGRAM_TIMEOUT_SECONDS=10
|
||||
TELEGRAM_STATE_FILE=telegram_monitor_state.json
|
||||
HEDGE_STATUS_FILE=hedge_status.json
|
||||
```
|
||||
|
||||
### 5. Run the Monitor
|
||||
|
||||
```bash
|
||||
python tools/telegram_monitor.py
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Running as Background Process
|
||||
|
||||
**Option 1: Simple Background**
|
||||
```bash
|
||||
nohup python tools/telegram_monitor.py > logs/telegram_monitor.log 2>&1 &
|
||||
```
|
||||
|
||||
**Option 2: Screen Session**
|
||||
```bash
|
||||
screen -S telegram_monitor
|
||||
python tools/telegram_monitor.py
|
||||
# Press Ctrl+A, D to detach
|
||||
```
|
||||
|
||||
**Option 3: Systemd Service** (Linux)
|
||||
Create `/etc/systemd/system/clp-telegram-monitor.service`:
|
||||
```ini
|
||||
[Unit]
|
||||
Description=CLP Telegram Monitor
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=your_username
|
||||
WorkingDirectory=/path/to/uniswap_auto_clp
|
||||
ExecStart=/usr/bin/python3 tools/telegram_monitor.py
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Enable and start:
|
||||
```bash
|
||||
sudo systemctl enable clp-telegram-monitor
|
||||
sudo systemctl start clp-telegram-monitor
|
||||
```
|
||||
|
||||
## Message Format Example
|
||||
|
||||
When a new position opens, you'll receive:
|
||||
|
||||
```
|
||||
🚀 NEW CLP POSITION DETECTED
|
||||
|
||||
📊 LAST CLOSED POSITION:
|
||||
• Token ID: 5171687
|
||||
• Entry: $3043.86
|
||||
• Target Value: $1985.02
|
||||
• Duration: 2h 14m
|
||||
• Hedge PnL: -$20.99
|
||||
• Hedge Fees: $2.30
|
||||
|
||||
🔥 CURRENTLY OPENED:
|
||||
• Token ID: 5173016
|
||||
• Entry: $2993.72
|
||||
• Target Value: $1935.04
|
||||
• Range: $2849.80 - $3140.07
|
||||
• Initial: 0.3181 ETH + 982.71 USDC
|
||||
• Time: 2h 15m ago
|
||||
|
||||
📈 PERFORMANCE COMPARISON:
|
||||
• Entry Change: -$50.14 (-1.65%)
|
||||
• Value Change: -$49.98 (-2.52%)
|
||||
• Hedge PnL Trend: -$20.99 → $2.75 (+$23.74)
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|----------|-------------|
|
||||
| `TELEGRAM_MONITOR_ENABLED` | `False` | Enable/disable notifications |
|
||||
| `TELEGRAM_BOT_TOKEN` | Required | Your bot's HTTP API token |
|
||||
| `TELEGRAM_CHAT_ID` | Required | Your personal chat ID |
|
||||
| `TELEGRAM_CHECK_INTERVAL_SECONDS` | `60` | How often to check for changes |
|
||||
| `TELEGRAM_TIMEOUT_SECONDS` | `10` | Telegram API timeout |
|
||||
| `TELEGRAM_STATE_FILE` | `telegram_monitor_state.json` | Internal state tracking |
|
||||
| `HEDGE_STATUS_FILE` | `hedge_status.json` | File to monitor |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Bot Not Receiving Messages
|
||||
- Ensure you've sent a message to your bot first
|
||||
- Check that `TELEGRAM_CHAT_ID` is correct (no quotes)
|
||||
- Verify bot token has no extra spaces
|
||||
|
||||
### No Notifications When Position Opens
|
||||
- Check `TELEGRAM_MONITOR_ENABLED=True`
|
||||
- Verify the script is running: `ps aux | grep telegram_monitor`
|
||||
- Check logs: `tail -f logs/telegram_monitor.log`
|
||||
- Ensure `hedge_status.json` path is correct
|
||||
|
||||
### Telegram API Errors
|
||||
- Verify bot token format (should have colon: `123:ABC`)
|
||||
- Check internet connectivity
|
||||
- Try running setup script to test connection
|
||||
|
||||
### File Not Found Errors
|
||||
- Make sure you're running from project root directory
|
||||
- Verify `hedge_status.json` exists and has correct permissions
|
||||
- Check that your trading system is generating the status file
|
||||
|
||||
## Security Notes
|
||||
|
||||
- **Never share your bot token** - it's like a password
|
||||
- **Store credentials in `.env`** - never commit to git
|
||||
- **Limit bot permissions** - only send messages, no admin rights
|
||||
- **Monitor logs regularly** for any unusual activity
|
||||
|
||||
## Files Created
|
||||
|
||||
- `tools/telegram_monitor.py` - Main monitoring script
|
||||
- `tools/telegram_setup.py` - Setup helper script
|
||||
- `tools/.env.example` - Environment template
|
||||
- `telegram_monitor_state.json` - Internal state (auto-created)
|
||||
- `logs/telegram_monitor.log` - Monitor logs (auto-created)
|
||||
|
||||
## Integration Notes
|
||||
|
||||
This monitor is **completely standalone** and:
|
||||
|
||||
- ✅ Does not modify your trading logic
|
||||
- ✅ Does not interfere with uniswap_manager.py
|
||||
- ✅ Does not affect clp_hedger.py
|
||||
- ✅ Safe to run alongside existing processes
|
||||
- ✅ Can be stopped/started independently
|
||||
- ✅ Uses minimal system resources
|
||||
|
||||
The monitor simply reads the status file that your existing trading system already generates, making it safe and non-intrusive.
|
||||
107
tools/analyze_pool_data.py
Normal file
107
tools/analyze_pool_data.py
Normal 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()
|
||||
171
tools/calculate_market_data.py
Normal file
171
tools/calculate_market_data.py
Normal 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()
|
||||
@ -277,12 +277,7 @@ class GitAgent:
|
||||
|
||||
# Stage and commit changes
|
||||
change_count = len(self.git.get_changed_files())
|
||||
commit_message = f"{branch_name}: Automated backup - {change_count} files changed
|
||||
|
||||
📋 Files modified: {change_count}
|
||||
⏰ Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')} UTC
|
||||
🔒 Security: PASSED (no secrets detected)
|
||||
💾 Automated by Git Agent"
|
||||
commit_message = f"{branch_name}: Automated backup - {change_count} files changed\n\n📋 Files modified: {change_count}\n⏰ Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')} UTC\n🔒 Security: PASSED (no secrets detected)\n💾 Automated by Git Agent"
|
||||
|
||||
if not self.git.commit_changes(commit_message):
|
||||
self.logger.error("❌ Failed to commit changes")
|
||||
|
||||
159
tools/git_opencode.py
Normal file
159
tools/git_opencode.py
Normal file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
OpenCode Git Agent - Direct Integration
|
||||
Simple direct commands for Git Agent operations
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def run_git_backup():
|
||||
"""Create automated backup"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
agent_path = os.path.join(project_root, "tools", "git_agent.py")
|
||||
|
||||
result = subprocess.run(
|
||||
["python", agent_path, "--backup"],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("SUCCESS: Backup completed successfully!")
|
||||
print("Automated backup created and pushed to remote repository.")
|
||||
else:
|
||||
error_msg = result.stderr or result.stdout or "Unknown error"
|
||||
print(f"ERROR: Backup failed!")
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during backup: {str(e)}")
|
||||
|
||||
def run_git_status():
|
||||
"""Show git status"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
agent_path = os.path.join(project_root, "tools", "git_agent.py")
|
||||
|
||||
result = subprocess.run(
|
||||
["python", agent_path, "--status"],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("SUCCESS: Git Agent Status")
|
||||
print(result.stdout)
|
||||
else:
|
||||
print(f"ERROR: Status check failed!")
|
||||
error_msg = result.stderr or result.stdout or "Unknown error"
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during status check: {str(e)}")
|
||||
|
||||
def run_git_cleanup():
|
||||
"""Clean up old backups"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
agent_path = os.path.join(project_root, "tools", "git_agent.py")
|
||||
|
||||
result = subprocess.run(
|
||||
["python", agent_path, "--cleanup"],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("SUCCESS: Cleanup completed!")
|
||||
print("Old backup branches have been removed according to retention policy.")
|
||||
else:
|
||||
print(f"ERROR: Cleanup failed!")
|
||||
error_msg = result.stderr or result.stdout or "Unknown error"
|
||||
print(f"Error: {error_msg}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during cleanup: {str(e)}")
|
||||
|
||||
def run_git_restore(time_input=None):
|
||||
"""Restore from backup"""
|
||||
try:
|
||||
project_root = "K:\\Projects\\uniswap_auto_clp"
|
||||
|
||||
if time_input:
|
||||
# Use git directly for restore
|
||||
branch_name = f"backup-{time_input}"
|
||||
|
||||
result = subprocess.run(
|
||||
["git", "checkout", branch_name],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
env=dict(os.environ, PYTHONIOENCODING='utf-8')
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print(f"SUCCESS: Restored to backup!")
|
||||
print(f"Branch: {branch_name}")
|
||||
print("Note: You are now on a backup branch.")
|
||||
print("Use 'git checkout main' to return to main branch when done.")
|
||||
else:
|
||||
print(f"ERROR: Restore failed!")
|
||||
print(f"Error: {result.stderr}")
|
||||
else:
|
||||
print("ERROR: Please specify backup timestamp")
|
||||
print("Usage: restore <timestamp>")
|
||||
print("Example: restore 2025-12-19-14")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Exception during restore: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == "backup":
|
||||
run_git_backup()
|
||||
elif command == "status":
|
||||
run_git_status()
|
||||
elif command == "cleanup":
|
||||
run_git_cleanup()
|
||||
elif command == "restore":
|
||||
timestamp = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
run_git_restore(timestamp)
|
||||
else:
|
||||
print("Git Agent - OpenCode Integration")
|
||||
print("Usage: python git_opencode.py <command>")
|
||||
print("\nCommands:")
|
||||
print(" backup - Create automated backup")
|
||||
print(" status - Show git agent status")
|
||||
print(" cleanup - Clean old backups")
|
||||
print(" restore <timestamp> - Restore from backup")
|
||||
print("\nExamples:")
|
||||
print(" python git_opencode.py backup")
|
||||
print(" python git_opencode.py status")
|
||||
print(" python git_opencode.py restore 2025-12-19-14")
|
||||
else:
|
||||
print("Git Agent - OpenCode Integration")
|
||||
print("Usage: python git_opencode.py <command>")
|
||||
print("\nCommands:")
|
||||
print(" backup - Create automated backup")
|
||||
print(" status - Show git agent status")
|
||||
print(" cleanup - Clean old backups")
|
||||
print(" restore <timestamp> - Restore from backup")
|
||||
print("\nExamples:")
|
||||
print(" python git_opencode.py backup")
|
||||
print(" python git_opencode.py status")
|
||||
print(" python git_opencode.py restore 2025-12-19-14")
|
||||
134
tools/kpi_tracker.py
Normal file
134
tools/kpi_tracker.py
Normal file
@ -0,0 +1,134 @@
|
||||
import os
|
||||
import csv
|
||||
import time
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from typing import Dict, Optional
|
||||
|
||||
# Setup Logger
|
||||
logger = logging.getLogger("KPI_TRACKER")
|
||||
logger.setLevel(logging.INFO)
|
||||
# Basic handler if not already handled by parent
|
||||
if not logger.handlers:
|
||||
ch = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - KPI - %(message)s')
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
KPI_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs', 'kpi_history.csv')
|
||||
|
||||
def initialize_kpi_csv():
|
||||
"""Creates the CSV with headers if it doesn't exist."""
|
||||
if not os.path.exists(os.path.dirname(KPI_FILE)):
|
||||
os.makedirs(os.path.dirname(KPI_FILE))
|
||||
|
||||
if not os.path.exists(KPI_FILE):
|
||||
with open(KPI_FILE, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"Timestamp",
|
||||
"Date",
|
||||
"NAV_Total_USD",
|
||||
"Benchmark_HODL_USD",
|
||||
"Alpha_USD",
|
||||
"Uniswap_Val_USD",
|
||||
"Uniswap_Fees_Claimed_USD",
|
||||
"Uniswap_Fees_Unclaimed_USD",
|
||||
"Hedge_Equity_USD",
|
||||
"Hedge_PnL_Realized_USD",
|
||||
"Hedge_Fees_Paid_USD",
|
||||
"ETH_Price",
|
||||
"Fee_Coverage_Ratio"
|
||||
])
|
||||
|
||||
def calculate_hodl_benchmark(initial_eth: Decimal, initial_usdc: Decimal, initial_hedge_usdc: Decimal, current_eth_price: Decimal) -> Decimal:
|
||||
"""Calculates value if assets were just held (Wallet Assets + Hedge Account Cash)."""
|
||||
return (initial_eth * current_eth_price) + initial_usdc + initial_hedge_usdc
|
||||
|
||||
def log_kpi_snapshot(
|
||||
snapshot_data: Dict[str, float]
|
||||
):
|
||||
"""
|
||||
Logs a KPI snapshot to CSV.
|
||||
Expected keys in snapshot_data:
|
||||
- initial_eth, initial_usdc, initial_hedge_usdc
|
||||
- current_eth_price
|
||||
- uniswap_pos_value_usd
|
||||
- uniswap_fees_claimed_usd
|
||||
- uniswap_fees_unclaimed_usd
|
||||
- hedge_equity_usd
|
||||
- hedge_pnl_realized_usd
|
||||
- hedge_fees_paid_usd
|
||||
- wallet_eth_bal, wallet_usdc_bal (Optional, for full NAV)
|
||||
"""
|
||||
try:
|
||||
initialize_kpi_csv()
|
||||
|
||||
# Convert all inputs to Decimal for precision
|
||||
price = Decimal(str(snapshot_data.get('current_eth_price', 0)))
|
||||
|
||||
# 1. Benchmark (HODL)
|
||||
init_eth = Decimal(str(snapshot_data.get('initial_eth', 0)))
|
||||
init_usdc = Decimal(str(snapshot_data.get('initial_usdc', 0)))
|
||||
init_hedge = Decimal(str(snapshot_data.get('initial_hedge_usdc', 0)))
|
||||
benchmark_val = calculate_hodl_benchmark(init_eth, init_usdc, init_hedge, price)
|
||||
|
||||
# 2. Strategy NAV (Net Asset Value)
|
||||
# NAV = Uni Pos + Uni Fees (Claimed+Unclaimed) + Hedge Equity + (Wallet Surplus - Initial Wallet Surplus?)
|
||||
# For simplicity, we focus on the Strategy PnL components:
|
||||
# Strategy Val = (Current Uni Pos) + (Claimed Fees) + (Unclaimed Fees) + (Hedge PnL Realized) + (Hedge Unrealized?)
|
||||
# Note: Hedge Equity usually includes margin. We strictly want "Value Generated".
|
||||
|
||||
uni_val = Decimal(str(snapshot_data.get('uniswap_pos_value_usd', 0)))
|
||||
uni_fees_claimed = Decimal(str(snapshot_data.get('uniswap_fees_claimed_usd', 0)))
|
||||
uni_fees_unclaimed = Decimal(str(snapshot_data.get('uniswap_fees_unclaimed_usd', 0)))
|
||||
|
||||
# Hedge PnL (Realized + Unrealized) is better than Equity for PnL tracking,
|
||||
# but Equity represents actual redeemable cash. Let's use Equity if provided, or PnL components.
|
||||
hedge_equity = Decimal(str(snapshot_data.get('hedge_equity_usd', 0)))
|
||||
hedge_fees = Decimal(str(snapshot_data.get('hedge_fees_paid_usd', 0)))
|
||||
|
||||
# Simplified NAV for Strategy Comparison:
|
||||
# We assume 'hedge_equity' is the Liquidation Value of the hedge account.
|
||||
# But if we want strictly "Strategy Performance", we usually do:
|
||||
# Current Value = Uni_Val + Unclaimed + Hedge_Equity
|
||||
# (Assuming Hedge_Equity started at 0 or we track delta? No, usually Hedge Account has deposit).
|
||||
|
||||
# Let's define NAV as Total Current Liquidation Value of Strategy Components
|
||||
current_nav = uni_val + uni_fees_unclaimed + uni_fees_claimed + hedge_equity
|
||||
|
||||
# Alpha
|
||||
alpha = current_nav - benchmark_val
|
||||
|
||||
# Coverage Ratio
|
||||
total_hedge_cost = abs(hedge_fees) # + funding if available
|
||||
total_uni_earnings = uni_fees_claimed + uni_fees_unclaimed
|
||||
|
||||
if total_hedge_cost > 0:
|
||||
coverage_ratio = total_uni_earnings / total_hedge_cost
|
||||
else:
|
||||
coverage_ratio = Decimal("999.0") # Infinite/Good
|
||||
|
||||
# Write
|
||||
with open(KPI_FILE, 'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
int(time.time()),
|
||||
time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
f"{current_nav:.2f}",
|
||||
f"{benchmark_val:.2f}",
|
||||
f"{alpha:.2f}",
|
||||
f"{uni_val:.2f}",
|
||||
f"{uni_fees_claimed:.2f}",
|
||||
f"{uni_fees_unclaimed:.2f}",
|
||||
f"{hedge_equity:.2f}",
|
||||
f"{snapshot_data.get('hedge_pnl_realized_usd', 0):.2f}",
|
||||
f"{hedge_fees:.2f}",
|
||||
f"{price:.2f}",
|
||||
f"{coverage_ratio:.2f}"
|
||||
])
|
||||
|
||||
logger.info(f"📊 KPI Logged | NAV: ${current_nav:.2f} | Benchmark: ${benchmark_val:.2f} | Alpha: ${alpha:.2f}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to log KPI: {e}")
|
||||
192
tools/pool_scanner.py
Normal file
192
tools/pool_scanner.py
Normal 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
|
||||
146
tools/record_pool_depth.py
Normal file
146
tools/record_pool_depth.py
Normal file
@ -0,0 +1,146 @@
|
||||
import os
|
||||
import time
|
||||
import csv
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from decimal import Decimal, getcontext
|
||||
from web3 import Web3
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
POOL_ADDRESS = '0xC6962004f452bE9203591991D15f6b388e09E8D0' # ETH/USDC 0.05% Arbitrum
|
||||
INTERVAL_SECONDS = 15 * 60 # 15 minutes
|
||||
RANGE_PCT = 10.0 # Total scan range +/- 10%
|
||||
STEP_PCT = 0.1 # Resolution step 0.1%
|
||||
TVL_USD_BASELINE = Decimal('74530000') # Baseline TVL for concentration calculation
|
||||
|
||||
# Token Details
|
||||
D0 = 18 # WETH
|
||||
D1 = 6 # USDC
|
||||
|
||||
getcontext().prec = 60
|
||||
load_dotenv()
|
||||
|
||||
# Setup Logging
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger("DEPTH_MONITOR")
|
||||
|
||||
# Ensure logs directory exists
|
||||
os.makedirs('logs', exist_ok=True)
|
||||
CSV_FILE = 'logs/pool_liquidity_depth.csv'
|
||||
|
||||
# ABI for Uniswap V3 Pool
|
||||
POOL_ABI = [
|
||||
{'inputs': [], 'name': 'liquidity', 'outputs': [{'internalType': 'uint128', 'name': '', 'type': 'uint128'}], 'stateMutability': 'view', 'type': 'function'},
|
||||
{'inputs': [], 'name': 'slot0', 'outputs': [{'internalType': 'uint160', 'name': 'sqrtPriceX96', 'type': 'uint160'}, {'internalType': 'int24', 'name': 'tick', 'type': 'int24'}], 'stateMutability': 'view', 'type': 'function'},
|
||||
{'inputs': [{'internalType': 'int24', 'name': 'tick', 'type': 'int24'}], 'name': 'ticks', 'outputs': [{'internalType': 'uint128', 'name': 'liquidityGross', 'type': 'uint128'}, {'internalType': 'int128', 'name': 'liquidityNet', 'type': 'int128'}], 'stateMutability': 'view', 'type': 'function'}
|
||||
]
|
||||
|
||||
def get_price_from_tick(tick):
|
||||
return (Decimal('1.0001') ** Decimal(str(tick))) * (Decimal('10') ** Decimal(str(D0 - D1)))
|
||||
|
||||
def get_liquidity_at_offsets(pool_contract, current_tick, current_liquidity):
|
||||
"""
|
||||
Samples liquidity at various price offsets.
|
||||
Note: This samples initialized ticks to calculate L at specific price points.
|
||||
"""
|
||||
results = []
|
||||
|
||||
# Tick spacing for 0.05% pools is 10.
|
||||
# 0.1% price move is approx 10 ticks.
|
||||
ticks_per_step = 10
|
||||
total_steps = int(RANGE_PCT / STEP_PCT)
|
||||
|
||||
# --- SCAN DOWN ---
|
||||
l_running = Decimal(current_liquidity)
|
||||
for i in range(1, total_steps + 1):
|
||||
target_tick = current_tick - (i * ticks_per_step)
|
||||
# Traverse ticks between previous and current target to update liquidity
|
||||
for t in range(current_tick - (i-1)*ticks_per_step, target_tick - 1, -1):
|
||||
data = pool_contract.functions.ticks(t).call()
|
||||
if data[0] > 0: # initialized
|
||||
l_running -= Decimal(data[1])
|
||||
|
||||
offset_pct = -round(i * STEP_PCT, 2)
|
||||
px = get_price_from_tick(target_tick)
|
||||
results.append({'offset': offset_pct, 'price': px, 'liquidity': l_running})
|
||||
|
||||
# --- SCAN UP ---
|
||||
l_running = Decimal(current_liquidity)
|
||||
for i in range(1, total_steps + 1):
|
||||
target_tick = current_tick + (i * ticks_per_step)
|
||||
for t in range(current_tick + (i-1)*ticks_per_step + 1, target_tick + 1):
|
||||
data = pool_contract.functions.ticks(t).call()
|
||||
if data[0] > 0:
|
||||
l_running += Decimal(data[1])
|
||||
|
||||
offset_pct = round(i * STEP_PCT, 2)
|
||||
px = get_price_from_tick(target_tick)
|
||||
results.append({'offset': offset_pct, 'price': px, 'liquidity': l_running})
|
||||
|
||||
# Add center point
|
||||
results.append({'offset': 0.0, 'price': get_price_from_tick(current_tick), 'liquidity': Decimal(current_liquidity)})
|
||||
|
||||
return sorted(results, key=lambda x: x['offset'])
|
||||
|
||||
def main():
|
||||
rpc_url = os.environ.get('MAINNET_RPC_URL')
|
||||
if not rpc_url:
|
||||
logger.error("MAINNET_RPC_URL not found in .env")
|
||||
return
|
||||
|
||||
w3 = Web3(Web3.HTTPProvider(rpc_url))
|
||||
pool = w3.eth.contract(address=Web3.to_checksum_address(POOL_ADDRESS), abi=POOL_ABI)
|
||||
|
||||
# Initialize CSV if it doesn't exist
|
||||
file_exists = os.path.isfile(CSV_FILE)
|
||||
|
||||
logger.info(f"Starting Depth Monitor for {POOL_ADDRESS}")
|
||||
logger.info(f"Scan Range: +/-{RANGE_PCT}% | Resolution: {STEP_PCT}% | Interval: {INTERVAL_SECONDS/60}m")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 1. Fetch State
|
||||
l_active = pool.functions.liquidity().call()
|
||||
s0 = pool.functions.slot0().call()
|
||||
curr_tick = s0[1]
|
||||
curr_price = get_price_from_tick(curr_tick)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# 2. Map Depth
|
||||
depth_data = get_liquidity_at_offsets(pool, curr_tick, l_active)
|
||||
|
||||
# 3. Calculate Concentration & Save
|
||||
with open(CSV_FILE, 'a', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
if not file_exists:
|
||||
writer.writerow(['timestamp', 'ref_price', 'offset_pct', 'target_price', 'liquidity', 'concentration'])
|
||||
file_exists = True
|
||||
|
||||
for row in depth_data:
|
||||
# L_full baseline for THIS specific price point
|
||||
# Corrected L_full: (TVL * 10^6) / (2 * sqrtP)
|
||||
# sqrtP_norm = sqrt(Price) / 10^((D0-D1)/2)
|
||||
sqrtP_norm = row['price'].sqrt() / (Decimal('10') ** (Decimal(str(D0 - D1)) / Decimal('2')))
|
||||
l_full = (TVL_USD_BASELINE * (Decimal('10')**Decimal('6'))) / (Decimal('2') * sqrtP_norm)
|
||||
conc = row['liquidity'] / l_full
|
||||
|
||||
writer.writerow([
|
||||
timestamp,
|
||||
f"{curr_price:.4f}",
|
||||
row['offset'],
|
||||
f"{row['price']:.4f}",
|
||||
f"{row['liquidity']:.0f}",
|
||||
f"{conc:.2f}"
|
||||
])
|
||||
|
||||
logger.info(f"Recorded depth snapshot at {curr_price:.2f}. Next in {INTERVAL_SECONDS/60}m.")
|
||||
time.sleep(INTERVAL_SECONDS)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in monitor loop: {e}")
|
||||
time.sleep(60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
394
tools/telegram_monitor.py
Normal file
394
tools/telegram_monitor.py
Normal file
@ -0,0 +1,394 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Telegram Monitor for CLP Position Notifications
|
||||
Monitors hedge_status.json file for new position openings and sends Telegram notifications
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import hashlib
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Dict, List, Tuple, Any
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# --- SETUP PROJECT PATH ---
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.dirname(current_dir)
|
||||
sys.path.append(current_dir)
|
||||
|
||||
# --- LOGGING SETUP ---
|
||||
os.makedirs(os.path.join(current_dir, 'logs'), exist_ok=True)
|
||||
|
||||
class UnixMsLogFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
record.unix_ms = int(record.created * 1000)
|
||||
return True
|
||||
|
||||
logger = logging.getLogger("TELEGRAM_MONITOR")
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.propagate = False
|
||||
logger.handlers.clear()
|
||||
|
||||
# Console handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.INFO)
|
||||
console_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
console_handler.setFormatter(console_formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
# File handler
|
||||
file_handler = logging.FileHandler(os.path.join(current_dir, 'logs', 'telegram_monitor.log'), encoding='utf-8')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.addFilter(UnixMsLogFilter())
|
||||
file_formatter = logging.Formatter('%(unix_ms)d, %(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
load_dotenv(os.path.join(current_dir, '.env'))
|
||||
|
||||
TELEGRAM_ENABLED = os.getenv('TELEGRAM_MONITOR_ENABLED', 'False').lower() == 'true'
|
||||
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN', '')
|
||||
TELEGRAM_CHAT_ID = os.getenv('TELEGRAM_CHAT_ID', '')
|
||||
TELEGRAM_CHECK_INTERVAL = int(os.getenv('TELEGRAM_CHECK_INTERVAL_SECONDS', '60'))
|
||||
TELEGRAM_STATE_FILE = os.getenv('TELEGRAM_STATE_FILE', 'telegram_monitor_state.json')
|
||||
TELEGRAM_TIMEOUT = int(os.getenv('TELEGRAM_TIMEOUT_SECONDS', '10'))
|
||||
HEDGE_STATUS_FILE = os.getenv('HEDGE_STATUS_FILE', 'hedge_status.json')
|
||||
|
||||
class TelegramNotifier:
|
||||
"""Handles Telegram API communication"""
|
||||
|
||||
def __init__(self, bot_token: str, chat_id: str):
|
||||
self.bot_token = bot_token
|
||||
self.chat_id = chat_id
|
||||
self.base_url = f"https://api.telegram.org/bot{bot_token}"
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""Test Telegram bot connection"""
|
||||
try:
|
||||
url = f"{self.base_url}/getMe"
|
||||
response = requests.get(url, timeout=TELEGRAM_TIMEOUT)
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
logger.error(f"Telegram connection test failed: {e}")
|
||||
return False
|
||||
|
||||
def send_message(self, text: str) -> bool:
|
||||
"""Send message to Telegram chat"""
|
||||
if not TELEGRAM_ENABLED or not self.bot_token or not self.chat_id:
|
||||
logger.debug("Telegram notifications disabled or missing credentials")
|
||||
return False
|
||||
|
||||
try:
|
||||
url = f"{self.base_url}/sendMessage"
|
||||
payload = {
|
||||
'chat_id': self.chat_id,
|
||||
'text': text,
|
||||
'parse_mode': 'Markdown'
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, timeout=TELEGRAM_TIMEOUT)
|
||||
result = response.json()
|
||||
|
||||
if result.get('ok'):
|
||||
logger.info("Telegram notification sent successfully")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Telegram API error: {result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send Telegram message: {e}")
|
||||
return False
|
||||
|
||||
def format_position_message(self, last_closed: Optional[Dict], current_open: Dict) -> str:
|
||||
"""Format position data into readable message"""
|
||||
lines = ["NEW CLP POSITION DETECTED\n"]
|
||||
|
||||
# Previous closed position section
|
||||
if last_closed:
|
||||
lines.append("LAST CLOSED POSITION:")
|
||||
lines.append(f"• Token ID: {last_closed.get('token_id', 'N/A')}")
|
||||
lines.append(f"• Entry: ${last_closed.get('entry_price', 0):.2f}")
|
||||
lines.append(f"• Target Value: ${last_closed.get('target_value', 0):.2f}")
|
||||
|
||||
# Add duration if timestamps available
|
||||
if last_closed.get('timestamp_open') and last_closed.get('timestamp_close'):
|
||||
duration = last_closed['timestamp_close'] - last_closed['timestamp_open']
|
||||
hours = duration // 3600
|
||||
minutes = (duration % 3600) // 60
|
||||
lines.append(f"• Duration: {hours}h {minutes}m")
|
||||
|
||||
# Add hedge performance if available
|
||||
hedge_pnl = last_closed.get('hedge_pnl_realized', 0)
|
||||
if hedge_pnl != 0:
|
||||
lines.append(f"• Hedge PnL: ${hedge_pnl:.2f}")
|
||||
|
||||
hedge_fees = last_closed.get('hedge_fees_paid', 0)
|
||||
if hedge_fees != 0:
|
||||
lines.append(f"• Hedge Fees: ${hedge_fees:.2f}")
|
||||
else:
|
||||
lines.append("LAST CLOSED POSITION: None")
|
||||
|
||||
lines.append("") # Empty line
|
||||
|
||||
# Current opened position section
|
||||
lines.append("CURRENTLY OPENED:")
|
||||
lines.append(f"• Token ID: {current_open.get('token_id', 'N/A')}")
|
||||
lines.append(f"• Entry: ${current_open.get('entry_price', 0):.2f}")
|
||||
lines.append(f"• Target Value: ${current_open.get('target_value', 0):.2f}")
|
||||
|
||||
# Range information
|
||||
range_lower = current_open.get('range_lower', 0)
|
||||
range_upper = current_open.get('range_upper', 0)
|
||||
if range_lower and range_upper:
|
||||
lines.append(f"• Range: ${range_lower:.2f} - ${range_upper:.2f}")
|
||||
|
||||
# Initial amounts
|
||||
amount0 = current_open.get('amount0_initial', 0)
|
||||
amount1 = current_open.get('amount1_initial', 0)
|
||||
if amount0 and amount1:
|
||||
lines.append(f"• Initial: {amount0:.4f} ETH + {amount1:.2f} USDC")
|
||||
|
||||
# Time since opening
|
||||
if current_open.get('timestamp_open'):
|
||||
age = int(time.time()) - current_open['timestamp_open']
|
||||
hours = age // 3600
|
||||
minutes = (age % 3600) // 60
|
||||
lines.append(f"• Time: {hours}h {minutes}m ago")
|
||||
|
||||
# Performance comparison if we have both positions
|
||||
if last_closed and current_open:
|
||||
lines.append("") # Empty line
|
||||
lines.append("PERFORMANCE COMPARISON:")
|
||||
|
||||
# Entry price change
|
||||
last_entry = Decimal(str(last_closed.get('entry_price', 0)))
|
||||
curr_entry = Decimal(str(current_open.get('entry_price', 0)))
|
||||
if last_entry > 0:
|
||||
entry_change = curr_entry - last_entry
|
||||
entry_change_pct = (entry_change / last_entry) * 100
|
||||
sign = "+" if entry_change >= 0 else ""
|
||||
lines.append(f"• Entry Change: {sign}${entry_change:.2f} ({sign}{entry_change_pct:.2f}%)")
|
||||
|
||||
# Value change
|
||||
last_value = Decimal(str(last_closed.get('target_value', 0)))
|
||||
curr_value = Decimal(str(current_open.get('target_value', 0)))
|
||||
if last_value > 0:
|
||||
value_change = curr_value - last_value
|
||||
value_change_pct = (value_change / last_value) * 100
|
||||
sign = "+" if value_change >= 0 else ""
|
||||
lines.append(f"• Value Change: {sign}${value_change:.2f} ({sign}{value_change_pct:.2f}%)")
|
||||
|
||||
# Hedge PnL trend
|
||||
last_hedge_pnl = last_closed.get('hedge_pnl_realized', 0)
|
||||
curr_hedge_equity = current_open.get('hedge_equity_usd', 0)
|
||||
if last_hedge_pnl != 0 and curr_hedge_equity != 0:
|
||||
hedge_trend = curr_hedge_equity - last_hedge_pnl
|
||||
sign = "+" if hedge_trend >= 0 else ""
|
||||
lines.append(f"• Hedge PnL Trend: ${last_hedge_pnl:.2f} -> ${curr_hedge_equity:.2f} ({sign}${hedge_trend:.2f})")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class PositionMonitor:
|
||||
"""Monitors hedge_status.json for changes"""
|
||||
|
||||
def __init__(self, json_file_path: str, state_file_path: str):
|
||||
self.json_file_path = json_file_path
|
||||
self.state_file_path = state_file_path
|
||||
self.last_known_data = []
|
||||
self.last_file_hash = ""
|
||||
self.state = self.load_state()
|
||||
|
||||
def load_state(self) -> Dict[str, Any]:
|
||||
"""Load monitor state from file"""
|
||||
try:
|
||||
if os.path.exists(self.state_file_path):
|
||||
with open(self.state_file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not load state file: {e}")
|
||||
|
||||
return {
|
||||
"last_known_open_positions": [],
|
||||
"last_processed_timestamp": 0,
|
||||
"last_file_hash": ""
|
||||
}
|
||||
|
||||
def save_state(self):
|
||||
"""Save monitor state to file"""
|
||||
try:
|
||||
with open(self.state_file_path, 'w') as f:
|
||||
json.dump(self.state, f, indent=2)
|
||||
except Exception as e:
|
||||
logger.error(f"Could not save state file: {e}")
|
||||
|
||||
def get_file_hash(self, data: List[Dict]) -> str:
|
||||
"""Generate hash of file content to detect changes"""
|
||||
content = json.dumps(data, sort_keys=True)
|
||||
return hashlib.md5(content.encode()).hexdigest()
|
||||
|
||||
def safe_read_json(self) -> List[Dict]:
|
||||
"""Safely read JSON file with retry logic"""
|
||||
attempts = 0
|
||||
while attempts < 3:
|
||||
try:
|
||||
if not os.path.exists(self.json_file_path):
|
||||
logger.warning(f"JSON file not found: {self.json_file_path}")
|
||||
return []
|
||||
|
||||
with open(self.json_file_path, 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"Attempt {attempts + 1}: Error reading JSON file: {e}")
|
||||
time.sleep(1)
|
||||
attempts += 1
|
||||
|
||||
logger.error("Failed to read JSON file after 3 attempts")
|
||||
return []
|
||||
|
||||
def extract_notification_data(self, data: List[Dict]) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""Extract last closed and current open positions"""
|
||||
current_open = None
|
||||
last_closed = None
|
||||
|
||||
# Find current open position
|
||||
for item in data:
|
||||
if item.get('status') == 'OPEN':
|
||||
current_open = item
|
||||
break
|
||||
|
||||
# Find most recent closed position
|
||||
closed_positions = [item for item in data if item.get('status') == 'CLOSED']
|
||||
if closed_positions:
|
||||
# Sort by timestamp_open (descending) to get most recent
|
||||
closed_positions.sort(key=lambda x: x.get('timestamp_open', 0), reverse=True)
|
||||
last_closed = closed_positions[0]
|
||||
|
||||
return last_closed, current_open
|
||||
|
||||
def check_for_changes(self) -> bool:
|
||||
"""Check if there are changes requiring notification"""
|
||||
current_data = self.safe_read_json()
|
||||
|
||||
if not current_data:
|
||||
return False
|
||||
|
||||
# Check if file content actually changed
|
||||
current_hash = self.get_file_hash(current_data)
|
||||
if current_hash == self.state.get("last_file_hash", ""):
|
||||
return False
|
||||
|
||||
# Extract positions
|
||||
last_closed, current_open = self.extract_notification_data(current_data)
|
||||
|
||||
if not current_open:
|
||||
# No open position, nothing to notify about
|
||||
return False
|
||||
|
||||
current_open_id = current_open.get('token_id')
|
||||
last_known_opens = self.state.get("last_known_open_positions", [])
|
||||
|
||||
# Check if this is a new open position
|
||||
if current_open_id not in last_known_opens:
|
||||
# New position detected!
|
||||
self.last_known_data = current_data
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_notification_data(self) -> Tuple[Optional[Dict], Optional[Dict]]:
|
||||
"""Get data for notification"""
|
||||
current_data = self.safe_read_json()
|
||||
return self.extract_notification_data(current_data)
|
||||
|
||||
def update_state(self):
|
||||
"""Update internal state after notification"""
|
||||
current_data = self.safe_read_json()
|
||||
if current_data:
|
||||
_, current_open = self.extract_notification_data(current_data)
|
||||
|
||||
if current_open:
|
||||
# Update state with current open positions
|
||||
self.state["last_known_open_positions"] = [current_open.get('token_id')]
|
||||
self.state["last_processed_timestamp"] = int(time.time())
|
||||
self.state["last_file_hash"] = self.get_file_hash(current_data)
|
||||
self.save_state()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main monitoring loop"""
|
||||
logger.info("🤖 Telegram Monitor Starting...")
|
||||
|
||||
notifier = None
|
||||
if not TELEGRAM_ENABLED:
|
||||
logger.info("📵 Telegram notifications disabled (TELEGRAM_MONITOR_ENABLED=False)")
|
||||
else:
|
||||
if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID:
|
||||
logger.error("❌ Telegram enabled but missing BOT_TOKEN or CHAT_ID")
|
||||
return
|
||||
|
||||
# Initialize notifier and test connection
|
||||
notifier = TelegramNotifier(TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID)
|
||||
if not notifier.test_connection():
|
||||
logger.error("❌ Telegram connection failed - check token and network")
|
||||
return
|
||||
|
||||
logger.info(f"✅ Telegram connection established to chat ID: {TELEGRAM_CHAT_ID}")
|
||||
|
||||
# Initialize monitor
|
||||
monitor = PositionMonitor(HEDGE_STATUS_FILE, TELEGRAM_STATE_FILE)
|
||||
|
||||
logger.info(f"Monitoring file: {HEDGE_STATUS_FILE}")
|
||||
logger.info(f"Check interval: {TELEGRAM_CHECK_INTERVAL} seconds")
|
||||
logger.info(f"State file: {TELEGRAM_STATE_FILE}")
|
||||
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
if monitor.check_for_changes():
|
||||
logger.info("New position opening detected!")
|
||||
|
||||
if TELEGRAM_ENABLED and notifier:
|
||||
last_closed, current_open = monitor.get_notification_data()
|
||||
|
||||
if current_open:
|
||||
message = notifier.format_position_message(last_closed, current_open)
|
||||
success = notifier.send_message(message)
|
||||
|
||||
if success:
|
||||
monitor.update_state()
|
||||
logger.info(f"Notification sent for position {current_open.get('token_id')}")
|
||||
else:
|
||||
logger.error("Failed to send notification")
|
||||
else:
|
||||
logger.warning("Position change detected but no open position found")
|
||||
else:
|
||||
logger.info("Telegram disabled or notifier not available - skipping notification")
|
||||
monitor.update_state() # Still update state to avoid loops
|
||||
else:
|
||||
logger.debug("No changes detected")
|
||||
|
||||
time.sleep(TELEGRAM_CHECK_INTERVAL)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"Error in monitoring loop: {e}")
|
||||
time.sleep(TELEGRAM_CHECK_INTERVAL) # Continue after error
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Shutting down Telegram Monitor...")
|
||||
|
||||
logger.info("Telegram Monitor stopped")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user