add correct backfile
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
f862abe5a4
commit
28e082f0fc
8 changed files with 317 additions and 35 deletions
|
|
@ -9,16 +9,28 @@ import (
|
|||
"git.g3e.fr/H6N/account/internal/store"
|
||||
)
|
||||
|
||||
// PriceBackfiller est implémenté par le pipeline pour récupérer l'historique de prix manquant.
|
||||
type PriceBackfiller interface {
|
||||
BackfillInstrument(ctx context.Context, instrumentID int32, from, to time.Time) error
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
store *store.Store
|
||||
logger *slog.Logger
|
||||
horizonDays int
|
||||
backfiller PriceBackfiller
|
||||
}
|
||||
|
||||
func New(st *store.Store, logger *slog.Logger, horizonDays int) *Engine {
|
||||
return &Engine{store: st, logger: logger, horizonDays: horizonDays}
|
||||
}
|
||||
|
||||
// WithBackfiller active le backfill automatique des prix historiques manquants.
|
||||
func (e *Engine) WithBackfiller(b PriceBackfiller) *Engine {
|
||||
e.backfiller = b
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Engine) Store() *store.Store { return e.store }
|
||||
|
||||
// RecomputeDay calcule les snapshots de tous les comptes pour une date donnée.
|
||||
|
|
@ -126,8 +138,11 @@ func (e *Engine) backfillAccount(ctx context.Context, accountID int32, from, to
|
|||
return fmt.Errorf("from (%s) after to (%s)", from.Format("2006-01-02"), to.Format("2006-01-02"))
|
||||
}
|
||||
|
||||
// backfilledInstruments évite de re-fetcher le même instrument plusieurs fois dans ce run.
|
||||
backfilledInstruments := make(map[int32]bool)
|
||||
|
||||
for d := from; !d.After(to); d = d.AddDate(0, 0, 1) {
|
||||
if err := e.recomputeAccount(ctx, accountID, d); err != nil {
|
||||
if err := e.recomputeAccountWithBackfill(ctx, accountID, d, backfilledInstruments); err != nil {
|
||||
return fmt.Errorf("recompute %s: %w", d.Format("2006-01-02"), err)
|
||||
}
|
||||
}
|
||||
|
|
@ -135,11 +150,20 @@ func (e *Engine) backfillAccount(ctx context.Context, accountID int32, from, to
|
|||
}
|
||||
|
||||
func (e *Engine) recomputeAccount(ctx context.Context, accountID int32, date time.Time) error {
|
||||
return e.recomputeAccountWithBackfill(ctx, accountID, date, nil)
|
||||
}
|
||||
|
||||
func (e *Engine) recomputeAccountWithBackfill(ctx context.Context, accountID int32, date time.Time, backfilled map[int32]bool) error {
|
||||
baseDate, hasBase, err := e.store.GetLatestSnapshotDate(ctx, accountID, date)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get latest snapshot date: %w", err)
|
||||
}
|
||||
|
||||
prus, err := e.store.ComputePRUs(ctx, accountID, date)
|
||||
if err != nil {
|
||||
return fmt.Errorf("compute PRUs: %w", err)
|
||||
}
|
||||
|
||||
positions := map[int32]store.PositionRow{}
|
||||
if hasBase {
|
||||
base, err := e.store.GetBasePositions(ctx, accountID, baseDate)
|
||||
|
|
@ -175,30 +199,53 @@ func (e *Engine) recomputeAccount(ctx context.Context, accountID int32, date tim
|
|||
if err != nil {
|
||||
return fmt.Errorf("get price instrument %d: %w", pos.InstrumentID, err)
|
||||
}
|
||||
if !found {
|
||||
if pos.InstrumentType == "devise" {
|
||||
prix = 1.0
|
||||
} else {
|
||||
e.logger.Warn("snapshot: no price, position skipped",
|
||||
"account_id", accountID,
|
||||
"instrument_id", pos.InstrumentID,
|
||||
"date", date.Format("2006-01-02"),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if !found && pos.InstrumentType == "devise" {
|
||||
prix = 1.0
|
||||
found = true
|
||||
}
|
||||
|
||||
valeur := pos.Quantite * prix
|
||||
totalValeur += valeur
|
||||
// Backfill automatique si prix manquant et backfiller disponible.
|
||||
// from = date - 1 jour (marge timezone), to = maintenant (inclut aujourd'hui).
|
||||
if !found && e.backfiller != nil && (backfilled == nil || !backfilled[pos.InstrumentID]) {
|
||||
from := date.AddDate(0, 0, -1)
|
||||
now := time.Now().UTC()
|
||||
if err := e.backfiller.BackfillInstrument(ctx, pos.InstrumentID, from, now); err != nil {
|
||||
e.logger.Warn("snapshot: backfill échoué",
|
||||
"instrument_id", pos.InstrumentID, "error", err)
|
||||
} else {
|
||||
if backfilled != nil {
|
||||
backfilled[pos.InstrumentID] = true
|
||||
}
|
||||
// Retry après backfill
|
||||
prix, found, err = e.store.GetPriceAt(ctx, pos.InstrumentID, date)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get price instrument %d (post-backfill): %w", pos.InstrumentID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
snap := store.PositionSnapshot{
|
||||
Date: date,
|
||||
AccountID: accountID,
|
||||
InstrumentID: pos.InstrumentID,
|
||||
Quantite: pos.Quantite,
|
||||
PrixCloture: &prix,
|
||||
Valeur: &valeur,
|
||||
}
|
||||
if pru, ok := prus[pos.InstrumentID]; ok {
|
||||
snap.PRU = &pru
|
||||
}
|
||||
if found {
|
||||
valeur := pos.Quantite * prix
|
||||
snap.PrixCloture = &prix
|
||||
snap.Valeur = &valeur
|
||||
totalValeur += valeur
|
||||
} else {
|
||||
e.logger.Warn("snapshot: no price, valeur non calculée",
|
||||
"account_id", accountID,
|
||||
"instrument_id", pos.InstrumentID,
|
||||
"date", date.Format("2006-01-02"),
|
||||
)
|
||||
}
|
||||
|
||||
if err := e.store.UpsertPositionSnapshot(ctx, snap); err != nil {
|
||||
return fmt.Errorf("upsert position snapshot: %w", err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue