140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"github.com/kjannette/trahn-backend/internal/models"
|
|
)
|
|
|
|
type SRRepo struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
func NewSRRepo(pool *pgxpool.Pool) *SRRepo {
|
|
return &SRRepo{pool: pool}
|
|
}
|
|
|
|
func (r *SRRepo) Record(ctx context.Context, sr *models.SupportResistance) (*models.SupportResistance, error) {
|
|
ts := sr.Timestamp
|
|
if ts.IsZero() {
|
|
ts = time.Now()
|
|
}
|
|
row := r.pool.QueryRow(ctx,
|
|
`INSERT INTO support_resistance_history
|
|
(timestamp, method, lookback_days, support, resistance, midpoint, avg_price, grid_recalculated)
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
|
|
RETURNING *`,
|
|
ts, sr.Method, sr.LookbackDays, sr.Support, sr.Resistance,
|
|
sr.Midpoint, sr.AvgPrice, sr.GridRecalculated,
|
|
)
|
|
return scanSR(row)
|
|
}
|
|
|
|
func (r *SRRepo) GetLatest(ctx context.Context) (*models.SupportResistance, error) {
|
|
row := r.pool.QueryRow(ctx,
|
|
`SELECT * FROM support_resistance_history ORDER BY timestamp DESC LIMIT 1`,
|
|
)
|
|
sr, err := scanSR(row)
|
|
if err != nil {
|
|
if err.Error() == "no rows in result set" {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return sr, nil
|
|
}
|
|
|
|
func (r *SRRepo) GetHistory(ctx context.Context, limit int) ([]models.SupportResistance, error) {
|
|
rows, err := r.pool.Query(ctx,
|
|
`SELECT * FROM support_resistance_history ORDER BY timestamp DESC LIMIT $1`,
|
|
limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
return collectSRs(rows)
|
|
}
|
|
|
|
func (r *SRRepo) NeedsRefresh(ctx context.Context, refreshHours int) (bool, error) {
|
|
latest, err := r.GetLatest(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if latest == nil {
|
|
return true, nil
|
|
}
|
|
age := time.Since(latest.Timestamp)
|
|
return age >= time.Duration(refreshHours)*time.Hour, nil
|
|
}
|
|
|
|
type ChangeAnalysis struct {
|
|
HasChanged bool `json:"hasChanged"`
|
|
ChangePercent *float64 `json:"changePercent"`
|
|
Previous *models.SupportResistance `json:"previous"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
func (r *SRRepo) CheckSignificantChange(ctx context.Context, newSR *models.SupportResistance, thresholdPercent float64) (*ChangeAnalysis, error) {
|
|
previous, err := r.GetLatest(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if previous == nil {
|
|
return &ChangeAnalysis{
|
|
HasChanged: true,
|
|
Reason: "First S/R fetch",
|
|
}, nil
|
|
}
|
|
|
|
pct := math.Abs((newSR.Midpoint - previous.Midpoint) / previous.Midpoint * 100)
|
|
changed := pct >= thresholdPercent
|
|
|
|
reason := "S/R stable"
|
|
if changed {
|
|
reason = fmt.Sprintf("Midpoint changed %.2f%%", pct)
|
|
}
|
|
|
|
return &ChangeAnalysis{
|
|
HasChanged: changed,
|
|
ChangePercent: &pct,
|
|
Previous: previous,
|
|
Reason: reason,
|
|
}, nil
|
|
}
|
|
|
|
// --- scan helpers ---
|
|
|
|
func scanSR(row scannable) (*models.SupportResistance, error) {
|
|
var sr models.SupportResistance
|
|
err := row.Scan(
|
|
&sr.ID, &sr.Timestamp, &sr.Method, &sr.LookbackDays,
|
|
&sr.Support, &sr.Resistance, &sr.Midpoint, &sr.AvgPrice,
|
|
&sr.GridRecalculated, &sr.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &sr, nil
|
|
}
|
|
|
|
func collectSRs(rows rowsIter) ([]models.SupportResistance, error) {
|
|
var out []models.SupportResistance
|
|
for rows.Next() {
|
|
var sr models.SupportResistance
|
|
if err := rows.Scan(
|
|
&sr.ID, &sr.Timestamp, &sr.Method, &sr.LookbackDays,
|
|
&sr.Support, &sr.Resistance, &sr.Midpoint, &sr.AvgPrice,
|
|
&sr.GridRecalculated, &sr.CreatedAt,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, sr)
|
|
}
|
|
return out, rows.Err()
|
|
}
|