165 lines
5.6 KiB
Go
165 lines
5.6 KiB
Go
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
|
|
_ "modernc.org/sqlite" // à la place de go-sqlite3
|
|
)
|
|
|
|
type DB struct {
|
|
*sql.DB
|
|
}
|
|
|
|
func Init(path string) (*DB, error) {
|
|
sqldb, err := sql.Open("sqlite", path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Une seule connexion — évite les SQLITE_BUSY entre goroutines concurrentes
|
|
sqldb.SetMaxOpenConns(1)
|
|
sqldb.SetMaxIdleConns(1)
|
|
|
|
if err := sqldb.Ping(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// WAL : lectures non bloquantes + timeout 10s avant BUSY
|
|
sqldb.Exec("PRAGMA journal_mode=WAL")
|
|
sqldb.Exec("PRAGMA busy_timeout=10000")
|
|
|
|
database := &DB{sqldb}
|
|
if err := database.migrate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return database, nil
|
|
}
|
|
|
|
func (db *DB) migrate() error {
|
|
queries := []string{
|
|
`CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
encrypted INTEGER DEFAULT 0,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS watchlist (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ticker TEXT NOT NULL UNIQUE,
|
|
name TEXT,
|
|
sector TEXT,
|
|
exchange TEXT,
|
|
active INTEGER DEFAULT 1,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS instruments (
|
|
instrument_id INTEGER PRIMARY KEY,
|
|
ticker TEXT NOT NULL,
|
|
name TEXT,
|
|
sector_id INTEGER,
|
|
exchange_id INTEGER,
|
|
asset_class_id INTEGER,
|
|
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS news (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ticker TEXT,
|
|
headline TEXT NOT NULL,
|
|
source TEXT,
|
|
url TEXT,
|
|
sentiment TEXT,
|
|
published_at DATETIME,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS prices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ticker TEXT NOT NULL,
|
|
date DATE NOT NULL,
|
|
open REAL,
|
|
high REAL,
|
|
low REAL,
|
|
close REAL,
|
|
volume INTEGER,
|
|
UNIQUE(ticker, date)
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS signals (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ticker TEXT NOT NULL UNIQUE,
|
|
price REAL,
|
|
change_pct REAL,
|
|
rsi14 REAL,
|
|
macd REAL,
|
|
macd_signal REAL,
|
|
macd_hist REAL,
|
|
sma20 REAL,
|
|
sma50 REAL,
|
|
volume INTEGER,
|
|
avg_volume20 INTEGER,
|
|
alert TEXT DEFAULT '',
|
|
computed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE TABLE IF NOT EXISTS insider_trades (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ticker TEXT NOT NULL,
|
|
insider_name TEXT,
|
|
insider_title TEXT,
|
|
transaction_code TEXT,
|
|
shares REAL,
|
|
price REAL,
|
|
total_value REAL,
|
|
transaction_date DATE,
|
|
accession_no TEXT UNIQUE,
|
|
filing_url TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_insider_ticker ON insider_trades(ticker)`,
|
|
}
|
|
|
|
for _, q := range queries {
|
|
if _, err := db.Exec(q); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Nettoyage : supprime les signaux watchlist pour les tickers retirés de la watchlist
|
|
db.Exec(`DELETE FROM signals WHERE source='watchlist' AND ticker NOT IN (SELECT ticker FROM watchlist WHERE active=1)`)
|
|
|
|
// Migrations additives — on ignore les erreurs si la colonne/index existe déjà
|
|
additive := []string{
|
|
`ALTER TABLE news ADD COLUMN finnhub_id INTEGER`,
|
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_news_finnhub_id ON news(finnhub_id) WHERE finnhub_id IS NOT NULL`,
|
|
`ALTER TABLE signals ADD COLUMN market_cap INTEGER DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN short_ratio REAL DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN score INTEGER DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN on_etoro INTEGER DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN week52_high REAL DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN week52_low REAL DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN pct_from_high REAL DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN insider_value_30d REAL DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN source TEXT DEFAULT 'watchlist'`,
|
|
`CREATE INDEX IF NOT EXISTS idx_instruments_ticker ON instruments(ticker)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_signals_score ON signals(score DESC)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_signals_source ON signals(source)`,
|
|
`ALTER TABLE signals ADD COLUMN insider_sell_value_30d REAL DEFAULT 0`,
|
|
`ALTER TABLE signals ADD COLUMN earnings_date TEXT DEFAULT ''`,
|
|
`CREATE TABLE IF NOT EXISTS company_events (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
ticker TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
title TEXT,
|
|
accession_no TEXT UNIQUE,
|
|
filing_date DATE,
|
|
filing_url TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_company_events_ticker ON company_events(ticker)`,
|
|
`ALTER TABLE signals ADD COLUMN ceo_change INTEGER DEFAULT 0`,
|
|
}
|
|
for _, q := range additive {
|
|
db.Exec(q) // intentionnellement sans vérification d'erreur
|
|
}
|
|
|
|
return nil
|
|
}
|