first commit of restructured project
This commit is contained in:
139
trahn-trade-backend/internal/repository/sr.go
Normal file
139
trahn-trade-backend/internal/repository/sr.go
Normal file
@@ -0,0 +1,139 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user