76 lines
2.3 KiB
Go
76 lines
2.3 KiB
Go
// Package fetch defines the pluggable data-source interface and its
|
|
// implementations (yfinance-equivalent prices, Finnhub, RSS news).
|
|
package fetch
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/ytshih/stonks/internal/config"
|
|
)
|
|
|
|
// Quote is a single point-in-time price observation for one symbol.
|
|
type Quote struct {
|
|
Symbol string // ticker as passed to the fetcher, e.g. "AAPL", "BTC-USD", "GC=F"
|
|
Label string // human-friendly name, e.g. "Apple Inc."
|
|
Price float64
|
|
ChangePercent float64 // percent change over the source's default window (usually 1 day)
|
|
Currency string
|
|
Source string // which fetcher produced this, e.g. "yfinance", "finnhub"
|
|
}
|
|
|
|
// NewsItem is a single headline/article.
|
|
type NewsItem struct {
|
|
Title string
|
|
Source string // publication or fetcher name
|
|
URL string
|
|
Published time.Time
|
|
Summary string
|
|
}
|
|
|
|
// Result is what one fetcher call returns. The orchestrator merges Results
|
|
// from every configured fetcher before handing them to the analyzer.
|
|
type Result struct {
|
|
Quotes []Quote
|
|
News []NewsItem
|
|
}
|
|
|
|
// Merge appends other's quotes and news onto r.
|
|
func (r *Result) Merge(other Result) {
|
|
r.Quotes = append(r.Quotes, other.Quotes...)
|
|
r.News = append(r.News, other.News...)
|
|
}
|
|
|
|
// Fetcher fetches market data and/or news for the given targets.
|
|
// Implementations should return a partial Result plus an error when only
|
|
// some data could be retrieved, rather than nothing at all — the caller
|
|
// decides whether a partial result is still worth reporting.
|
|
type Fetcher interface {
|
|
Name() string
|
|
Fetch(ctx context.Context, targets config.Targets) (Result, error)
|
|
}
|
|
|
|
// Constructor builds a Fetcher from its PluginConfig (name + params).
|
|
type Constructor func(cfg config.PluginConfig) (Fetcher, error)
|
|
|
|
var registry = map[string]Constructor{}
|
|
|
|
// Register makes a fetcher constructor available under name. Called from
|
|
// each fetcher implementation's init().
|
|
func Register(name string, ctor Constructor) {
|
|
if _, exists := registry[name]; exists {
|
|
panic(fmt.Sprintf("fetch: constructor already registered for %q", name))
|
|
}
|
|
registry[name] = ctor
|
|
}
|
|
|
|
// Build looks up the constructor for cfg.Name and invokes it.
|
|
func Build(cfg config.PluginConfig) (Fetcher, error) {
|
|
ctor, ok := registry[cfg.Name]
|
|
if !ok {
|
|
return nil, fmt.Errorf("fetch: no fetcher registered for %q", cfg.Name)
|
|
}
|
|
return ctor(cfg)
|
|
}
|