package server import ( "database/sql" "encoding/json" "net/http" "git.rouggy.com/rouggy/stockradar/internal/scanner" ) // ---- eToro ---- func (s *Server) handleSyncEtoro(w http.ResponseWriter, r *http.Request) { go func() { s.etoroPoller.Sync() }() w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{"status":"syncing"}`)) } func (s *Server) handleEtoroStatus(w http.ResponseWriter, r *http.Request) { status := s.etoroPoller.Status() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) } // ---- Discovery ---- func (s *Server) handleRunDiscovery(w http.ResponseWriter, r *http.Request) { started := s.discovery.Run() w.Header().Set("Content-Type", "application/json") if started { w.Write([]byte(`{"status":"started"}`)) } else { w.Write([]byte(`{"status":"already_running"}`)) } } func (s *Server) handleDiscoveryStatus(w http.ResponseWriter, r *http.Request) { status := s.discovery.Status() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) } func (s *Server) handleGetDiscovery(w http.ResponseWriter, r *http.Request) { minScore := r.URL.Query().Get("min_score") if minScore == "" { minScore = "30" } rows, err := s.db.Query(` SELECT sig.ticker, COALESCE(inst.name, sig.ticker), sig.price, sig.change_pct, sig.rsi14, sig.macd_hist, sig.volume, sig.avg_volume20, COALESCE(sig.week52_high, 0), COALESCE(sig.pct_from_high, 0), COALESCE(sig.market_cap, 0), COALESCE(sig.score, 0), COALESCE(sig.alert,''), sig.computed_at FROM signals sig LEFT JOIN instruments inst ON inst.ticker = sig.ticker WHERE sig.source = 'discovery' AND sig.on_etoro = 1 AND sig.score >= ? ORDER BY sig.score DESC LIMIT 200 `, minScore) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } defer rows.Close() type discoveryRow struct { Ticker string `json:"ticker"` Name string `json:"name"` Price float64 `json:"price"` ChangePct float64 `json:"change_pct"` RSI14 float64 `json:"rsi14"` MACDHist float64 `json:"macd_hist"` Volume int64 `json:"volume"` AvgVolume20 int64 `json:"avg_volume20"` Week52High float64 `json:"week52_high"` PctFromHigh float64 `json:"pct_from_high"` MarketCap int64 `json:"market_cap"` Score int `json:"score"` Alert string `json:"alert"` ComputedAt string `json:"computed_at"` } results := []discoveryRow{} for rows.Next() { var row discoveryRow var vol sql.NullInt64 var avg sql.NullInt64 if err := rows.Scan( &row.Ticker, &row.Name, &row.Price, &row.ChangePct, &row.RSI14, &row.MACDHist, &vol, &avg, &row.Week52High, &row.PctFromHigh, &row.MarketCap, &row.Score, &row.Alert, &row.ComputedAt, ); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if vol.Valid { row.Volume = vol.Int64 } if avg.Valid { row.AvgVolume20 = avg.Int64 } results = append(results, row) } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(results) } // handleEtoroStats garde la compatibilité avec l'ancien endpoint func (s *Server) handleEtoroStats(w http.ResponseWriter, r *http.Request) { s.handleEtoroStatus(w, r) } func (s *Server) handleAnalyzeDeep(w http.ResponseWriter, r *http.Request) { var body struct { Tickers []string `json:"tickers"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil || len(body.Tickers) == 0 { http.Error(w, "tickers required", http.StatusBadRequest) return } if len(body.Tickers) > 50 { body.Tickers = body.Tickers[:50] // limite de sécurité } w.Header().Set("Content-Type", "application/json") if s.scanner.Analyze(body.Tickers) { w.Write([]byte(`{"status":"started"}`)) } else { w.Write([]byte(`{"status":"already_running"}`)) } } func (s *Server) handleAnalyzeStatus(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(s.scanner.AnalyzeStatus()) } // Scan watchlist signal - déjà dans handlers_scanner.go, on ajoute juste // un champ source à la query existante func signalFromRow(rows interface { Scan(...any) error }) (scanner.Signal, int, error) { var sig scanner.Signal var onEtoro int err := rows.Scan( &sig.Ticker, &sig.Name, &sig.Price, &sig.ChangePct, &sig.RSI14, &sig.MACD, &sig.MACDSignal, &sig.MACDHist, &sig.SMA20, &sig.SMA50, &sig.Volume, &sig.AvgVolume20, &sig.MarketCap, &sig.ShortRatio, &sig.Week52High, &sig.Week52Low, &sig.PctFromHigh, &sig.InsiderValue30d, &sig.Score, &onEtoro, &sig.Alert, &sig.ComputedAt, ) return sig, onEtoro, err }