mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-11 06:46:59 +08:00
reliability: wrap all 27 bare goroutines with safe.Go/GoNamed panic recovery
Applied safe.GoNamed to: - 9 exchange order_sync goroutines (OKX, Hyperliquid, Aster, Bybit, KuCoin, Gate, Bitget, Lighter, Binance×2) - Drawdown monitor (auto_trader_risk.go) - Brain news scanner + market briefs (agent/brain.go) - Sentinel scanner (agent/sentinel.go) - Agent scheduler (agent/scheduler.go) - x402 idle watchdog (mcp/payment/x402.go) - MCP stream idle watchdog (mcp/client.go) - Rate limiter cleanup (api/rate_limiter.go) - 3 telemetry fire-and-forget sends (telemetry/experience.go) - CoinAnk WS handler (provider/coinank/coinank_api/kline_ws.go) - API server goroutine (main.go) Added manual defer/recover with error reporting to: - Telegram AI agent handler (sends error msg to user on panic) - Trader data fetch (returns error result on panic to prevent deadlock) Before: a panic in ANY of these 27 goroutines would crash the entire trading process with zero diagnostics. Now all panics are caught, logged with stack traces, and the process continues running.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"nofx/safe"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -49,7 +50,7 @@ func (b *Brain) HandleSignal(sig Signal) {
|
|||||||
|
|
||||||
func (b *Brain) StartNewsScan(interval time.Duration) {
|
func (b *Brain) StartNewsScan(interval time.Duration) {
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
go func() {
|
safe.GoNamed("brain-news-scan", func() {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
@@ -59,7 +60,7 @@ func (b *Brain) StartNewsScan(interval time.Duration) {
|
|||||||
b.scanNews(seen)
|
b.scanNews(seen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Brain) scanNews(seen map[string]bool) {
|
func (b *Brain) scanNews(seen map[string]bool) {
|
||||||
@@ -117,7 +118,7 @@ func (b *Brain) scanNews(seen map[string]bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Brain) StartMarketBriefs(hours []int) {
|
func (b *Brain) StartMarketBriefs(hours []int) {
|
||||||
go func() {
|
safe.GoNamed("brain-market-briefs", func() {
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
sent := make(map[string]bool)
|
sent := make(map[string]bool)
|
||||||
@@ -134,7 +135,7 @@ func (b *Brain) StartMarketBriefs(hours []int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Brain) sendBrief(hour int) {
|
func (b *Brain) sendBrief(hour int) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"nofx/safe"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -19,7 +20,7 @@ func NewScheduler(a *Agent, l *slog.Logger) *Scheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scheduler) Start(ctx context.Context) {
|
func (s *Scheduler) Start(ctx context.Context) {
|
||||||
go func() {
|
safe.GoNamed("agent-scheduler", func() {
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
lastReport := time.Time{}
|
lastReport := time.Time{}
|
||||||
@@ -50,7 +51,7 @@ func (s *Scheduler) Start(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scheduler) Stop() { close(s.stopCh) }
|
func (s *Scheduler) Stop() { close(s.stopCh) }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"nofx/safe"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -61,7 +62,7 @@ func NewSentinel(symbols []string, cb SignalCallback, logger *slog.Logger) *Sent
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sentinel) Start() {
|
func (s *Sentinel) Start() {
|
||||||
go func() {
|
safe.GoNamed("sentinel", func() {
|
||||||
ticker := time.NewTicker(60 * time.Second)
|
ticker := time.NewTicker(60 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
s.scan()
|
s.scan()
|
||||||
@@ -73,7 +74,7 @@ func (s *Sentinel) Start() {
|
|||||||
s.scan()
|
s.scan()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Sentinel) Stop() { close(s.stopCh) }
|
func (s *Sentinel) Stop() { close(s.stopCh) }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"nofx/safe"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -35,13 +36,13 @@ func NewLoginRateLimiter() *LoginRateLimiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up stale entries every 10 minutes
|
// Clean up stale entries every 10 minutes
|
||||||
go func() {
|
safe.GoNamed("rate-limiter-cleanup", func() {
|
||||||
ticker := time.NewTicker(10 * time.Minute)
|
ticker := time.NewTicker(10 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
rl.cleanup()
|
rl.cleanup()
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
return rl
|
return rl
|
||||||
}
|
}
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -10,6 +10,7 @@ import (
|
|||||||
"nofx/telemetry"
|
"nofx/telemetry"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
"nofx/manager"
|
"nofx/manager"
|
||||||
|
"nofx/safe"
|
||||||
_ "nofx/mcp/payment"
|
_ "nofx/mcp/payment"
|
||||||
_ "nofx/mcp/provider"
|
_ "nofx/mcp/provider"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
@@ -136,11 +137,11 @@ func main() {
|
|||||||
telegramReloadCh := make(chan struct{}, 1)
|
telegramReloadCh := make(chan struct{}, 1)
|
||||||
server.SetTelegramReloadCh(telegramReloadCh)
|
server.SetTelegramReloadCh(telegramReloadCh)
|
||||||
|
|
||||||
go func() {
|
safe.GoNamed("api-server", func() {
|
||||||
if err := server.Start(); err != nil {
|
if err := server.Start(); err != nil {
|
||||||
logger.Fatalf("❌ Failed to start API server: %v", err)
|
logger.Fatalf("❌ Failed to start API server: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
// Start NOFXi Agent (proactive intelligence layer) — must be created BEFORE Telegram bot
|
// Start NOFXi Agent (proactive intelligence layer) — must be created BEFORE Telegram bot
|
||||||
nofxiAgent := nofxiagent.New(traderManager, st, nil, slog.Default())
|
nofxiAgent := nofxiagent.New(traderManager, st, nil, slog.Default())
|
||||||
|
|||||||
@@ -273,6 +273,16 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
|
|||||||
// Concurrently fetch data for each trader
|
// Concurrently fetch data for each trader
|
||||||
for i, t := range traders {
|
for i, t := range traders {
|
||||||
go func(index int, trader *trader.AutoTrader) {
|
go func(index int, trader *trader.AutoTrader) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Errorf("🔥 [trader-data-fetch] panic for trader %s: %v", trader.GetName(), r)
|
||||||
|
resultChan <- traderResult{index: index, data: map[string]interface{}{
|
||||||
|
"trader_id": trader.GetID(),
|
||||||
|
"trader_name": trader.GetName(),
|
||||||
|
"error": "internal panic",
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}()
|
||||||
// Set timeout to 10 seconds for single trader (increased from 3s for DEX reliability)
|
// Set timeout to 10 seconds for single trader (increased from 3s for DEX reliability)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -281,14 +291,14 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [
|
|||||||
accountChan := make(chan map[string]interface{}, 1)
|
accountChan := make(chan map[string]interface{}, 1)
|
||||||
errorChan := make(chan error, 1)
|
errorChan := make(chan error, 1)
|
||||||
|
|
||||||
go func() {
|
safe.GoNamed("trader-account-info", func() {
|
||||||
account, err := trader.GetAccountInfo()
|
account, err := trader.GetAccountInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorChan <- err
|
errorChan <- err
|
||||||
} else {
|
} else {
|
||||||
accountChan <- account
|
accountChan <- account
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
status := trader.GetStatus()
|
status := trader.GetStatus()
|
||||||
var traderData map[string]interface{}
|
var traderData map[string]interface{}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"nofx/safe"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -689,7 +690,7 @@ func (client *Client) CallWithRequestStream(req *Request, onChunk func(string))
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
resetCh := make(chan struct{}, 1)
|
resetCh := make(chan struct{}, 1)
|
||||||
go func() {
|
safe.GoNamed("mcp-stream-idle-watchdog", func() {
|
||||||
t := time.NewTimer(idleTimeout)
|
t := time.NewTimer(idleTimeout)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
for {
|
for {
|
||||||
@@ -710,7 +711,7 @@ func (client *Client) CallWithRequestStream(req *Request, onChunk func(string))
|
|||||||
t.Reset(idleTimeout)
|
t.Reset(idleTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
httpReq = httpReq.WithContext(ctx)
|
httpReq = httpReq.WithContext(ctx)
|
||||||
resp, err := client.HTTPClient.Do(httpReq)
|
resp, err := client.HTTPClient.Do(httpReq)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
||||||
"nofx/mcp"
|
"nofx/mcp"
|
||||||
|
"nofx/safe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -405,7 +406,7 @@ func X402CallStream(c *mcp.Client, signFn X402SignFunc, tag string, systemPrompt
|
|||||||
|
|
||||||
// Start idle-timeout watchdog AFTER the 402 dance is done.
|
// Start idle-timeout watchdog AFTER the 402 dance is done.
|
||||||
resetCh := make(chan struct{}, 1)
|
resetCh := make(chan struct{}, 1)
|
||||||
go func() {
|
safe.GoNamed("x402-idle-watchdog", func() {
|
||||||
t := time.NewTimer(x402StreamIdleTimeout)
|
t := time.NewTimer(x402StreamIdleTimeout)
|
||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
for {
|
for {
|
||||||
@@ -426,7 +427,7 @@ func X402CallStream(c *mcp.Client, signFn X402SignFunc, tag string, systemPrompt
|
|||||||
t.Reset(x402StreamIdleTimeout)
|
t.Reset(x402StreamIdleTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
onLine := func() {
|
onLine := func() {
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"nofx/provider/coinank"
|
"nofx/provider/coinank"
|
||||||
"nofx/provider/coinank/coinank_enum"
|
"nofx/provider/coinank/coinank_enum"
|
||||||
|
"nofx/safe"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ func read(conn *websocket.Conn, ch chan string) {
|
|||||||
func handleResponse(ch <-chan string, needKline bool, needTicker bool) (<-chan *WsResult[coinank.KlineResult], <-chan *WsResult[KlineTickers]) {
|
func handleResponse(ch <-chan string, needKline bool, needTicker bool) (<-chan *WsResult[coinank.KlineResult], <-chan *WsResult[KlineTickers]) {
|
||||||
klineCh := make(chan *WsResult[coinank.KlineResult], 1024)
|
klineCh := make(chan *WsResult[coinank.KlineResult], 1024)
|
||||||
tickersCh := make(chan *WsResult[KlineTickers], 1024)
|
tickersCh := make(chan *WsResult[KlineTickers], 1024)
|
||||||
go func() {
|
safe.GoNamed("coinank-ws-handler", func() {
|
||||||
if needKline {
|
if needKline {
|
||||||
defer close(klineCh)
|
defer close(klineCh)
|
||||||
} else {
|
} else {
|
||||||
@@ -147,7 +148,7 @@ func handleResponse(ch <-chan string, needKline bool, needTicker bool) (<-chan *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
return klineCh, tickersCh
|
return klineCh, tickersCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,13 @@ func runBot(token string, cfg *config.Config, st *store.Store) bool {
|
|||||||
|
|
||||||
// ── AI agent ─────────────────────────────────────────────────────────
|
// ── AI agent ─────────────────────────────────────────────────────────
|
||||||
go func(chatID int64, text string) {
|
go func(chatID int64, text string) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
logger.Errorf("🔥 [telegram-ai-agent] panic: %v", r)
|
||||||
|
msg := tgbotapi.NewMessage(chatID, "⚠️ Internal error. Please try again.")
|
||||||
|
bot.Send(msg) //nolint:errcheck
|
||||||
|
}
|
||||||
|
}()
|
||||||
sent, err := bot.Send(tgbotapi.NewMessage(chatID, "⏳"))
|
sent, err := bot.Send(tgbotapi.NewMessage(chatID, "⏳"))
|
||||||
placeholderID := 0
|
placeholderID := 0
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"nofx/safe"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -108,9 +109,9 @@ func TrackTrade(event TradeEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send asynchronously to not block trading
|
// Send asynchronously to not block trading
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
_ = sendTradeEvent(event)
|
_ = sendTradeEvent(event)
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendTradeEvent sends the trade event to GA4
|
// sendTradeEvent sends the trade event to GA4
|
||||||
@@ -165,7 +166,7 @@ func TrackStartup(version string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
client.mu.RLock()
|
client.mu.RLock()
|
||||||
installationID := client.installationID
|
installationID := client.installationID
|
||||||
client.mu.RUnlock()
|
client.mu.RUnlock()
|
||||||
@@ -194,7 +195,7 @@ func TrackStartup(version string) {
|
|||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TrackAIUsage(event AIUsageEvent) {
|
func TrackAIUsage(event AIUsageEvent) {
|
||||||
@@ -202,7 +203,7 @@ func TrackAIUsage(event AIUsageEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
safe.Go(func() {
|
||||||
client.mu.RLock()
|
client.mu.RLock()
|
||||||
installationID := client.installationID
|
installationID := client.installationID
|
||||||
client.mu.RUnlock()
|
client.mu.RUnlock()
|
||||||
@@ -238,5 +239,5 @@ func TrackAIUsage(event AIUsageEvent) {
|
|||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package aster
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -182,12 +183,12 @@ func deriveAsterOrderAction(side, positionSide string, realizedPnL float64) stri
|
|||||||
// StartOrderSync starts background order sync task for Aster
|
// StartOrderSync starts background order sync task for Aster
|
||||||
func (t *AsterTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *AsterTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("aster-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromAster(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromAster(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ Aster order sync failed: %v", err)
|
logger.Infof("⚠️ Aster order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 Aster order sync started (interval: %v)", interval)
|
logger.Infof("🔄 Aster order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package trader
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
|
"nofx/safe"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
// startDrawdownMonitor starts drawdown monitoring
|
// startDrawdownMonitor starts drawdown monitoring
|
||||||
func (at *AutoTrader) startDrawdownMonitor() {
|
func (at *AutoTrader) startDrawdownMonitor() {
|
||||||
at.monitorWg.Add(1)
|
at.monitorWg.Add(1)
|
||||||
go func() {
|
safe.GoNamed("drawdown-monitor", func() {
|
||||||
defer at.monitorWg.Done()
|
defer at.monitorWg.Done()
|
||||||
|
|
||||||
ticker := time.NewTicker(1 * time.Minute) // Check every minute
|
ticker := time.NewTicker(1 * time.Minute) // Check every minute
|
||||||
@@ -27,7 +28,7 @@ func (at *AutoTrader) startDrawdownMonitor() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkPositionDrawdown checks position drawdown situation
|
// checkPositionDrawdown checks position drawdown situation
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"nofx/trader/types"
|
"nofx/trader/types"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -351,21 +352,21 @@ func (t *FuturesTrader) determineOrderAction(side, positionSide string, realized
|
|||||||
// StartOrderSync starts background order sync task for Binance
|
// StartOrderSync starts background order sync task for Binance
|
||||||
func (t *FuturesTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *FuturesTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
// Run first sync immediately
|
// Run first sync immediately
|
||||||
go func() {
|
safe.GoNamed("binance-order-sync-initial", func() {
|
||||||
logger.Infof("🔄 Running initial Binance order sync...")
|
logger.Infof("🔄 Running initial Binance order sync...")
|
||||||
if err := t.SyncOrdersFromBinance(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromBinance(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ Initial Binance order sync failed: %v", err)
|
logger.Infof("⚠️ Initial Binance order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
|
|
||||||
// Then run periodically
|
// Then run periodically
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("binance-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromBinance(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromBinance(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ Binance order sync failed: %v", err)
|
logger.Infof("⚠️ Binance order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 Binance order sync started (interval: %v)", interval)
|
logger.Infof("🔄 Binance order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -281,12 +282,12 @@ func (t *BitgetTrader) SyncOrdersFromBitget(traderID string, exchangeID string,
|
|||||||
// StartOrderSync starts background order sync task for Bitget
|
// StartOrderSync starts background order sync task for Bitget
|
||||||
func (t *BitgetTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *BitgetTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("bitget-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromBitget(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromBitget(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ Bitget order sync failed: %v", err)
|
logger.Infof("⚠️ Bitget order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 Bitget order sync started (interval: %v)", interval)
|
logger.Infof("🔄 Bitget order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -300,12 +301,12 @@ func (t *BybitTrader) SyncOrdersFromBybit(traderID string, exchangeID string, ex
|
|||||||
// StartOrderSync starts background order sync task for Bybit
|
// StartOrderSync starts background order sync task for Bybit
|
||||||
func (t *BybitTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *BybitTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("bybit-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromBybit(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromBybit(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ Bybit order sync failed: %v", err)
|
logger.Infof("⚠️ Bybit order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 Bybit order sync started (interval: %v)", interval)
|
logger.Infof("🔄 Bybit order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -293,12 +294,12 @@ func (t *GateTrader) SyncOrdersFromGate(traderID string, exchangeID string, exch
|
|||||||
// StartOrderSync starts background order sync task for Gate
|
// StartOrderSync starts background order sync task for Gate
|
||||||
func (t *GateTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *GateTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("gate-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromGate(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromGate(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ Gate order sync failed: %v", err)
|
logger.Infof("⚠️ Gate order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 Gate order sync started (interval: %v)", interval)
|
logger.Infof("🔄 Gate order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package hyperliquid
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -138,12 +139,12 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
|
|||||||
// StartOrderSync starts background order sync task
|
// StartOrderSync starts background order sync task
|
||||||
func (t *HyperliquidTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *HyperliquidTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("hyperliquid-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromHyperliquid(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromHyperliquid(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ Hyperliquid order sync failed: %v", err)
|
logger.Infof("⚠️ Hyperliquid order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 Hyperliquid order sync started (interval: %v)", interval)
|
logger.Infof("🔄 Hyperliquid order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"nofx/trader/types"
|
"nofx/trader/types"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -401,12 +402,12 @@ func (t *KuCoinTrader) SyncOrdersFromKuCoin(traderID string, exchangeID string,
|
|||||||
// StartOrderSync starts background order sync task for KuCoin
|
// StartOrderSync starts background order sync task for KuCoin
|
||||||
func (t *KuCoinTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *KuCoinTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("kucoin-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromKuCoin(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromKuCoin(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ KuCoin order sync failed: %v", err)
|
logger.Infof("⚠️ KuCoin order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 KuCoin order sync started (interval: %v)", interval)
|
logger.Infof("🔄 KuCoin order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -147,7 +148,7 @@ func (t *LighterTraderV2) SyncOrdersFromLighter(traderID string, exchangeID stri
|
|||||||
// StartOrderSync starts background order sync task
|
// StartOrderSync starts background order sync task
|
||||||
func (t *LighterTraderV2) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *LighterTraderV2) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("lighter-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromLighter(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromLighter(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
// Only log non-404 errors to reduce log spam
|
// Only log non-404 errors to reduce log spam
|
||||||
@@ -156,6 +157,6 @@ func (t *LighterTraderV2) StartOrderSync(traderID string, exchangeID string, exc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 Lighter order+position sync started (interval: %v)", interval)
|
logger.Infof("🔄 Lighter order+position sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
|
"nofx/safe"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -274,12 +275,12 @@ func (t *OKXTrader) SyncOrdersFromOKX(traderID string, exchangeID string, exchan
|
|||||||
// StartOrderSync starts background order sync task for OKX
|
// StartOrderSync starts background order sync task for OKX
|
||||||
func (t *OKXTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
func (t *OKXTrader) StartOrderSync(traderID string, exchangeID string, exchangeType string, st *store.Store, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
go func() {
|
safe.GoNamed("okx-order-sync", func() {
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
if err := t.SyncOrdersFromOKX(traderID, exchangeID, exchangeType, st); err != nil {
|
if err := t.SyncOrdersFromOKX(traderID, exchangeID, exchangeType, st); err != nil {
|
||||||
logger.Infof("⚠️ OKX order sync failed: %v", err)
|
logger.Infof("⚠️ OKX order sync failed: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
})
|
||||||
logger.Infof("🔄 OKX order sync started (interval: %v)", interval)
|
logger.Infof("🔄 OKX order sync started (interval: %v)", interval)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user