142 lines
3.9 KiB
Go
142 lines
3.9 KiB
Go
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)
|
|
}
|
|
|
|
// 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
|
|
}
|