203 lines
5.1 KiB
Go
203 lines
5.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"github.com/kjannette/trahn-backend/internal/models"
|
|
)
|
|
|
|
type GridStateRepo struct {
|
|
pool *pgxpool.Pool
|
|
}
|
|
|
|
func NewGridStateRepo(pool *pgxpool.Pool) *GridStateRepo {
|
|
return &GridStateRepo{pool: pool}
|
|
}
|
|
|
|
func (r *GridStateRepo) GetActive(ctx context.Context) (*models.GridState, error) {
|
|
row := r.pool.QueryRow(ctx,
|
|
`SELECT * FROM grid_state WHERE is_active = true ORDER BY updated_at DESC LIMIT 1`,
|
|
)
|
|
gs, err := scanGridState(row)
|
|
if err != nil {
|
|
if err.Error() == "no rows in result set" {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return gs, nil
|
|
}
|
|
|
|
func (r *GridStateRepo) Save(ctx context.Context, data *models.GridState) (*models.GridState, error) {
|
|
tx, err := r.pool.Begin(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
_, err = tx.Exec(ctx, `UPDATE grid_state SET is_active = false WHERE is_active = true`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
row := tx.QueryRow(ctx,
|
|
`INSERT INTO grid_state
|
|
(base_price, grid_levels_json, trades_executed, total_profit,
|
|
last_sr_refresh, is_active, updated_at)
|
|
VALUES ($1,$2,$3,$4,$5,true,NOW())
|
|
RETURNING *`,
|
|
data.BasePrice,
|
|
data.GridLevelsJSON,
|
|
data.TradesExecuted,
|
|
data.TotalProfit,
|
|
data.LastSRRefresh,
|
|
)
|
|
gs, err := scanGridState(row)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return gs, nil
|
|
}
|
|
|
|
func (r *GridStateRepo) UpdateGridLevels(ctx context.Context, levels json.RawMessage) error {
|
|
_, err := r.pool.Exec(ctx,
|
|
`UPDATE grid_state SET grid_levels_json = $1, updated_at = NOW() WHERE is_active = true`,
|
|
levels,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (r *GridStateRepo) UpdateTradeStats(ctx context.Context, tradesExecuted int, totalProfit float64) error {
|
|
_, err := r.pool.Exec(ctx,
|
|
`UPDATE grid_state SET trades_executed = $1, total_profit = $2, updated_at = NOW() WHERE is_active = true`,
|
|
tradesExecuted, totalProfit,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (r *GridStateRepo) UpdatePaperWallet(ctx context.Context, pw *models.PaperWallet) error {
|
|
tradesJSON, err := json.Marshal(pw.Trades)
|
|
if err != nil {
|
|
tradesJSON = []byte("[]")
|
|
}
|
|
_, err = r.pool.Exec(ctx,
|
|
`UPDATE grid_state
|
|
SET paper_eth_balance = $1,
|
|
paper_usdc_balance = $2,
|
|
paper_total_gas_spent = $3,
|
|
paper_trades_json = $4,
|
|
updated_at = NOW()
|
|
WHERE is_active = true`,
|
|
pw.ETHBalance, pw.USDCBalance, pw.TotalGasSpent, tradesJSON,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (r *GridStateRepo) InitializePaperWallet(ctx context.Context, initialETH, initialUSDC float64) error {
|
|
state, err := r.GetActive(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if state != nil && state.PaperETHBalance != nil {
|
|
return nil // already initialized
|
|
}
|
|
|
|
_, err = r.pool.Exec(ctx,
|
|
`UPDATE grid_state
|
|
SET paper_eth_balance = $1,
|
|
paper_usdc_balance = $2,
|
|
paper_initial_eth = $1,
|
|
paper_initial_usdc = $2,
|
|
paper_total_gas_spent = 0,
|
|
paper_trades_json = '[]'::jsonb,
|
|
paper_start_time = NOW(),
|
|
updated_at = NOW()
|
|
WHERE is_active = true`,
|
|
initialETH, initialUSDC,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (r *GridStateRepo) GetPaperWallet(ctx context.Context) (*models.PaperWallet, error) {
|
|
state, err := r.GetActive(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if state == nil || state.PaperETHBalance == nil {
|
|
return nil, nil
|
|
}
|
|
pw := &models.PaperWallet{
|
|
ETHBalance: valOr(state.PaperETHBalance, 0),
|
|
USDCBalance: valOr(state.PaperUSDCBalance, 0),
|
|
TotalGasSpent: valOr(state.PaperTotalGasSpent, 0),
|
|
Trades: state.PaperTradesJSON,
|
|
StartTime: state.PaperStartTime,
|
|
InitialETH: valOr(state.PaperInitialETH, 0),
|
|
InitialUSDC: valOr(state.PaperInitialUSDC, 0),
|
|
}
|
|
return pw, nil
|
|
}
|
|
|
|
func (r *GridStateRepo) GetHistory(ctx context.Context, limit int) ([]models.GridState, error) {
|
|
rows, err := r.pool.Query(ctx,
|
|
`SELECT * FROM grid_state ORDER BY updated_at DESC LIMIT $1`,
|
|
limit,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
return collectGridStates(rows)
|
|
}
|
|
|
|
// --- scan helpers ---
|
|
|
|
func scanGridState(row scannable) (*models.GridState, error) {
|
|
var gs models.GridState
|
|
err := row.Scan(
|
|
&gs.ID, &gs.BasePrice, &gs.GridLevelsJSON,
|
|
&gs.TradesExecuted, &gs.TotalProfit, &gs.LastSRRefresh,
|
|
&gs.IsActive, &gs.CreatedAt, &gs.UpdatedAt,
|
|
// paper wallet columns
|
|
&gs.PaperETHBalance, &gs.PaperUSDCBalance, &gs.PaperTotalGasSpent,
|
|
&gs.PaperTradesJSON, &gs.PaperStartTime,
|
|
&gs.PaperInitialETH, &gs.PaperInitialUSDC,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &gs, nil
|
|
}
|
|
|
|
func collectGridStates(rows rowsIter) ([]models.GridState, error) {
|
|
var out []models.GridState
|
|
for rows.Next() {
|
|
var gs models.GridState
|
|
if err := rows.Scan(
|
|
&gs.ID, &gs.BasePrice, &gs.GridLevelsJSON,
|
|
&gs.TradesExecuted, &gs.TotalProfit, &gs.LastSRRefresh,
|
|
&gs.IsActive, &gs.CreatedAt, &gs.UpdatedAt,
|
|
&gs.PaperETHBalance, &gs.PaperUSDCBalance, &gs.PaperTotalGasSpent,
|
|
&gs.PaperTradesJSON, &gs.PaperStartTime,
|
|
&gs.PaperInitialETH, &gs.PaperInitialUSDC,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, gs)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
func valOr(p *float64, fallback float64) float64 {
|
|
if p != nil {
|
|
return *p
|
|
}
|
|
return fallback
|
|
}
|