source: bots/main.go@ 8b447ef

main
Last change on this file since 8b447ef was b715712, checked in by Stefan <trsunovstefan@…>, 8 weeks ago

Add the server side and configuration

  • Property mode set to 100644
File size: 3.8 KB
Line 
1// EduBerza market simulation bot.
2//
3// Walks a small price for every active market, inserts rows into
4// market_trades once per tick, and upserts the current 1m candle.
5//
6// Run from the repo root so the relative .env path resolves:
7//
8// go run ./bots/...
9package main
10
11import (
12 "bufio"
13 "database/sql"
14 "flag"
15 "fmt"
16 "log"
17 "math/rand"
18 "os"
19 "strings"
20 "time"
21
22 _ "github.com/lib/pq"
23)
24
25type market struct {
26 id string
27 symbol string
28 price float64
29}
30
31func main() {
32 interval := flag.Duration("interval", 3*time.Second, "seconds between price ticks")
33 flag.Parse()
34
35 loadEnv(".env")
36 dsn := fmt.Sprintf(
37 "host=%s port=%s user=%s password=%s dbname=%s sslmode=disable options='--search_path=project,public'",
38 env("DBHOST", "localhost"),
39 env("DBPORT", "5432"),
40 env("DBUSER", "postgres"),
41 env("DBPASSWORD", ""),
42 env("DBNAME", "postgres"),
43 )
44 db, err := sql.Open("postgres", dsn)
45 if err != nil {
46 log.Fatalf("open db: %v", err)
47 }
48 defer db.Close()
49 if err := db.Ping(); err != nil {
50 log.Fatalf("ping: %v", err)
51 }
52
53 markets, err := loadMarkets(db)
54 if err != nil {
55 log.Fatalf("load markets: %v", err)
56 }
57 if len(markets) == 0 {
58 log.Fatal("no active markets found - run `go run ./server -init` first")
59 }
60
61 log.Printf("bot started. simulating %d markets every %s", len(markets), *interval)
62 rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
63
64 for {
65 for i := range markets {
66 m := &markets[i]
67 // random walk: ±0.3% per tick
68 drift := (rnd.Float64() - 0.5) * 0.006
69 m.price = m.price * (1 + drift)
70 if m.price <= 0 {
71 m.price = 0.000001
72 }
73 qty := rnd.Float64()*0.5 + 0.01
74
75 side := "buy"
76 if rnd.Float64() < 0.5 {
77 side = "sell"
78 }
79
80 if err := insertTick(db, m.id, m.price, qty, side); err != nil {
81 log.Printf("insert tick %s: %v", m.symbol, err)
82 continue
83 }
84 log.Printf(" %-8s %.6f qty=%.4f side=%s", m.symbol, m.price, qty, side)
85 }
86 time.Sleep(*interval)
87 }
88}
89
90func loadMarkets(db *sql.DB) ([]market, error) {
91 rows, err := db.Query(`
92 SELECT m.id, c.symbol, COALESCE(lp.price, 100)
93 FROM markets m
94 JOIN crypto c ON c.id = m.crypto_id
95 LEFT JOIN v_latest_prices lp ON lp.market_id = m.id
96 WHERE m.is_active = true
97 ORDER BY c.symbol`)
98 if err != nil {
99 return nil, err
100 }
101 defer rows.Close()
102 var out []market
103 for rows.Next() {
104 var m market
105 if err := rows.Scan(&m.id, &m.symbol, &m.price); err != nil {
106 return nil, err
107 }
108 out = append(out, m)
109 }
110 return out, nil
111}
112
113func insertTick(db *sql.DB, marketID string, price, qty float64, side string) error {
114 tx, err := db.Begin()
115 if err != nil {
116 return err
117 }
118 defer tx.Rollback()
119
120 if _, err := tx.Exec(
121 `INSERT INTO market_trades (market_id, executed_at, price, quantity, side, source)
122 VALUES ($1, now(), $2, $3, $4, 'simulation')`,
123 marketID, price, qty, side,
124 ); err != nil {
125 return err
126 }
127
128 // upsert the current 1m candle
129 if _, err := tx.Exec(`
130 INSERT INTO market_candles (market_id, timeframe, open, high, low, close, volume, candle_time)
131 VALUES ($1, '1m', $2, $2, $2, $2, $3, date_trunc('minute', now()))
132 ON CONFLICT (market_id, timeframe, candle_time) DO UPDATE
133 SET high = GREATEST(market_candles.high, EXCLUDED.close),
134 low = LEAST( market_candles.low, EXCLUDED.close),
135 close = EXCLUDED.close,
136 volume = market_candles.volume + EXCLUDED.volume`,
137 marketID, price, qty,
138 ); err != nil {
139 return err
140 }
141 return tx.Commit()
142}
143
144func loadEnv(path string) {
145 f, err := os.Open(path)
146 if err != nil {
147 return
148 }
149 defer f.Close()
150 s := bufio.NewScanner(f)
151 for s.Scan() {
152 line := strings.TrimSpace(s.Text())
153 if line == "" || strings.HasPrefix(line, "#") {
154 continue
155 }
156 parts := strings.SplitN(line, "=", 2)
157 if len(parts) == 2 {
158 os.Setenv(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]))
159 }
160 }
161}
162
163func env(k, def string) string {
164 if v := os.Getenv(k); v != "" {
165 return v
166 }
167 return def
168}
Note: See TracBrowser for help on using the repository browser.