- Implemented Citations component to display citation data. - Created WatchlistDrawer and ChatSidebar components for managing watchlists and AI chat functionality. - Integrated API calls for watchlist management and chat interactions. - Updated index.tsx to include new components in the main application layout. - Added API client in lib/api.ts for structured API interactions. - Developed Alerts, Dashboard, Portfolio, Routines, Screener, and Report pages with relevant data fetching and UI components. - Introduced styles in tokens.css for consistent theming across the application. - Configured TypeScript and Vite for project setup and development.
90 lines
2.0 KiB
Go
90 lines
2.0 KiB
Go
package store
|
|
|
|
import (
|
|
"sort"
|
|
)
|
|
|
|
// ResolveDue resolves predictions older than 30d against actual return:
|
|
// first stored close on/after predict_date vs latest stored close.
|
|
// BUY hits above +5%, AVOID hits below -5%, HOLD hits within ±10%.
|
|
// Returns the number of newly resolved rows.
|
|
func (db *DB) ResolveDue() (int, error) {
|
|
due, err := db.DueForResolution()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
resolved := 0
|
|
for _, p := range due {
|
|
id, _ := p["id"].(int64)
|
|
ticker, _ := p["ticker"].(string)
|
|
pred, _ := p["prediction"].(string)
|
|
since, _ := p["predict_date"].(string)
|
|
series, err := db.dailyCloses(ticker)
|
|
if err != nil || len(series) < 2 {
|
|
continue
|
|
}
|
|
base, ok := firstOnOrAfter(series, since)
|
|
if !ok {
|
|
continue
|
|
}
|
|
last := series[len(series)-1]
|
|
if base.close <= 0 {
|
|
continue
|
|
}
|
|
ret := (last.close - base.close) / base.close
|
|
hit := false
|
|
switch pred {
|
|
case "BUY":
|
|
hit = ret > 0.05
|
|
case "AVOID":
|
|
hit = ret < -0.05
|
|
default: // HOLD
|
|
hit = ret >= -0.10 && ret <= 0.10
|
|
}
|
|
if err := db.ResolvePrediction(id, hit, ret); err == nil {
|
|
resolved++
|
|
}
|
|
}
|
|
return resolved, nil
|
|
}
|
|
|
|
type closePoint struct {
|
|
date string
|
|
close float64
|
|
}
|
|
|
|
// dailyCloses parses every stored daily payload into a date-sorted series.
|
|
func (db *DB) dailyCloses(ticker string) ([]closePoint, error) {
|
|
rows, err := db.Query(`SELECT payload_json FROM snapshots WHERE ticker=? AND source='daily'`, ticker)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []closePoint
|
|
for rows.Next() {
|
|
var raw string
|
|
if err := rows.Scan(&raw); err != nil {
|
|
return nil, err
|
|
}
|
|
for _, b := range parseDailyBars(raw, "") {
|
|
if b.Date != "" && b.Close > 0 {
|
|
out = append(out, closePoint{b.Date, b.Close})
|
|
}
|
|
}
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
sort.Slice(out, func(i, j int) bool { return out[i].date < out[j].date })
|
|
return out, nil
|
|
}
|
|
|
|
func firstOnOrAfter(series []closePoint, date string) (closePoint, bool) {
|
|
for _, p := range series {
|
|
if p.date >= date {
|
|
return p, true
|
|
}
|
|
}
|
|
return closePoint{}, false
|
|
}
|