Files
asepharyana 8c184ccae1 feat: add Citations and WatchlistChat components, integrate with API
- 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.
2026-09-15 12:36:48 +07:00

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
}