up
This commit is contained in:
@@ -118,6 +118,31 @@ 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
|
||||
|
||||
|
||||
@@ -19,13 +19,16 @@ func (s *Server) handleGetSignals(w http.ResponseWriter, r *http.Request) {
|
||||
COALESCE(sig.market_cap, 0), COALESCE(sig.short_ratio, 0),
|
||||
COALESCE(sig.week52_high, 0), COALESCE(sig.week52_low, 0),
|
||||
COALESCE(sig.pct_from_high, 0), COALESCE(sig.insider_value_30d, 0),
|
||||
COALESCE(sig.insider_sell_value_30d, 0), COALESCE(sig.earnings_date, ''),
|
||||
COALESCE(sig.ceo_change, 0),
|
||||
COALESCE(sig.score, 0), COALESCE(sig.on_etoro, 0),
|
||||
COALESCE(sig.alert,''), sig.computed_at
|
||||
FROM signals sig
|
||||
LEFT JOIN instruments inst ON inst.ticker = sig.ticker`
|
||||
|
||||
query += ` WHERE sig.source = 'watchlist'`
|
||||
if onlyEtoro {
|
||||
query += ` WHERE sig.on_etoro = 1`
|
||||
query += ` AND sig.on_etoro = 1`
|
||||
}
|
||||
query += ` ORDER BY sig.score DESC, CASE WHEN sig.alert != '' THEN 0 ELSE 1 END`
|
||||
|
||||
@@ -39,7 +42,7 @@ func (s *Server) handleGetSignals(w http.ResponseWriter, r *http.Request) {
|
||||
signals := []scanner.Signal{}
|
||||
for rows.Next() {
|
||||
var sig scanner.Signal
|
||||
var onEtoro int
|
||||
var onEtoro, ceoChange int
|
||||
if err := rows.Scan(
|
||||
&sig.Ticker, &sig.Name,
|
||||
&sig.Price, &sig.ChangePct,
|
||||
@@ -48,6 +51,8 @@ func (s *Server) handleGetSignals(w http.ResponseWriter, r *http.Request) {
|
||||
&sig.MarketCap, &sig.ShortRatio,
|
||||
&sig.Week52High, &sig.Week52Low,
|
||||
&sig.PctFromHigh, &sig.InsiderValue30d,
|
||||
&sig.InsiderSell30d, &sig.EarningsDate,
|
||||
&ceoChange,
|
||||
&sig.Score, &onEtoro,
|
||||
&sig.Alert, &sig.ComputedAt,
|
||||
); err != nil {
|
||||
@@ -55,6 +60,7 @@ func (s *Server) handleGetSignals(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
sig.OnEtoro = onEtoro == 1
|
||||
sig.CEOChange = ceoChange == 1
|
||||
signals = append(signals, sig)
|
||||
}
|
||||
|
||||
@@ -112,4 +118,3 @@ func (s *Server) handleGetPrices(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(bars)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,11 +60,8 @@ func (s *Server) handleAddWatchlist(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) handleRemoveWatchlist(w http.ResponseWriter, r *http.Request) {
|
||||
ticker := mux.Vars(r)["ticker"]
|
||||
_, err := s.db.Exec(`DELETE FROM watchlist WHERE ticker = ?`, ticker)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
s.db.Exec(`DELETE FROM watchlist WHERE ticker = ?`, ticker)
|
||||
s.db.Exec(`DELETE FROM signals WHERE ticker = ? AND source = 'watchlist'`, ticker)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"status":"removed"}`))
|
||||
|
||||
@@ -46,13 +46,24 @@ func New(database *db.DB, port string) (*Server, error) {
|
||||
|
||||
s.scanner = scanner.New(database)
|
||||
s.scanner.Start()
|
||||
s.edgarPoller = edgar.NewPoller(database)
|
||||
s.edgarPoller.Start()
|
||||
s.scanner.SetEdgar(s.edgarPoller)
|
||||
s.scanner.SetEarnings(s.poller)
|
||||
|
||||
s.discovery = scanner.NewDiscovery(database)
|
||||
|
||||
s.edgarPoller = edgar.NewPoller(database)
|
||||
s.edgarPoller.Start()
|
||||
|
||||
s.etoroPoller = etoro.NewPoller(database)
|
||||
s.etoroPoller = etoro.NewPoller(database, func() (string, string, error) {
|
||||
apiKey, err := svc.Get("etoro_api_key")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
userKey, err := svc.Get("etoro_user_key")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return apiKey, userKey, nil
|
||||
})
|
||||
s.etoroPoller.Start()
|
||||
|
||||
s.setupRoutes()
|
||||
@@ -96,6 +107,8 @@ func (s *Server) setupRoutes() {
|
||||
api.HandleFunc("/discover", s.handleGetDiscovery).Methods("GET", "OPTIONS")
|
||||
api.HandleFunc("/discover/run", s.handleRunDiscovery).Methods("POST", "OPTIONS")
|
||||
api.HandleFunc("/discover/status", s.handleDiscoveryStatus).Methods("GET", "OPTIONS")
|
||||
api.HandleFunc("/discover/analyze", s.handleAnalyzeDeep).Methods("POST", "OPTIONS")
|
||||
api.HandleFunc("/discover/analyze/status", s.handleAnalyzeStatus).Methods("GET", "OPTIONS")
|
||||
|
||||
s.router.PathPrefix("/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "StockRadar API running")
|
||||
@@ -140,6 +153,7 @@ func (s *Server) handleSaveSettings(w http.ResponseWriter, r *http.Request) {
|
||||
// Clés API → chiffrées, reste → plain text
|
||||
encryptedKeys := map[string]bool{
|
||||
"etoro_api_key": true,
|
||||
"etoro_user_key": true,
|
||||
"finnhub_api_key": true,
|
||||
"alphavantage_key": true,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user