fix: HL size floor, random scan jitter 50-250ms
- GetHLSize: change rounding from Sprintf (round-to-nearest) to math.Floor (floor), consistent with GetBitgetSize - Scan interval: fixed 200ms → random 50-250ms to avoid lock-step with HyperLiquid's ~200ms allMids push cycle - README: update architecture diagram, trading logic, config note
This commit is contained in:
@@ -19,7 +19,8 @@ PriceStore ─────────┼─────────────
|
||||
└──────────────┘
|
||||
│
|
||||
┌─────────▼─────────┐
|
||||
│ ScanBGHL (200ms) │
|
||||
│ ScanBGHL (50-250ms │
|
||||
│ random jitter) │
|
||||
│ BG ↔ HL only │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
@@ -92,11 +93,13 @@ Then open [http://localhost:8888](http://localhost:8888) for the Web dashboard.
|
||||
| `TRADE_THRESHOLD` | `0.15` | Min net profit % to enter (after fees) |
|
||||
| `TRADE_AMOUNT_USD` | `10` | USD per leg |
|
||||
| `TRADE_COOLDOWN_MS` | `30000` | Cooldown between same-coin trades (ms) |
|
||||
| `TEST_MODE` | `false` | Simulate orders (no real API calls) |
|
||||
| `TEST_MODE` | `false` | Simulate orders with mock fills (no real API calls) |
|
||||
| `MOCK_SLIPPAGE_PCT` | `0.005` | Simulated slippage per leg (%) |
|
||||
| `BITGET_API_KEY` / `BITGET_API_SECRET` / `BITGET_PASSPHRASE` | — | Bitget API credentials (test mode skips) |
|
||||
| `HL_PRIVATE_KEY` / `HL_ADDRESS` | — | HyperLiquid wallet credentials (test mode skips) |
|
||||
|
||||
> **Note:** Scan interval is fixed at **50-250ms random jitter** (not configurable). This prevents lock-step with HyperLiquid's ~200ms push cycle.
|
||||
|
||||
## Fee Model
|
||||
|
||||
All trades use **maker** (limit orders), no rebate. Only Bitget and HyperLiquid are used for trading:
|
||||
@@ -110,7 +113,7 @@ Round trip (2 legs entry + 2 legs exit): **0.07%** total fees.
|
||||
|
||||
## Trading Logic
|
||||
|
||||
1. **Scanner** runs every 500ms, checks all 6 coins for BG ↔ HL spread
|
||||
1. **Scanner** runs every 50-250ms (random jitter to avoid lock-step with exchange push cycles), checks all 6 coins for BG ↔ HL spread
|
||||
2. **Entry** when net profit ≥ `TRADE_THRESHOLD` (after full round-trip fees)
|
||||
- Uses scan-time prices directly (no re-read from store to avoid WS jitter)
|
||||
- Synchronous execution in the scanner tick (no goroutine delay)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -171,21 +172,28 @@ func (h *HyperLiquidTrade) signAction(action HLOrderAction, nonce int64) (string
|
||||
// GetHLSize calculates size for a given USD amount on HyperLiquid.
|
||||
// Uses szDecimals precision from HL's contract universe.
|
||||
// Returns size as a decimal string complying with HL precision.
|
||||
// Uses math.Floor to round DOWN to the nearest valid step, consistent with GetBitgetSize.
|
||||
func GetHLSize(coin string, amountUSD, price float64) string {
|
||||
sz := amountUSD / price // raw coin count
|
||||
switch coin {
|
||||
case "DOGE":
|
||||
return fmt.Sprintf("%.0f", sz) // szDecimals=0
|
||||
sz = math.Floor(sz) // step=1, szDecimals=0
|
||||
return fmt.Sprintf("%.0f", sz)
|
||||
case "LINK":
|
||||
return fmt.Sprintf("%.1f", sz) // szDecimals=1
|
||||
sz = math.Floor(sz*10) / 10 // step=0.1, szDecimals=1
|
||||
return fmt.Sprintf("%.1f", sz)
|
||||
case "ONDO":
|
||||
return fmt.Sprintf("%.0f", sz) // szDecimals=0
|
||||
sz = math.Floor(sz) // step=1, szDecimals=0
|
||||
return fmt.Sprintf("%.0f", sz)
|
||||
case "OP":
|
||||
return fmt.Sprintf("%.1f", sz) // szDecimals=1
|
||||
sz = math.Floor(sz*10) / 10 // step=0.1, szDecimals=1
|
||||
return fmt.Sprintf("%.1f", sz)
|
||||
case "WIF":
|
||||
return fmt.Sprintf("%.0f", sz) // szDecimals=0
|
||||
sz = math.Floor(sz) // step=1, szDecimals=0
|
||||
return fmt.Sprintf("%.0f", sz)
|
||||
case "ARB":
|
||||
return fmt.Sprintf("%.1f", sz) // szDecimals=1
|
||||
sz = math.Floor(sz*10) / 10 // step=0.1, szDecimals=1
|
||||
return fmt.Sprintf("%.1f", sz)
|
||||
default:
|
||||
return fmt.Sprintf("%.4f", sz)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
@@ -113,10 +114,16 @@ func main() {
|
||||
|
||||
// Main loop
|
||||
lastHour := -1
|
||||
scannerTick := time.NewTicker(time.Duration(cfg.ScanIntervalMs) * time.Millisecond)
|
||||
|
||||
// Random jitter 50-250ms to avoid lock-step with exchange push cycles
|
||||
jitterMin, jitterMax := 50, 250
|
||||
randInterval := func() time.Duration {
|
||||
return time.Duration(jitterMin+rand.Intn(jitterMax-jitterMin+1)) * time.Millisecond
|
||||
}
|
||||
scannerTick := time.NewTimer(randInterval())
|
||||
statusTick := time.NewTicker(30 * time.Second)
|
||||
|
||||
log.Printf("[Monitor] Scanner running every %dms", cfg.ScanIntervalMs)
|
||||
log.Printf("[Monitor] Scanner running every %d-%dms (random jitter)", jitterMin, jitterMax)
|
||||
|
||||
runLoop := true
|
||||
for runLoop {
|
||||
@@ -202,6 +209,8 @@ func main() {
|
||||
notifier.SendTradeSummary(positions, now.Format("2006-01-02 15:04"))
|
||||
lastHour = hour
|
||||
}
|
||||
|
||||
scannerTick.Reset(randInterval())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user