mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-13 07:46:54 +08:00
fix: KuCoin timestamp sync, improve no-coins handling, update README icons
- Add server time synchronization for KuCoin API to fix timestamp error (400002) - Return empty list instead of error when no available coins (ai500.go) - Save cycle record even when no candidate coins (show in frontend without red error) - Update Claude icon to Anthropic dark brand color (#141413) - Add exchange and AI model icons to README.md and README.ja.md
This commit is contained in:
@@ -578,9 +578,19 @@ func (at *AutoTrader) runCycle() error {
|
||||
// NOTE: Must be called BEFORE candidate coins check to ensure equity is always recorded
|
||||
at.saveEquitySnapshot(ctx)
|
||||
|
||||
// 如果没有候选币种,友好提示并跳过本周期
|
||||
// 如果没有候选币种,记录但不报错
|
||||
if len(ctx.CandidateCoins) == 0 {
|
||||
logger.Infof("ℹ️ No candidate coins available, skipping this cycle")
|
||||
record.Success = true // 不是错误,只是没有候选币
|
||||
record.ExecutionLog = append(record.ExecutionLog, "No candidate coins available, cycle skipped")
|
||||
record.AccountState = store.AccountSnapshot{
|
||||
TotalBalance: ctx.Account.TotalEquity,
|
||||
AvailableBalance: ctx.Account.AvailableBalance,
|
||||
TotalUnrealizedProfit: ctx.Account.UnrealizedPnL,
|
||||
PositionCount: ctx.Account.PositionCount,
|
||||
InitialBalance: at.initialBalance,
|
||||
}
|
||||
at.saveDecision(record)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ type KuCoinTrader struct {
|
||||
// HTTP client
|
||||
httpClient *http.Client
|
||||
|
||||
// Server time offset (local - server) in milliseconds
|
||||
serverTimeOffset int64
|
||||
serverTimeMutex sync.RWMutex
|
||||
|
||||
// Balance cache
|
||||
cachedBalance map[string]interface{}
|
||||
balanceCacheTime time.Time
|
||||
@@ -107,10 +111,64 @@ func NewKuCoinTrader(apiKey, secretKey, passphrase string) *KuCoinTrader {
|
||||
contractsCache: make(map[string]*KuCoinContract),
|
||||
}
|
||||
|
||||
// Sync server time on initialization
|
||||
if err := trader.syncServerTime(); err != nil {
|
||||
logger.Warnf("⚠️ Failed to sync KuCoin server time: %v (will retry on first request)", err)
|
||||
}
|
||||
|
||||
logger.Infof("✓ KuCoin Futures trader initialized")
|
||||
return trader
|
||||
}
|
||||
|
||||
// syncServerTime fetches KuCoin server time and calculates offset
|
||||
func (t *KuCoinTrader) syncServerTime() error {
|
||||
resp, err := t.httpClient.Get(kucoinBaseURL + "/api/v1/timestamp")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get server time: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
var result struct {
|
||||
Code string `json:"code"`
|
||||
Data int64 `json:"data"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return fmt.Errorf("failed to parse response: %w", err)
|
||||
}
|
||||
|
||||
if result.Code != "200000" {
|
||||
return fmt.Errorf("server time API error: %s", result.Code)
|
||||
}
|
||||
|
||||
serverTime := result.Data
|
||||
localTime := time.Now().UnixMilli()
|
||||
offset := localTime - serverTime
|
||||
|
||||
t.serverTimeMutex.Lock()
|
||||
t.serverTimeOffset = offset
|
||||
t.serverTimeMutex.Unlock()
|
||||
|
||||
logger.Infof("✓ KuCoin time synced: offset=%dms (local %d - server %d)", offset, localTime, serverTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTimestamp returns the current timestamp adjusted for server time offset
|
||||
func (t *KuCoinTrader) getTimestamp() string {
|
||||
t.serverTimeMutex.RLock()
|
||||
offset := t.serverTimeOffset
|
||||
t.serverTimeMutex.RUnlock()
|
||||
|
||||
// Subtract offset to get server time from local time
|
||||
timestamp := time.Now().UnixMilli() - offset
|
||||
return strconv.FormatInt(timestamp, 10)
|
||||
}
|
||||
|
||||
// sign generates KuCoin API signature
|
||||
func (t *KuCoinTrader) sign(timestamp, method, requestPath, body string) string {
|
||||
// KuCoin signature: base64(HMAC-SHA256(timestamp + method + endpoint + body, secretKey))
|
||||
@@ -147,7 +205,7 @@ func (t *KuCoinTrader) doRequest(method, path string, body interface{}) ([]byte,
|
||||
}
|
||||
}
|
||||
|
||||
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
||||
timestamp := t.getTimestamp()
|
||||
signature := t.sign(timestamp, method, path, string(bodyBytes))
|
||||
signedPassphrase := t.signPassphrase(t.passphrase)
|
||||
|
||||
@@ -186,6 +244,13 @@ func (t *KuCoinTrader) doRequest(method, path string, body interface{}) ([]byte,
|
||||
}
|
||||
|
||||
if kcResp.Code != "200000" {
|
||||
// If timestamp error, try to re-sync server time
|
||||
if kcResp.Code == "400002" || strings.Contains(kcResp.Msg, "TIMESTAMP") {
|
||||
logger.Warnf("⚠️ KuCoin timestamp error, re-syncing server time...")
|
||||
if err := t.syncServerTime(); err != nil {
|
||||
logger.Warnf("⚠️ Failed to re-sync server time: %v", err)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("KuCoin API error: code=%s, msg=%s", kcResp.Code, kcResp.Msg)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user