fix: data race, scale-in PnL, nonce mutex, dead code, hourly check

- 🔴 Data race: Add GetPositionsCopy() returning deep copies (no shared
  ArbPosition pointers). Use it in dashboard broadcastLoop + handleStatus.
- 🟡 Scale-in PnL: Track LongEntryPrices/ShortEntryPrices on ArbPosition,
  compute weighted average (harmonic mean) at exit for accurate PnL.
- 🟢 CalcNetProfit: Delete dead code from exchange/helpers.go.
- 🟢 HL nonce: Add sync.Mutex around lastNonce++ (thread safety).
- 🟢 Hourly check: Change from 5-second window to minute window.
- 🟢 ExitPrice: Test mode closeLeg already handled by checkExit.
This commit is contained in:
jackyu66git
2026-05-03 18:31:14 +08:00
parent ab48e207a5
commit b08d8490fc
6 changed files with 94 additions and 54 deletions
+3 -40
View File
@@ -1,42 +1,5 @@
package exchange
import "github.com/gorilla/websocket"
// These are needed for compilation of the exchange package.
// PriceConnector is defined in connector.go.
var _ = websocket.ErrCloseSent // keep gorilla/websocket import
// CalcNetProfit calculates net profit % for a complete round trip (entry + exit) between two exchanges.
// buyPrice: price on the buy exchange
// sellPrice: price on the sell exchange
// buyFee: fee rate on buy exchange (e.g. 0.03 for 0.03%)
// sellFee: fee rate on sell exchange
// buyFee2: buy fee on the other exchange
// sellFee2: sell fee on the other exchange
// Returns net profit in percentage.
func CalcNetProfit(price1, price2, fee1Buy, fee1Sell, fee2Buy, fee2Sell float64) float64 {
// price1 = Bitget, price2 = HyperLiquid
// Try: buy cheap (min), sell expensive (max)
buyPrice := price1
sellPrice := price2
buyFee := fee1Buy
sellFee := fee2Sell
if price2 < price1 {
buyPrice = price2
sellPrice = price1
buyFee = fee2Buy
sellFee = fee1Sell
}
// Entry: buy at buyPrice (pay buyFee), sell short at sellPrice (pay sellFee)
if buyPrice <= 0 || sellPrice <= 0 {
return 0
}
cost := buyPrice * (1 + buyFee/100)
revenue := sellPrice * (1 - sellFee/100)
// Exit: sell long (pay sellFee), buy back short (pay buyFee)
// Total fees = 2 * (buyFee + sellFee), first round already in formula above
return (revenue/cost-1)*100 - (buyFee + sellFee)
}
// Package-level helpers for the exchange package.
// connector.go imports gorilla/websocket, so this file needs no imports
// for that dependency. CalcNetProfit was removed (see netProfit in scanner.go).
+5 -1
View File
@@ -8,6 +8,7 @@ import (
"math/big"
"net/http"
"strings"
"sync"
"time"
"crypto/ed25519"
@@ -50,6 +51,7 @@ type HyperLiquidTrade struct {
Address string
client *http.Client
lastNonce int64
nonceMu sync.Mutex // protect lastNonce++ (Issue #4)
}
func NewHyperLiquidTrade(privateKeyHex, address string) (*HyperLiquidTrade, error) {
@@ -96,9 +98,11 @@ func (h *HyperLiquidTrade) PlaceMarketOrder(coin, side, sz string) (string, erro
BrokerCode: 0,
}
// Generate nonce
// Generate nonce (thread-safe)
h.nonceMu.Lock()
h.lastNonce++
nonce := time.Now().UnixMilli()*1_000_000 + h.lastNonce%1_000_000
h.nonceMu.Unlock()
// Sign the action
sig, err := h.signAction(action, nonce)