mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-02 02:21:19 +08:00
- Redesign dashboard into a cream-paper + vermilion IBM Plex Mono terminal (live L2 order book, cost/liq map, WS K-line, signal matrix, orchestration topology, risk radar, execution log, current positions, equity curve) - Convert all user-facing UI and backend strings/prompts from Chinese to English (multi-language retained, default English) - Add /api/statistics/full endpoint + full-stats frontend wiring - Fix Autopilot launch: reuse the existing trader instead of creating duplicates (eliminates repeat ~35s create cost and stale-trader 404s); launch sends 5m scan interval - Fix unreadable toasts: cream theme with high-contrast text + per-type accent - Silence background dashboard polls (getTraderConfig) to stop error-toast spam
138 lines
4.1 KiB
Go
138 lines
4.1 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"nofx/logger"
|
|
"nofx/provider/vergex"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func (s *Server) handleVergexSignalRanking(c *gin.Context) {
|
|
client, ok := s.newVergexClientForRequest(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
data, err := client.GetSignalRanking(context.Background(), vergex.Query{
|
|
Chain: strings.TrimSpace(c.Query("chain")),
|
|
LiqBand: strings.TrimSpace(c.Query("liqBand")),
|
|
})
|
|
if err != nil {
|
|
logger.Warnf("Vergex signal-ranking failed: %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
limit := parsePositiveInt(c.Query("limit"), vergex.MaxSignalRankingItems)
|
|
marketType := strings.TrimSpace(c.Query("marketType"))
|
|
items := vergex.FilterSignalRankingItems(data.Items, marketType, limit)
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"items": items,
|
|
"raw": data.Raw,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleVergexSignalLab(c *gin.Context) {
|
|
client, ok := s.newVergexClientForRequest(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
body, err := client.GetSignalLab(context.Background(), vergex.Query{
|
|
MarketType: withDefault(strings.TrimSpace(c.Query("marketType")), vergex.DefaultMarketType),
|
|
Symbol: strings.TrimSpace(c.Query("symbol")),
|
|
Chain: strings.TrimSpace(c.Query("chain")),
|
|
LiqBand: strings.TrimSpace(c.Query("liqBand")),
|
|
})
|
|
if err != nil {
|
|
logger.Warnf("Vergex signal-lab failed: %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.Data(http.StatusOK, "application/json; charset=utf-8", body)
|
|
}
|
|
|
|
func (s *Server) handleVergexCostLiquidationHeatmap(c *gin.Context) {
|
|
client, ok := s.newVergexClientForRequest(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
body, err := client.GetCostLiquidationHeatmap(context.Background(), vergex.Query{
|
|
MarketType: withDefault(strings.TrimSpace(c.Query("marketType")), vergex.DefaultMarketType),
|
|
Symbol: strings.TrimSpace(c.Query("symbol")),
|
|
Chain: strings.TrimSpace(c.Query("chain")),
|
|
LiqBand: strings.TrimSpace(c.Query("liqBand")),
|
|
})
|
|
if err != nil {
|
|
logger.Warnf("Vergex cost-liquidation-heatmap failed: %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.Data(http.StatusOK, "application/json; charset=utf-8", body)
|
|
}
|
|
|
|
// handleVergexFlowMarkets proxies the Vergex net-flow market ranking (paid x402
|
|
// endpoint) using the caller's claw402 wallet. The upstream JSON is passed
|
|
// through verbatim: { data: { window, by, inflow: [{ symbol, netFlow,
|
|
// buyNotional, sellNotional, trades, latestPrice }, ...] } }.
|
|
func (s *Server) handleVergexFlowMarkets(c *gin.Context) {
|
|
client, ok := s.newVergexClientForRequest(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
chain := withDefault(strings.TrimSpace(c.Query("chain")), "mainnet")
|
|
window := withDefault(strings.TrimSpace(c.Query("window")), "1h")
|
|
limit := parsePositiveInt(c.Query("limit"), 25)
|
|
|
|
body, err := client.GetFlowMarkets(context.Background(), chain, window, limit)
|
|
if err != nil {
|
|
logger.Warnf("Vergex flow-markets failed: %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.Data(http.StatusOK, "application/json; charset=utf-8", body)
|
|
}
|
|
|
|
func (s *Server) newVergexClientForRequest(c *gin.Context) (*vergex.Client, bool) {
|
|
userID := c.GetString("user_id")
|
|
if userID == "" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
|
return nil, false
|
|
}
|
|
walletKey, err := s.resolveStrategyDataWalletKey(userID, c.Query("ai_model_id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return nil, false
|
|
}
|
|
if walletKey == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "claw402 wallet is not configured"})
|
|
return nil, false
|
|
}
|
|
client, err := vergex.NewClient("", walletKey, &logger.MCPLogger{})
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return nil, false
|
|
}
|
|
return client, true
|
|
}
|
|
|
|
func parsePositiveInt(raw string, fallback int) int {
|
|
if raw == "" {
|
|
return fallback
|
|
}
|
|
var n int
|
|
if _, err := fmt.Sscanf(raw, "%d", &n); err != nil || n <= 0 {
|
|
return fallback
|
|
}
|
|
return n
|
|
}
|
|
|
|
func withDefault(value, fallback string) string {
|
|
if strings.TrimSpace(value) == "" {
|
|
return fallback
|
|
}
|
|
return value
|
|
}
|