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:
jackyu66git
2026-05-03 22:25:01 +08:00
parent 6a4b046742
commit 915b316ca7
3 changed files with 31 additions and 11 deletions
+6 -3
View File
@@ -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)
+14 -6
View File
@@ -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)
}
+11 -2
View File
@@ -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())
}
}