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:
tinkle-community
2026-02-04 02:41:37 +08:00
parent 23dbbf6bdd
commit ca92b849cd
6 changed files with 137 additions and 27 deletions

View File

@@ -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
}

View File

@@ -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)
}