Skip to content

Commit 04f7995

Browse files
committed
Enhance EVMTrader with volume and PnL metrics
1 parent 9b76e41 commit 04f7995

File tree

6 files changed

+106
-6
lines changed

6 files changed

+106
-6
lines changed

sai-trading/cmd/trader/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,9 @@ func setupConfig(requireAuth bool) (evmtrader.Config, error) {
620620
// Load Slack webhook from environment variable
621621
cfg.SlackWebhook = os.Getenv("SLACK_WEBHOOK")
622622

623+
// Optional
624+
cfg.KeeperDBDSN = os.Getenv("KEEPER_DB_DSN")
625+
623626
return cfg, nil
624627
}
625628

sai-trading/services/evmtrader/auto_trader.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -518,17 +518,21 @@ func (t *EVMTrader) runHealthCheck(ctx context.Context, cfg AutoTradingConfig, c
518518
fields["balance_"+denom] = balance.String()
519519
}
520520

521-
// 24h metrics derived from CSV logs.
521+
// 24h metrics derived from CSV logs and sai-keeper.
522522
nowUTC := time.Now().UTC()
523-
metrics := compute24hMetrics(nowUTC, "logs")
523+
metrics := compute24hMetrics(ctx, nowUTC, "logs", t.keeper, t.ethAddrBech32)
524524
fields["positions_opened_24h"] = metrics.positionsOpened24h
525525
fields["positions_closed_24h"] = metrics.positionsClosed24h
526526
fields["failed_txs_24h"] = metrics.failedTxs24h
527527
fields["failed_reason_types"] = metrics.failedReasonsBlock
528528
fields["failed_reason"] = metrics.failedReasonsBlock
529-
// TODO: Add volume generated and realized PnL
530-
fields["volume_generated"] = "N/A"
531-
fields["realized_pnl"] = "N/A"
529+
if t.keeper != nil {
530+
fields["volume_generated"] = fmt.Sprintf("%.2f USD", metrics.volumeUSD)
531+
fields["realized_pnl"] = fmt.Sprintf("%.2f USD", metrics.realizedPnL)
532+
} else {
533+
fields["volume_generated"] = "N/A"
534+
fields["realized_pnl"] = "N/A"
535+
}
532536

533537
// Best-effort market pair labels for Slack readability.
534538
marketPairs := make([]string, 0, len(marketIndices))

sai-trading/services/evmtrader/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type Config struct {
4444

4545
// Static JSON file for trades
4646
TradeJSONFile string // Path to JSON file with open_trade parameters
47+
48+
KeeperDBDSN string
4749
}
4850

4951
// ContractAddresses stores addresses/ids loaded from localnet_contracts.env

sai-trading/services/evmtrader/evm_trader.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ type EVMTrader struct {
4444

4545
tradeLogMu sync.Mutex
4646
openTrades map[uint64]*tradeLifecycle
47+
48+
keeper *KeeperClient
4749
}
4850

4951
// tradeLifecycle stores metadata about an open trade so that when it closes,
@@ -162,6 +164,14 @@ func New(ctx context.Context, cfg Config) (*EVMTrader, error) {
162164
openTrades: make(map[uint64]*tradeLifecycle),
163165
}
164166

167+
if cfg.KeeperDBDSN != "" {
168+
if kc, err := NewKeeperClient(cfg.KeeperDBDSN); err != nil {
169+
fmt.Printf("Warning: failed to init keeper client: %v\n", err)
170+
} else {
171+
trader.keeper = kc
172+
}
173+
}
174+
165175
return trader, nil
166176
}
167177

@@ -176,6 +186,12 @@ func (t *EVMTrader) Close() {
176186
t.logWarn("Failed to close gRPC connection", "error", err.Error())
177187
}
178188
}
189+
190+
if t.keeper != nil {
191+
if err := t.keeper.Close(); err != nil {
192+
t.logWarn("Failed to close keeper DB", "error", err.Error())
193+
}
194+
}
179195
}
180196

181197
// OpenTradeFromConfig opens a trade using the trader's configuration
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package evmtrader
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"time"
8+
9+
_ "github.com/lib/pq"
10+
)
11+
12+
type KeeperClient struct {
13+
db *sql.DB
14+
}
15+
16+
type KeeperUser24hStats struct {
17+
VolumeUSD float64
18+
RealizedPnL float64
19+
}
20+
21+
func NewKeeperClient(dsn string) (*KeeperClient, error) {
22+
db, err := sql.Open("postgres", dsn)
23+
if err != nil {
24+
return nil, fmt.Errorf("open keeper db: %w", err)
25+
}
26+
27+
db.SetMaxOpenConns(1)
28+
db.SetMaxIdleConns(1)
29+
db.SetConnMaxLifetime(30 * time.Minute)
30+
31+
return &KeeperClient{db: db}, nil
32+
}
33+
34+
func (kc *KeeperClient) ComputeUser24hStats(ctx context.Context, trader string) (KeeperUser24hStats, error) {
35+
var out KeeperUser24hStats
36+
37+
const q = `
38+
WITH recent AS (
39+
SELECT h.*
40+
FROM perp_trade_history h
41+
JOIN block b ON b.block = h.block
42+
WHERE h.trader = $1
43+
AND b.block_ts >= now() - interval '24 hours'
44+
AND h.trade_change_type IN ('position_closed', 'position_closed_liquidation')
45+
)
46+
SELECT
47+
COALESCE(SUM(notional_usd), 0) AS volume_usd,
48+
COALESCE(SUM(realized_pnl_usd), 0) AS realized_pnl_usd
49+
FROM recent;
50+
`
51+
52+
row := kc.db.QueryRowContext(ctx, q, trader)
53+
if err := row.Scan(&out.VolumeUSD, &out.RealizedPnL); err != nil {
54+
return KeeperUser24hStats{}, fmt.Errorf("scan keeper stats: %w", err)
55+
}
56+
57+
return out, nil
58+
}
59+
60+
func (kc *KeeperClient) Close() error {
61+
if kc.db != nil {
62+
return kc.db.Close()
63+
}
64+
return nil
65+
}

sai-trading/services/evmtrader/status_report.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package evmtrader
22

33
import (
4+
"context"
45
"encoding/csv"
56
"os"
67
"path/filepath"
@@ -15,9 +16,11 @@ type status24hMetrics struct {
1516
positionsClosed24h int
1617
failedTxs24h int
1718
failedReasonsBlock string
19+
volumeUSD float64
20+
realizedPnL float64
1821
}
1922

20-
func compute24hMetrics(now time.Time, logsDir string) status24hMetrics {
23+
func compute24hMetrics(ctx context.Context, now time.Time, logsDir string, keeper *KeeperClient, traderAddr string) status24hMetrics {
2124
cutoff := now.Add(-24 * time.Hour)
2225
out := status24hMetrics{}
2326

@@ -94,6 +97,13 @@ func compute24hMetrics(now time.Time, logsDir string) status24hMetrics {
9497

9598
out.failedReasonsBlock = formatFailedReasonsBlock(reasonCounts)
9699

100+
if keeper != nil && ctx != nil && ctx.Err() == nil && traderAddr != "" {
101+
if stats, err := keeper.ComputeUser24hStats(ctx, traderAddr); err == nil {
102+
out.volumeUSD = stats.VolumeUSD
103+
out.realizedPnL = stats.RealizedPnL
104+
}
105+
}
106+
97107
return out
98108
}
99109

0 commit comments

Comments
 (0)