commit 67a5d3abc0d33af7532893c5b32354669487021d Author: Kevin Cotugno Date: Thu Sep 7 23:04:47 2017 -0700 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..899186c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017, Kevin Cotugno + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fe4a33 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Tacitus diff --git a/cmd/interval/main.go b/cmd/interval/main.go new file mode 100644 index 0000000..b03a4a7 --- /dev/null +++ b/cmd/interval/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "github.com/shopspring/decimal" + "encoding/csv" + "log" + "fmt" + "os" + "time" + "strconv" + + "tacitus/postgres" +) + + +func main() { + client := postgres.NewClient() + client.Name = "gdax" + client.User = "gdax" + + err := client.Open() + if err != nil { + log.Fatal(err) + } + + var start time.Time + var end time.Time + var done bool + file, err := os.Create("btcusd_30_sec.csv") + if err != nil { + log.Fatal(err) + } + + csv := csv.NewWriter(file) + + first, _ := client.TradeService().Trade(1) + + start = first.Timestamp.Truncate(30 * time.Second) + end = start.Add(30 * time.Second) + + err = csv.Write([]string{"timestamp", "price", "volume", "transactions"}) + if err != nil { + log.Fatal(err) + } + + var lastPrice string + for !done { + trades, _ := client.TradeService().TradesInDateRange("BTCUSD", start, end) + if len(trades) > 0 { + var volume decimal.Decimal + var transactions int + + for _, t := range trades { + volume = volume.Add(t.Size) + transactions += 1 + lastPrice = t.Price.String() + } + + data := []string{end.String(), lastPrice, volume.String(), strconv.Itoa(transactions)} + if err = csv.Write(data); err != nil { + log.Fatal(err) + } + + fmt.Println(end) + } else { + data := []string{end.String(), lastPrice, "0", "0"} + if err = csv.Write(data); err != nil { + log.Fatal(err) + } + } + + start = end + end = end.Add(30 * time.Second) + + if start.After(time.Date(2017, 8, 30, 16, 13, 50, 0, time.UTC)) { + done = true + } + + } + + csv.Flush() + file.Close() +} diff --git a/config/postgres.sql b/config/postgres.sql new file mode 100644 index 0000000..580a7a3 --- /dev/null +++ b/config/postgres.sql @@ -0,0 +1,15 @@ +CREATE TABLE trades ( + id SERIAL PRIMARY KEY, + trade_id integer NOT NULL, + product CHAR(6) NOT NULL, + price NUMERIC(1000, 8) NOT NULL, + size NUMERIC(1000, 8) NOT NULL, + buy BOOLEAN NOT NULL DEFAULT FALSE, + sell BOOLEAN NOT NULL DEFAULT FALSE, + timestamp TIMESTAMPTZ NOT NULL); + +CREATE UNIQUE INDEX trade_id_product_INDEX ON trades ( + trade_id, + product); + +CREATE INDEX timestamp_index ON trades (timestamp); diff --git a/postgres/client.go b/postgres/client.go new file mode 100644 index 0000000..3f72b6d --- /dev/null +++ b/postgres/client.go @@ -0,0 +1,53 @@ +package postgres + +import ( + _ "github.com/lib/pq" + + "bytes" + "database/sql" + "text/template" + + "tacitus" +) + +const connStr = `user={{.User}} dbname={{.Name}} sslmode={{.SslMode}}` + +type Client struct { + Name string + User string + SslMode string + + tradeService TradeService + + db *sql.DB +} + +func NewClient() *Client { + c := Client{} + c.tradeService.client = &c + c.SslMode = "disable" + + return &c +} + +func (c *Client) Open() error { + buf := bytes.Buffer{} + tmpl, _ := template.New("conn").Parse(connStr) + tmpl.Execute(&buf, c) + + db, err := sql.Open("postgres", buf.String()) + if err != nil { + return err + } + + if err = db.Ping(); err != nil { + return err + } + + c.db = db + return nil +} + +func (c *Client) TradeService() tacitus.TradeService { + return &c.tradeService +} diff --git a/postgres/trade_service.go b/postgres/trade_service.go new file mode 100644 index 0000000..211f7ff --- /dev/null +++ b/postgres/trade_service.go @@ -0,0 +1,96 @@ +package postgres + +import ( + "time" + + "tacitus" +) + +const ( + trade_insert = `INSERT INTO trades (trade_id, product, price, size, buy, ` + + `sell, timestamp) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id;` + trade_find = `SELECT id, trade_id, product, price, size, buy, sell, ` + + `timestamp FROM trades WHERE id = $1;` + trade_find_trade_id = `SELECT id, trade_id, product, price, size, buy, ` + + `sell, timestamp FROM trades WHERE trade_id = $1 AND product = $2;` + trade_delete = `DELETE FROM trades WHERE id = $1;` + trades_in_date_range = `SELECT id, trade_id, product, price, size, buy, ` + + `sell, timestamp FROM trades WHERE product = $1 AND timestamp >= $2 ` + + `AND timestamp < $3;` +) + +type TradeService struct { + client *Client +} + +func (t *TradeService) Trade(id int) (*tacitus.Trade, error) { + var tr tacitus.Trade + + row := t.client.db.QueryRow(trade_find, id) + if err := row.Scan(&tr.Id, &tr.TradeId, &tr.Product, &tr.Price, &tr.Size, &tr.Buy, + &tr.Sell, &tr.Timestamp); err != nil { + + return nil, err + } + + return &tr, nil +} + +func (t *TradeService) TradeByTradeId(id int, prod string) (*tacitus.Trade, error) { + var tr tacitus.Trade + + row := t.client.db.QueryRow(trade_find_trade_id, id, prod) + if err := row.Scan(&tr.Id, &tr.TradeId, &tr.Product, &tr.Price, &tr.Size, &tr.Buy, + &tr.Sell, &tr.Timestamp); err != nil { + + return nil, err + } + + return &tr, nil +} + +func (t *TradeService) CreateTrade(trade *tacitus.Trade) error { + var id int + res := t.client.db.QueryRow(trade_insert, trade.TradeId, trade.Product, + trade.Price, trade.Size, trade.Buy, trade.Sell, trade.Timestamp) + if err := res.Scan(&id); err != nil { + return err + } + + trade.Id = id + + return nil +} + +func (t *TradeService) DeleteTrade(id int) { + t.client.db.Exec(trade_delete, id) +} + +func (t *TradeService) TradesInDateRange(product string, start, + end time.Time) ([]tacitus.Trade, error) { + + trades := make([]tacitus.Trade, 0) + + rows, err := t.client.db.Query(trades_in_date_range, product, start, end) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var trade tacitus.Trade + if err = rows.Scan(&trade.Id, &trade.TradeId, &trade.Product, &trade.Price, + &trade.Size, &trade.Buy, &trade.Sell, &trade.Timestamp); err != nil { + + return nil, err + } + + trades = append(trades, trade) + } + + if rows.Err() != nil { + return nil, rows.Err() + } + + return trades, nil +} diff --git a/trade.go b/trade.go new file mode 100644 index 0000000..ca2f8ff --- /dev/null +++ b/trade.go @@ -0,0 +1,26 @@ +package tacitus + +import ( + "github.com/shopspring/decimal" + + "time" +) + +type Trade struct { + Id int + TradeId int + Product string + Price decimal.Decimal + Size decimal.Decimal + Buy bool + Sell bool + Timestamp time.Time +} + +type TradeService interface { + Trade(id int) (*Trade, error) + TradeByTradeId(id int, prod string) (*Trade, error) + CreateTrade(t *Trade) error + DeleteTrade(id int) + TradesInDateRange(product string, start, end time.Time) ([]Trade, error) +}