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
+37
View File
@@ -103,6 +103,43 @@ Binance互換の分散型無期限先物取引所!
--- ---
## 対応取引所
### CEX(中央集権型取引所)
| 取引所 | ステータス | 登録(手数料割引) |
|:-------|:----------:|:-------------------|
| <img src="web/public/exchange-icons/binance.jpg" width="20" height="20" style="vertical-align: middle;"/> **Binance** | ✅ | [登録](https://www.binance.com/join?ref=NOFXENG) |
| <img src="web/public/exchange-icons/bybit.png" width="20" height="20" style="vertical-align: middle;"/> **Bybit** | ✅ | [登録](https://partner.bybit.com/b/83856) |
| <img src="web/public/exchange-icons/okx.svg" width="20" height="20" style="vertical-align: middle;"/> **OKX** | ✅ | [登録](https://www.okx.com/join/1865360) |
| <img src="web/public/exchange-icons/bitget.svg" width="20" height="20" style="vertical-align: middle;"/> **Bitget** | ✅ | [登録](https://www.bitget.com/referral/register?from=referral&clacCode=c8a43172) |
| <img src="web/public/exchange-icons/kucoin.svg" width="20" height="20" style="vertical-align: middle;"/> **KuCoin** | ✅ | [登録](https://www.kucoin.com/r/broker/CXEV7XKK) |
| <img src="web/public/exchange-icons/gate.svg" width="20" height="20" style="vertical-align: middle;"/> **Gate** | ✅ | [登録](https://www.gatenode.xyz/share/VQBGUAxY) |
### Perp-DEX(分散型無期限取引所)
| 取引所 | ステータス | 登録(手数料割引) |
|:-------|:----------:|:-------------------|
| <img src="web/public/exchange-icons/hyperliquid.png" width="20" height="20" style="vertical-align: middle;"/> **Hyperliquid** | ✅ | [登録](https://app.hyperliquid.xyz/join/AITRADING) |
| <img src="web/public/exchange-icons/aster.svg" width="20" height="20" style="vertical-align: middle;"/> **Aster DEX** | ✅ | [登録](https://www.asterdex.com/en/referral/fdfc0e) |
| <img src="web/public/exchange-icons/lighter.png" width="20" height="20" style="vertical-align: middle;"/> **Lighter** | ✅ | [登録](https://app.lighter.xyz/?referral=68151432) |
---
## 対応AIモデル
| AIモデル | ステータス | APIキー取得 |
|:---------|:----------:|:------------|
| <img src="web/public/icons/deepseek.svg" width="20" height="20" style="vertical-align: middle;"/> **DeepSeek** | ✅ | [APIキー取得](https://platform.deepseek.com) |
| <img src="web/public/icons/qwen.svg" width="20" height="20" style="vertical-align: middle;"/> **Qwen** | ✅ | [APIキー取得](https://dashscope.console.aliyun.com) |
| <img src="web/public/icons/openai.svg" width="20" height="20" style="vertical-align: middle;"/> **OpenAI (GPT)** | ✅ | [APIキー取得](https://platform.openai.com) |
| <img src="web/public/icons/claude.svg" width="20" height="20" style="vertical-align: middle;"/> **Claude** | ✅ | [APIキー取得](https://console.anthropic.com) |
| <img src="web/public/icons/gemini.svg" width="20" height="20" style="vertical-align: middle;"/> **Gemini** | ✅ | [APIキー取得](https://aistudio.google.com) |
| <img src="web/public/icons/grok.svg" width="20" height="20" style="vertical-align: middle;"/> **Grok** | ✅ | [APIキー取得](https://console.x.ai) |
| <img src="web/public/icons/kimi.svg" width="20" height="20" style="vertical-align: middle;"/> **Kimi** | ✅ | [APIキー取得](https://platform.moonshot.cn) |
---
## 📸 スクリーンショット ## 📸 スクリーンショット
### 🏆 競争モード - リアルタイムAIバトル ### 🏆 競争モード - リアルタイムAIバトル
+19 -19
View File
@@ -78,35 +78,35 @@ To use NOFX, you'll need:
### CEX (Centralized Exchanges) ### CEX (Centralized Exchanges)
| Exchange | Status | Register (Fee Discount) | | Exchange | Status | Register (Fee Discount) |
|----------|--------|-------------------------| |:---------|:------:|:------------------------|
| **Binance** | ✅ Supported | [Register](https://www.binance.com/join?ref=NOFXENG) | | <img src="web/public/exchange-icons/binance.jpg" width="20" height="20" style="vertical-align: middle;"/> **Binance** | ✅ | [Register](https://www.binance.com/join?ref=NOFXENG) |
| **Bybit** | ✅ Supported | [Register](https://partner.bybit.com/b/83856) | | <img src="web/public/exchange-icons/bybit.png" width="20" height="20" style="vertical-align: middle;"/> **Bybit** | ✅ | [Register](https://partner.bybit.com/b/83856) |
| **OKX** | ✅ Supported | [Register](https://www.okx.com/join/1865360) | | <img src="web/public/exchange-icons/okx.svg" width="20" height="20" style="vertical-align: middle;"/> **OKX** | ✅ | [Register](https://www.okx.com/join/1865360) |
| **Bitget** | ✅ Supported | [Register](https://www.bitget.com/referral/register?from=referral&clacCode=c8a43172) | | <img src="web/public/exchange-icons/bitget.svg" width="20" height="20" style="vertical-align: middle;"/> **Bitget** | ✅ | [Register](https://www.bitget.com/referral/register?from=referral&clacCode=c8a43172) |
| **KuCoin** | ✅ Supported | [Register](https://www.kucoin.com/r/broker/CXEV7XKK) | | <img src="web/public/exchange-icons/kucoin.svg" width="20" height="20" style="vertical-align: middle;"/> **KuCoin** | ✅ | [Register](https://www.kucoin.com/r/broker/CXEV7XKK) |
| **Gate** | ✅ Supported | [Register](https://www.gatenode.xyz/share/VQBGUAxY) | | <img src="web/public/exchange-icons/gate.svg" width="20" height="20" style="vertical-align: middle;"/> **Gate** | ✅ | [Register](https://www.gatenode.xyz/share/VQBGUAxY) |
### Perp-DEX (Decentralized Perpetual Exchanges) ### Perp-DEX (Decentralized Perpetual Exchanges)
| Exchange | Status | Register (Fee Discount) | | Exchange | Status | Register (Fee Discount) |
|----------|--------|-------------------------| |:---------|:------:|:------------------------|
| **Hyperliquid** | ✅ Supported | [Register](https://app.hyperliquid.xyz/join/AITRADING) | | <img src="web/public/exchange-icons/hyperliquid.png" width="20" height="20" style="vertical-align: middle;"/> **Hyperliquid** | ✅ | [Register](https://app.hyperliquid.xyz/join/AITRADING) |
| **Aster DEX** | ✅ Supported | [Register](https://www.asterdex.com/en/referral/fdfc0e) | | <img src="web/public/exchange-icons/aster.svg" width="20" height="20" style="vertical-align: middle;"/> **Aster DEX** | ✅ | [Register](https://www.asterdex.com/en/referral/fdfc0e) |
| **Lighter** | ✅ Supported | [Register](https://app.lighter.xyz/?referral=68151432) | | <img src="web/public/exchange-icons/lighter.png" width="20" height="20" style="vertical-align: middle;"/> **Lighter** | ✅ | [Register](https://app.lighter.xyz/?referral=68151432) |
--- ---
## Supported AI Models ## Supported AI Models
| AI Model | Status | Get API Key | | AI Model | Status | Get API Key |
|----------|--------|-------------| |:---------|:------:|:------------|
| **DeepSeek** | ✅ Supported | [Get API Key](https://platform.deepseek.com) | | <img src="web/public/icons/deepseek.svg" width="20" height="20" style="vertical-align: middle;"/> **DeepSeek** | ✅ | [Get API Key](https://platform.deepseek.com) |
| **Qwen** | ✅ Supported | [Get API Key](https://dashscope.console.aliyun.com) | | <img src="web/public/icons/qwen.svg" width="20" height="20" style="vertical-align: middle;"/> **Qwen** | ✅ | [Get API Key](https://dashscope.console.aliyun.com) |
| **OpenAI (GPT)** | ✅ Supported | [Get API Key](https://platform.openai.com) | | <img src="web/public/icons/openai.svg" width="20" height="20" style="vertical-align: middle;"/> **OpenAI (GPT)** | ✅ | [Get API Key](https://platform.openai.com) |
| **Claude** | ✅ Supported | [Get API Key](https://console.anthropic.com) | | <img src="web/public/icons/claude.svg" width="20" height="20" style="vertical-align: middle;"/> **Claude** | ✅ | [Get API Key](https://console.anthropic.com) |
| **Gemini** | ✅ Supported | [Get API Key](https://aistudio.google.com) | | <img src="web/public/icons/gemini.svg" width="20" height="20" style="vertical-align: middle;"/> **Gemini** | ✅ | [Get API Key](https://aistudio.google.com) |
| **Grok** | ✅ Supported | [Get API Key](https://console.x.ai) | | <img src="web/public/icons/grok.svg" width="20" height="20" style="vertical-align: middle;"/> **Grok** | ✅ | [Get API Key](https://console.x.ai) |
| **Kimi** | ✅ Supported | [Get API Key](https://platform.moonshot.cn) | | <img src="web/public/icons/kimi.svg" width="20" height="20" style="vertical-align: middle;"/> **Kimi** | ✅ | [Get API Key](https://platform.moonshot.cn) |
--- ---
+3 -5
View File
@@ -105,7 +105,8 @@ func (c *Client) GetTopRatedCoins(limit int) ([]string, error) {
} }
if len(availableCoins) == 0 { if len(availableCoins) == 0 {
return nil, fmt.Errorf("no available coins") // Empty list is normal - just return empty slice, not an error
return []string{}, nil
} }
// Sort by Score descending (bubble sort) // Sort by Score descending (bubble sort)
@@ -147,10 +148,7 @@ func (c *Client) GetAvailableCoins() ([]string, error) {
} }
} }
if len(symbols) == 0 { // Empty list is normal - just return empty slice, not an error
return nil, fmt.Errorf("no available coins")
}
return symbols, nil return symbols, nil
} }
+11 -1
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 // NOTE: Must be called BEFORE candidate coins check to ensure equity is always recorded
at.saveEquitySnapshot(ctx) at.saveEquitySnapshot(ctx)
// 如果没有候选币种,友好提示并跳过本周期 // 如果没有候选币种,记录但不报错
if len(ctx.CandidateCoins) == 0 { if len(ctx.CandidateCoins) == 0 {
logger.Infof("️ No candidate coins available, skipping this cycle") 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 return nil
} }
+66 -1
View File
@@ -50,6 +50,10 @@ type KuCoinTrader struct {
// HTTP client // HTTP client
httpClient *http.Client httpClient *http.Client
// Server time offset (local - server) in milliseconds
serverTimeOffset int64
serverTimeMutex sync.RWMutex
// Balance cache // Balance cache
cachedBalance map[string]interface{} cachedBalance map[string]interface{}
balanceCacheTime time.Time balanceCacheTime time.Time
@@ -107,10 +111,64 @@ func NewKuCoinTrader(apiKey, secretKey, passphrase string) *KuCoinTrader {
contractsCache: make(map[string]*KuCoinContract), 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") logger.Infof("✓ KuCoin Futures trader initialized")
return trader 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 // sign generates KuCoin API signature
func (t *KuCoinTrader) sign(timestamp, method, requestPath, body string) string { func (t *KuCoinTrader) sign(timestamp, method, requestPath, body string) string {
// KuCoin signature: base64(HMAC-SHA256(timestamp + method + endpoint + body, secretKey)) // 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)) signature := t.sign(timestamp, method, path, string(bodyBytes))
signedPassphrase := t.signPassphrase(t.passphrase) signedPassphrase := t.signPassphrase(t.passphrase)
@@ -186,6 +244,13 @@ func (t *KuCoinTrader) doRequest(method, path string, body interface{}) ([]byte,
} }
if kcResp.Code != "200000" { 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) return nil, fmt.Errorf("KuCoin API error: code=%s, msg=%s", kcResp.Code, kcResp.Msg)
} }
+1 -1
View File
@@ -1,4 +1,4 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<title>Claude</title> <title>Claude</title>
<path fill="#D97757" d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"/> <path fill="#141413" d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 301 B