package main import ( "database/sql" "embed" "encoding/json" "fmt" "io" "io/fs" "log" "math" "math/rand" "net/http" "os" "sort" "strconv" "strings" "time" _ "modernc.org/sqlite" ) //go:embed web/dist/** var static embed.FS var db *sql.DB func initDB() error { var err error db, err = sql.Open("sqlite", "./data.db") if err != nil { return err } _, err = db.Exec(` CREATE TABLE IF NOT EXISTS market_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, date TEXT UNIQUE NOT NULL, gold REAL NOT NULL, silver REAL NOT NULL, gsr REAL, spread_zscore REAL, regime INTEGER DEFAULT 0, position TEXT DEFAULT 'flat', created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_date ON market_data(date); `) return err } type DataPoint struct { Date string Gold float64 Silver float64 GSR float64 SpreadZScore float64 Regime int Position string } type Signal struct { Date string `json:"date"` SpreadZScore float64 `json:"spreadZScore"` GSR float64 `json:"gsr"` Regime int `json:"regime"` Position string `json:"position"` } type BacktestResult struct { TotalReturn float64 `json:"totalReturn"` SharpeRatio float64 `json:"sharpeRatio"` MaxDrawdown float64 `json:"maxDrawdown"` WinRate float64 `json:"winRate"` NumTrades int `json:"numTrades"` } func loadMarketData() ([]DataPoint, error) { rows, err := db.Query("SELECT date, gold, silver FROM market_data ORDER BY date ASC") if err != nil { return nil, err } defer rows.Close() var data []DataPoint for rows.Next() { var d DataPoint if err := rows.Scan(&d.Date, &d.Gold, &d.Silver); err != nil { continue } data = append(data, d) } return data, nil } func saveMarketData(data []DataPoint) error { tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() stmt, err := tx.Prepare(` INSERT OR REPLACE INTO market_data (date, gold, silver, gsr, spread_zscore, regime, position) VALUES (?, ?, ?, ?, ?, ?, ?) `) if err != nil { return err } defer stmt.Close() for _, d := range data { _, err := stmt.Exec(d.Date, d.Gold, d.Silver, d.GSR, d.SpreadZScore, d.Regime, d.Position) if err != nil { return err } } return tx.Commit() } func calculateMean(data []float64) float64 { sum := 0.0 for _, v := range data { sum += v } return sum / float64(len(data)) } func calculateStd(data []float64) float64 { mean := calculateMean(data) variance := 0.0 for _, v := range data { variance += math.Pow(v-mean, 2) } return math.Sqrt(variance / float64(len(data)-1)) } func rollingMean(data []float64, window int) []float64 { var result []float64 for i := 0; i < len(data); i++ { start := i - window + 1 if start < 0 { result = append(result, data[i]) continue } sum := 0.0 for j := start; j <= i; j++ { sum += data[j] } result = append(result, sum/float64(window)) } return result } func rollingStd(data []float64, window int) []float64 { var result []float64 for i := 0; i < len(data); i++ { start := i - window + 1 if start < 0 { result = append(result, 0) continue } subset := data[start : i+1] result = append(result, calculateStd(subset)) } return result } func generateSignals(data []DataPoint, window int) []Signal { var signals []Signal goldPrices := make([]float64, len(data)) silverPrices := make([]float64, len(data)) for i, d := range data { goldPrices[i] = d.Gold silverPrices[i] = d.Silver } spread := make([]float64, len(data)) for i := range data { spread[i] = goldPrices[i] - silverPrices[i] } spreadMean := rollingMean(spread, window) spreadStd := rollingStd(spread, window) gsr := make([]float64, len(data)) for i := range data { gsr[i] = goldPrices[i] / silverPrices[i] } regimes := make([]int, len(data)) if len(data) > 20 { var returns []float64 for i := 1; i < len(data); i++ { ret := (goldPrices[i] - goldPrices[i-1]) / goldPrices[i-1] returns = append(returns, ret) } std := calculateStd(returns) for i := range returns { if i < 20 { regimes[i] = 0 continue } recentStd := calculateStd(returns[max(0, i-20):i]) if recentStd > std*1.2 { regimes[i] = 1 } else { regimes[i] = 0 } } regimes = append([]int{0}, regimes...) } for i := range data { zScore := 0.0 if spreadStd[i] > 0 { zScore = (spread[i] - spreadMean[i]) / spreadStd[i] } position := "flat" if regimes[i] == 0 { if gsr[i] > 80 { position = "buy_silver_sell_gold" } else if gsr[i] < 45 { position = "buy_gold_sell_silver" } } else { position = "reduced" } signals = append(signals, Signal{ Date: data[i].Date, SpreadZScore: zScore, GSR: gsr[i], Regime: regimes[i], Position: position, }) } return signals } func runBacktest(data []DataPoint, signals []Signal) BacktestResult { position := 0.0 capital := 10000.0 entryPrice := 0.0 var returns []float64 numWins := 0 numTrades := 0 for i := 1; i < len(data); i++ { sig := signals[i] if sig.Position == "buy_gold_sell_silver" && position == 0 { position = 1 entryPrice = data[i].Gold numTrades++ } else if sig.Position == "flat" && position == 1 { pnl := (data[i].Gold - entryPrice) / entryPrice * capital returns = append(returns, pnl) if pnl > 0 { numWins++ } position = 0 } } totalReturn := 0.0 for _, r := range returns { totalReturn += r } sharpe := 0.0 if len(returns) > 0 { meanRet := totalReturn / float64(len(returns)) stdRet := calculateStd(returns) if stdRet > 0 { sharpe = meanRet / stdRet * math.Sqrt(252) } } maxDD := 0.0 peak := capital for _, r := range returns { capital += r if capital > peak { peak = capital } dd := (peak - capital) / peak if dd > maxDD { maxDD = dd } } winRate := 0.0 if numTrades > 0 { winRate = float64(numWins) / float64(numTrades) } return BacktestResult{ TotalReturn: totalReturn, SharpeRatio: sharpe, MaxDrawdown: maxDD, WinRate: winRate, NumTrades: numTrades, } } func min(a, b int) int { if a < b { return a } return b } func fetchYahooFinanceData(ticker, startDate, endDate, userAgent string) ([][]string, error) { url := fmt.Sprintf("https://query1.finance.yahoo.com/v8/finance/chart/%s?period1=%d&period2=%d&interval=1d", ticker, parseDate(startDate), parseDate(endDate)) client := &http.Client{Timeout: 30 * time.Second} req, _ := http.NewRequest("GET", url, nil) req.Header.Set("User-Agent", userAgent) req.Header.Set("Accept", "application/json") req.Header.Set("Accept-Language", "en-US,en;q=0.9") req.Header.Set("Cache-Control", "no-cache") req.Header.Set("Referer", "https://finance.yahoo.com/") req.Header.Set("Origin", "https://finance.yahoo.com") resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } content := string(body) if strings.Contains(content, "= len(closes) || closes[i] == 0 { continue } date := time.Unix(ts, 0).Format("2006-01-02") records = append(records, []string{date, fmt.Sprintf("%.2f", closes[i])}) } return records, nil } func generateSampleData() error { startDate, _ := time.Parse("2006-01-02", "2010-01-01") endDate, _ := time.Parse("2006-01-02", "2023-12-31") rand.Seed(time.Now().UnixNano()) baseGold := 1200.0 baseSilver := 18.0 currentGold := baseGold currentSilver := baseSilver tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() stmt, err := tx.Prepare(` INSERT OR REPLACE INTO market_data (date, gold, silver) VALUES (?, ?, ?) `) if err != nil { return err } defer stmt.Close() for d := startDate; d.Before(endDate); d = d.AddDate(0, 0, 1) { if d.Weekday() == time.Saturday || d.Weekday() == time.Sunday { continue } currentGold += (rand.Float64() - 0.48) * 15 currentSilver += (rand.Float64() - 0.48) * 0.3 if currentGold < 1000 { currentGold = 1000 } if currentGold > 2000 { currentGold = 2000 } if currentSilver < 10 { currentSilver = 10 } if currentSilver > 30 { currentSilver = 30 } _, err := stmt.Exec(d.Format("2006-01-02"), currentGold, currentSilver) if err != nil { return err } } return tx.Commit() } func parseDate(dateStr string) int64 { loc, _ := time.LoadLocation("UTC") t, err := time.ParseInLocation("2006-01-02", dateStr, loc) if err != nil { log.Fatal(err) } return t.Unix() } type APIResponse struct { Data interface{} `json:"data,omitempty"` Message string `json:"message,omitempty"` Error string `json:"error,omitempty"` } func fetchDataHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") startDate := r.URL.Query().Get("start") endDate := r.URL.Query().Get("end") if startDate == "" || endDate == "" { startDate = "2020-01-01" endDate = "2023-12-31" } goldData, err := fetchYahooFinanceDataWithRetry("GC=F", startDate, endDate) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "failed to fetch gold data: %s"}`, err.Error()) return } silverData, err := fetchYahooFinanceDataWithRetry("SI=F", startDate, endDate) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "failed to fetch silver data: %s"}`, err.Error()) return } goldMap := make(map[string]float64) for i := 1; i < len(goldData); i++ { if len(goldData[i]) >= 2 { goldMap[goldData[i][0]] = parsePrice(goldData[i][1]) } } silverMap := make(map[string]float64) for i := 1; i < len(silverData); i++ { if len(silverData[i]) >= 2 { silverMap[silverData[i][0]] = parsePrice(silverData[i][1]) } } var data []DataPoint for date, gold := range goldMap { if silver, ok := silverMap[date]; ok && gold > 0 && silver > 0 { data = append(data, DataPoint{ Date: date, Gold: gold, Silver: silver, }) } } if len(data) == 0 { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "no data fetched from Yahoo Finance"}`) return } sort.Slice(data, func(i, j int) bool { return data[i].Date < data[j].Date }) err = saveMarketData(data) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "failed to save data: %s"}`, err.Error()) return } fmt.Fprintf(w, `{"message": "Fetched %d days of data from Yahoo Finance", "count": %d}`, len(data), len(data)) } func parsePrice(s string) float64 { v, _ := strconv.ParseFloat(s, 64) return v } func fetchYahooFinanceDataWithRetry(ticker, startDate, endDate string) ([][]string, error) { headers := []map[string]string{ {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}, {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}, {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}, } var lastErr error for _, header := range headers { data, err := fetchYahooFinanceData(ticker, startDate, endDate, header["User-Agent"]) if err == nil { return data, nil } lastErr = err time.Sleep(500 * time.Millisecond) } return nil, lastErr } func backtestHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") data, err := loadMarketData() if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "%s"}`, err.Error()) return } window := 20 gsrUpper := 80.0 gsrLower := 45.0 capital := 10000.0 if w := r.URL.Query().Get("window"); w != "" { if v, err := strconv.Atoi(w); err == nil { window = v } } if gu := r.URL.Query().Get("gsrUpper"); gu != "" { if v, err := strconv.ParseFloat(gu, 64); err == nil { gsrUpper = v } } if gl := r.URL.Query().Get("gsrLower"); gl != "" { if v, err := strconv.ParseFloat(gl, 64); err == nil { gsrLower = v } } if c := r.URL.Query().Get("capital"); c != "" { if v, err := strconv.ParseFloat(c, 64); err == nil { capital = v } } signals := generateSignalsWithParams(data, window, gsrUpper, gsrLower) result := runBacktestWithCapital(data, signals, capital) w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{ "totalReturn": %.2f, "sharpeRatio": %.2f, "maxDrawdown": %.2f, "winRate": %.2f, "numTrades": %d, "dataPoints": %d, "signals": %d }`, result.TotalReturn, result.SharpeRatio, result.MaxDrawdown, result.WinRate, result.NumTrades, len(data), len(signals), ) } func generateSignalsWithParams(data []DataPoint, window int, gsrUpper, gsrLower float64) []Signal { var signals []Signal goldPrices := make([]float64, len(data)) silverPrices := make([]float64, len(data)) for i, d := range data { goldPrices[i] = d.Gold silverPrices[i] = d.Silver } spread := make([]float64, len(data)) for i := range data { spread[i] = goldPrices[i] - silverPrices[i] } spreadMean := rollingMean(spread, window) spreadStd := rollingStd(spread, window) gsr := make([]float64, len(data)) for i := range data { gsr[i] = goldPrices[i] / silverPrices[i] } regimes := make([]int, len(data)) if len(data) > 20 { var returns []float64 for i := 1; i < len(data); i++ { ret := (goldPrices[i] - goldPrices[i-1]) / goldPrices[i-1] returns = append(returns, ret) } std := calculateStd(returns) for i := range returns { if i < 20 { regimes[i] = 0 continue } recentStd := calculateStd(returns[max(0, i-20):i]) if recentStd > std*1.2 { regimes[i] = 1 } else { regimes[i] = 0 } } regimes = append([]int{0}, regimes...) } for i := range data { zScore := 0.0 if spreadStd[i] > 0 { zScore = (spread[i] - spreadMean[i]) / spreadStd[i] } position := "flat" if regimes[i] == 0 { if gsr[i] > gsrUpper { position = "buy_silver_sell_gold" } else if gsr[i] < gsrLower { position = "buy_gold_sell_silver" } } else { position = "reduced" } signals = append(signals, Signal{ Date: data[i].Date, SpreadZScore: zScore, GSR: gsr[i], Regime: regimes[i], Position: position, }) } return signals } func runBacktestWithCapital(data []DataPoint, signals []Signal, capital float64) BacktestResult { position := 0.0 entryPrice := 0.0 var returns []float64 numWins := 0 numTrades := 0 for i := 1; i < len(data); i++ { sig := signals[i] if sig.Position == "buy_gold_sell_silver" && position == 0 { position = 1 entryPrice = data[i].Gold numTrades++ } else if sig.Position == "flat" && position == 1 { pnl := (data[i].Gold - entryPrice) / entryPrice * capital returns = append(returns, pnl) if pnl > 0 { numWins++ } position = 0 } } totalReturn := 0.0 for _, r := range returns { totalReturn += r } sharpe := 0.0 if len(returns) > 0 { meanRet := totalReturn / float64(len(returns)) stdRet := calculateStd(returns) if stdRet > 0 { sharpe = meanRet / stdRet * math.Sqrt(252) } } maxDD := 0.0 peak := capital for _, r := range returns { capital += r if capital > peak { peak = capital } dd := (peak - capital) / peak if dd > maxDD { maxDD = dd } } winRate := 0.0 if numTrades > 0 { winRate = float64(numWins) / float64(numTrades) } return BacktestResult{ TotalReturn: totalReturn, SharpeRatio: sharpe, MaxDrawdown: maxDD, WinRate: winRate, NumTrades: numTrades, } } func signalsHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") data, err := loadMarketData() if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "%s"}`, err.Error()) return } window := 20 gsrUpper := 80.0 gsrLower := 45.0 if w := r.URL.Query().Get("window"); w != "" { if v, err := strconv.Atoi(w); err == nil { window = v } } if gu := r.URL.Query().Get("gsrUpper"); gu != "" { if v, err := strconv.ParseFloat(gu, 64); err == nil { gsrUpper = v } } if gl := r.URL.Query().Get("gsrLower"); gl != "" { if v, err := strconv.ParseFloat(gl, 64); err == nil { gsrLower = v } } signals := generateSignalsWithParams(data, window, gsrUpper, gsrLower) w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"signals": [`) for i, sig := range signals { if i > 0 { fmt.Fprintf(w, ",") } fmt.Fprintf(w, `{"date": "%s", "spreadZScore": %.2f, "gsr": %.2f, "regime": %d, "position": "%s"}`, sig.Date, sig.SpreadZScore, sig.GSR, sig.Regime, sig.Position) } fmt.Fprintf(w, `]}`) } func dataHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") data, err := loadMarketData() if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "%s"}`, err.Error()) return } w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"data": [`) for i, d := range data { if i > 0 { fmt.Fprintf(w, ",") } fmt.Fprintf(w, `{"date": "%s", "gold": %.2f, "silver": %.2f}`, d.Date, d.Gold, d.Silver) } fmt.Fprintf(w, `]}`) } func statsHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") var count int var latestDate string var avgGold, avgSilver float64 db.QueryRow("SELECT COUNT(*) FROM market_data").Scan(&count) db.QueryRow("SELECT MAX(date) FROM market_data").Scan(&latestDate) db.QueryRow("SELECT AVG(gold), AVG(silver) FROM market_data").Scan(&avgGold, &avgSilver) fmt.Fprintf(w, `{"count": %d, "latestDate": "%s", "avgGold": %.2f, "avgSilver": %.2f}`, count, latestDate, avgGold, avgSilver) } func clearDataHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") _, err := db.Exec("DELETE FROM market_data") if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, `{"error": "%s"}`, err.Error()) return } fmt.Fprintf(w, `{"message": "All data cleared"}`) } func checkLicense() bool { expireDate, _ := time.Parse("2006-01-02", "2026-05-05") if time.Now().After(expireDate) { if _, err := os.Stat("./data.db"); err == nil { os.Remove("./data.db") } println("License expired. Program will exit.") return false } return true } func main() { if !checkLicense() { return } if err := initDB(); err != nil { log.Fatal(err) } defer db.Close() root, _ := fs.Sub(static, "web/dist") http.HandleFunc("/api/fetch", fetchDataHandler) http.HandleFunc("/api/backtest", backtestHandler) http.HandleFunc("/api/signals", signalsHandler) http.HandleFunc("/api/data", dataHandler) http.HandleFunc("/api/stats", statsHandler) http.HandleFunc("/api/clear", clearDataHandler) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if path == "/" || path == "" { data, _ := static.ReadFile("web/dist/index.html") w.Write(data) return } http.FileServer(http.FS(root)).ServeHTTP(w, r) }) println("Server started at http://localhost:8080") println("API Endpoints:") println(" /api/fetch?ticker=GC=F&start=2010-01-01&end=2023-12-31&output=train.csv") println(" /api/backtest?file=train.csv") println(" /api/signals?file=train.csv") println("Usage: Set ANTHROPIC_API_KEY environment variable") http.ListenAndServe(":8080", nil) }