diff --git a/cmd/trade.go b/cmd/trade.go index 6c60bb170..0e9b12a0e 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -493,6 +493,15 @@ func startFillTracking( fillTracker := plugins.MakeFillTracker(tradingPair, threadTracker, exchangeShim, botConfig.FillTrackerSleepMillis, botConfig.FillTrackerDeleteCyclesThreshold) fillLogger := plugins.MakeFillLogger() fillTracker.RegisterHandler(fillLogger) + if botConfig.SqlDbPath != "" { + fillDBWriter, e := plugins.MakeFillDBWriter(botConfig.SqlDbPath) + if e != nil { + l.Info("") + l.Errorf("problem encountered while making the FillDBWriter: %s", e) + deleteAllOffersAndExit(l, botConfig, client, sdex, exchangeShim, threadTracker) + } + fillTracker.RegisterHandler(fillDBWriter) + } if strategyFillHandlers != nil { for _, h := range strategyFillHandlers { fillTracker.RegisterHandler(h) diff --git a/examples/configs/trader/sample_trader.cfg b/examples/configs/trader/sample_trader.cfg index 947b2d7c2..cc833fc6a 100644 --- a/examples/configs/trader/sample_trader.cfg +++ b/examples/configs/trader/sample_trader.cfg @@ -89,6 +89,10 @@ MAX_OP_FEE_STROOPS=5000 # minimum values for Kraken: https://support.kraken.com/hc/en-us/articles/205893708-What-is-the-minimum-order-size-volume- # minimum order value for Binance: https://support.binance.com/hc/en-us/articles/115000594711-Trading-Rule #MIN_CENTRALIZED_BASE_VOLUME=30.0 + +# uncomment if you want to track fills in a sql db for sqlite +#SQL_DB_PATH="./mydatabase.db" + # uncomment lines below to use kraken. Can use "sdex" or leave out to trade on the Stellar Decentralized Exchange. # can alternatively use any of the ccxt-exchanges marked as "Trading" (run `kelp exchanges` for full list) #TRADING_EXCHANGE="kraken" diff --git a/glide.lock b/glide.lock index e17f93f90..f95a8012a 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: c1a6fe42c2e0796f29822018134adc2401292b654fec58b7d790daafceec2585 -updated: 2019-04-03T12:27:32.117314051-07:00 +hash: 8191585be558e1e725be674a1dba62e5cafa8d09b782b021dd3fa3db647f4b5e +updated: 2019-06-28T14:28:49.963622978-07:00 imports: - name: cloud.google.com/go version: 793297ec250352b0ece46e103381a0fc3dab95a1 @@ -43,6 +43,8 @@ imports: version: 359442d561ca28acd0fe503aa9f075f505bc9ed0 - name: github.com/manucorporat/sse version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d +- name: github.com/mattn/go-sqlite3 + version: c7c4067b79cc51e6dfdcef5c702e74b1e0fa7c75 - name: github.com/mitchellh/mapstructure version: 3536a929edddb9a5b34bd6861dc4a9647cb459fe - name: github.com/nikhilsaraf/go-tools diff --git a/glide.yaml b/glide.yaml index 600c64020..87f417577 100644 --- a/glide.yaml +++ b/glide.yaml @@ -40,3 +40,5 @@ import: version: a26df67722de7fcf1a8e22cd934e63e553dd3875 - package: github.com/mitchellh/mapstructure version: v1.1.2 +- package: github.com/mattn/go-sqlite3 + version: 1.10.0 diff --git a/plugins/fillDBWriter.go b/plugins/fillDBWriter.go new file mode 100644 index 000000000..49ca9b420 --- /dev/null +++ b/plugins/fillDBWriter.go @@ -0,0 +1,100 @@ +package plugins + +import ( + "database/sql" + "fmt" + "log" + "time" + + _ "github.com/mattn/go-sqlite3" + "github.com/stellar/kelp/api" + "github.com/stellar/kelp/model" + "github.com/stellar/kelp/support/utils" +) + +const dateFormatString = "2006/01/02" +const sqlDbCreate = "CREATE TABLE IF NOT EXISTS trades (txid TEXT PRIMARY KEY, date_utc VARCHAR(10), timestamp_millis INTEGER, base TEXT, quote TEXT, action TEXT, type TEXT, counter_price REAL, base_volume REAL, counter_cost REAL, fee REAL)" +const sqlInsert = "INSERT INTO trades (txid, date_utc, timestamp_millis, base, quote, action, type, counter_price, base_volume, counter_cost, fee) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + +var sqlIndexes = []string{ + "CREATE INDEX IF NOT EXISTS date ON trades (date_utc, base, quote)", +} + +// FillDBWriter is a FillHandler that writes fills to a SQL database +type FillDBWriter struct { + db *sql.DB +} + +var _ api.FillHandler = &FillDBWriter{} + +// MakeFillDBWriter is a factory method +func MakeFillDBWriter(sqlDbPath string) (api.FillHandler, error) { + db, e := sql.Open("sqlite3", sqlDbPath) + if e != nil { + return nil, fmt.Errorf("could not open sqlite3 database: %s", e) + } + + statement, e := db.Prepare(sqlDbCreate) + if e != nil { + return nil, fmt.Errorf("could not prepare sql statement: %s", e) + } + _, e = statement.Exec() + if e != nil { + return nil, fmt.Errorf("could not execute sql create table statement: %s", e) + } + + for i, sqlIndexCreate := range sqlIndexes { + statement, e = db.Prepare(sqlIndexCreate) + if e != nil { + return nil, fmt.Errorf("could not prepare sql statement to create index (i=%d): %s", i, e) + } + _, e = statement.Exec() + if e != nil { + return nil, fmt.Errorf("could not execute sql statement to create index (i=%d): %s", i, e) + } + } + + log.Printf("making FillDBWriter with db path: %s\n", sqlDbPath) + return &FillDBWriter{ + db: db, + }, nil +} + +// HandleFill impl. +func (f *FillDBWriter) HandleFill(trade model.Trade) error { + statement, e := f.db.Prepare(sqlInsert) + if e != nil { + return fmt.Errorf("could not prepare sql insert values statement: %s", e) + } + + txid := utils.CheckedString(trade.TransactionID) + timeSeconds := trade.Timestamp.AsInt64() / 1000 + date := time.Unix(timeSeconds, 0).UTC() + dateString := date.Format(dateFormatString) + + _, e = statement.Exec( + txid, + dateString, + utils.CheckedString(trade.Timestamp), + string(trade.Pair.Base), + string(trade.Pair.Quote), + trade.OrderAction.String(), + trade.OrderType.String(), + f.checkedFloat(trade.Price), + f.checkedFloat(trade.Volume), + f.checkedFloat(trade.Cost), + f.checkedFloat(trade.Fee), + ) + if e != nil { + return fmt.Errorf("could not execute sql insert values statement: %s", e) + } + log.Printf("wrote trade (txid=%s) to db\n", txid) + return nil +} + +func (f *FillDBWriter) checkedFloat(n *model.Number) interface{} { + if n == nil { + return nil + } + return n.AsFloat() +} diff --git a/trader/config.go b/trader/config.go index 5ec0a68e5..e0edbb28e 100644 --- a/trader/config.go +++ b/trader/config.go @@ -34,6 +34,7 @@ type BotConfig struct { HorizonURL string `valid:"-" toml:"HORIZON_URL"` Fee *FeeConfig `valid:"-" toml:"FEE"` MinCentralizedBaseVolume float64 `valid:"-" toml:"MIN_CENTRALIZED_BASE_VOLUME"` + SqlDbPath string `valid:"-" toml:"SQL_DB_PATH"` AlertType string `valid:"-" toml:"ALERT_TYPE"` AlertAPIKey string `valid:"-" toml:"ALERT_API_KEY"` MonitoringPort uint16 `valid:"-" toml:"MONITORING_PORT"`