mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-11 06:46:59 +08:00
- Candlestick chart + volume histogram - 8 trading pairs: BTC/ETH/SOL/BNB/XRP/DOGE/ADA/AVAX - 5 timeframes: 15m/1H/4H/1D/1W - Real-time price & 24h change display - Auto-refresh every 30s - Backend: /api/klines + /api/ticker (Binance futures API) - Dark trading terminal theme, responsive resize - Tab switching: Chat ↔ Chart
127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
package interaction
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// KlineBar represents a single candlestick.
|
|
type KlineBar struct {
|
|
Time int64 `json:"time"`
|
|
Open float64 `json:"open"`
|
|
High float64 `json:"high"`
|
|
Low float64 `json:"low"`
|
|
Close float64 `json:"close"`
|
|
Volume float64 `json:"volume"`
|
|
}
|
|
|
|
// fetchKlines gets kline data from Binance public API (no auth needed).
|
|
func fetchKlines(symbol, interval string, limit int) ([]KlineBar, error) {
|
|
if limit <= 0 {
|
|
limit = 200
|
|
}
|
|
url := fmt.Sprintf("https://fapi.binance.com/fapi/v1/klines?symbol=%s&interval=%s&limit=%d",
|
|
symbol, interval, limit)
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("fetch klines: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var raw [][]interface{}
|
|
if err := json.Unmarshal(body, &raw); err != nil {
|
|
return nil, fmt.Errorf("parse klines: %w", err)
|
|
}
|
|
|
|
bars := make([]KlineBar, 0, len(raw))
|
|
for _, k := range raw {
|
|
if len(k) < 6 {
|
|
continue
|
|
}
|
|
bar := KlineBar{
|
|
Time: int64(k[0].(float64)) / 1000, // ms → seconds for lightweight-charts
|
|
}
|
|
bar.Open, _ = strconv.ParseFloat(k[1].(string), 64)
|
|
bar.High, _ = strconv.ParseFloat(k[2].(string), 64)
|
|
bar.Low, _ = strconv.ParseFloat(k[3].(string), 64)
|
|
bar.Close, _ = strconv.ParseFloat(k[4].(string), 64)
|
|
bar.Volume, _ = strconv.ParseFloat(k[5].(string), 64)
|
|
bars = append(bars, bar)
|
|
}
|
|
return bars, nil
|
|
}
|
|
|
|
// RegisterKlineRoutes adds kline API endpoints to the mux.
|
|
func RegisterKlineRoutes(mux *http.ServeMux) {
|
|
// GET /api/klines?symbol=BTCUSDT&interval=1h&limit=200
|
|
mux.HandleFunc("/api/klines", func(w http.ResponseWriter, r *http.Request) {
|
|
symbol := r.URL.Query().Get("symbol")
|
|
if symbol == "" {
|
|
symbol = "BTCUSDT"
|
|
}
|
|
interval := r.URL.Query().Get("interval")
|
|
if interval == "" {
|
|
interval = "1h"
|
|
}
|
|
limitStr := r.URL.Query().Get("limit")
|
|
limit := 200
|
|
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 {
|
|
limit = l
|
|
}
|
|
if limit > 1000 {
|
|
limit = 1000
|
|
}
|
|
|
|
bars, err := fetchKlines(symbol, interval, limit)
|
|
if err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusBadGateway)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
json.NewEncoder(w).Encode(bars)
|
|
})
|
|
|
|
// GET /api/ticker?symbol=BTCUSDT — current price + 24h change
|
|
mux.HandleFunc("/api/ticker", func(w http.ResponseWriter, r *http.Request) {
|
|
symbol := r.URL.Query().Get("symbol")
|
|
if symbol == "" {
|
|
symbol = "BTCUSDT"
|
|
}
|
|
|
|
url := fmt.Sprintf("https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=%s", symbol)
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Get(url)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadGateway)
|
|
json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Write(body)
|
|
})
|
|
}
|