Trades api

This commit is contained in:
Kevin Cotugno 2017-09-09 10:14:50 -07:00
parent 28dd7e3190
commit a5597382b0
7 changed files with 205 additions and 44 deletions

7
errors.go Normal file
View File

@ -0,0 +1,7 @@
package tacitus
type Error string
func (e Error) Error() string {
return string(e)
}

23
http/mux.go Normal file
View File

@ -0,0 +1,23 @@
package http
import (
"net/http"
)
type Mux struct {
*http.ServeMux
TradeHandler TradeHandler
}
type errorResponse struct {
Error string `json:"error,omitempty"`
}
func NewMux() *Mux {
m := Mux{ServeMux: http.NewServeMux()}
m.Handle("/api/trades/", &m.TradeHandler)
return &m
}

View File

@ -9,7 +9,7 @@ const DefaultAddr = ":8080"
type Server struct { type Server struct {
Addr string Addr string
Handler *http.Handler Handler *Mux
Err chan error Err chan error
@ -17,7 +17,8 @@ type Server struct {
} }
func NewServer() *Server { func NewServer() *Server {
return &Server{Addr: DefaultAddr} return &Server{Addr: DefaultAddr,
Handler: NewMux()}
} }
func (s *Server) Open() error { func (s *Server) Open() error {

73
http/trade_handler.go Normal file
View File

@ -0,0 +1,73 @@
package http
import (
"net/http"
"net/url"
"strconv"
"strings"
"tacitus"
)
type TradeHandler struct {
TradeService tacitus.TradeService
}
type tradesResponse struct {
Trades []tacitus.Trade `json:"trades,omitempty"`
Error string `json:"error,omitempty"`
}
func (t *TradeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
paths := strings.Split(r.URL.Path, "/")
if len(paths) < 4 && !tacitus.ValidProduct(paths[3]) {
errorResp(w, tacitus.Error("Invalid product"))
return
}
params := r.URL.Query()
params.Set("product", paths[3])
switch r.Method {
case "GET":
t.getTrades(w, params)
default:
errorResp(w, tacitus.Error("Invalid request"))
}
}
func (t *TradeHandler) getTrades(w http.ResponseWriter, params url.Values) {
var trades []tacitus.Trade
var err error
limit := parseLimit(params.Get("limit"))
prod := params.Get("product")
after, _ := strconv.ParseInt(params.Get("after"), 10, 0)
before, _ := strconv.ParseInt(params.Get("before"), 10, 0)
if params.Get("after") != "" {
trades, err = t.TradeService.TradesAfter(prod, int(after), limit)
} else if params.Get("before") != "" {
trades, err = t.TradeService.TradesBefore(prod, int(before), limit)
} else {
trades, err = t.TradeService.LastTrades(prod, limit)
}
if err == nil {
encodeJson(w, &tradesResponse{Trades: trades})
} else {
errorResp(w, tacitus.Error("Invalid request"))
}
}
func parseLimit(limit string) int {
l, err := strconv.ParseInt(limit, 10, 0)
if err != nil {
l = 1000
}
if l > 1000 {
l = 1000
}
return int(l)
}

17
http/util.go Normal file
View File

@ -0,0 +1,17 @@
package http
import (
"encoding/json"
"net/http"
)
func encodeJson(w http.ResponseWriter, data interface{}) {
if buf, err := json.Marshal(data); err == nil {
w.Header().Add("Content-Type", "application/json")
w.Write(buf)
}
}
func errorResp(w http.ResponseWriter, err error) {
json.NewEncoder(w).Encode(&errorResponse{Error: err.Error()})
}

View File

@ -1,6 +1,7 @@
package postgres package postgres
import ( import (
"database/sql"
"time" "time"
"tacitus" "tacitus"
@ -13,13 +14,17 @@ const (
trade_find = `SELECT id, ` + trade_columns + ` FROM trades WHERE id = $1;` trade_find = `SELECT id, ` + trade_columns + ` FROM trades WHERE id = $1;`
trade_find_trade_id = `SELECT id, ` + trade_columns + ` FROM trades ` + trade_find_trade_id = `SELECT id, ` + trade_columns + ` FROM trades ` +
`WHERE trade_id = $1 AND product = $2;` `WHERE trade_id = $1 AND product = $2;`
trade_delete = `DELETE FROM trades WHERE id = $1;` trade_delete = `DELETE FROM trades WHERE id = $1;`
trades_in_date_range = `SELECT id, ` + trade_columns + ` FROM ` + trade_in_date_range = `SELECT id, ` + trade_columns + ` FROM ` +
`trades WHERE product = $1 AND timestamp >= $2 AND timestamp < $3;` `trades WHERE product = $1 AND timestamp >= $2 AND timestamp < $3;`
trade_first = `SELECT id, ` + trade_columns + ` FROM trades WHERE ` + trade_first = `SELECT id, ` + trade_columns + ` FROM trades WHERE ` +
`product = $1 ORDER BY trade_id ASC LIMIT 1;` `product = $1 ORDER BY trade_id ASC LIMIT $2;`
trade_last = `SELECT id, ` + trade_columns + ` FROM trades WHERE ` + trade_last = `SELECT id, ` + trade_columns + ` FROM trades WHERE ` +
`product = $1 ORDER BY trade_id DESC LIMIT 1;` `product = $1 ORDER BY trade_id DESC LIMIT $2;`
trade_after = `SELECT id, ` + trade_columns + ` FROM trades ` +
`WHERE product = $1 AND trade_id < $2 ORDER BY trade_id DESC LIMIT $3;`
trade_before = `SELECT id, ` + trade_columns + ` FROM trades ` +
`WHERE product = $1 AND trade_id > $2 ORDER BY trade_id ASC LIMIT $3;`
) )
type TradeService struct { type TradeService struct {
@ -72,17 +77,60 @@ func (t *TradeService) DeleteTrade(id int) {
func (t *TradeService) TradesInDateRange(product string, start, func (t *TradeService) TradesInDateRange(product string, start,
end time.Time) ([]tacitus.Trade, error) { end time.Time) ([]tacitus.Trade, error) {
trades := make([]tacitus.Trade, 0) rows, err := t.client.db.Query(trade_in_date_range, product, start, end)
rows, err := t.client.db.Query(trades_in_date_range, product, start, end)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return deserializeTrades(rows)
}
func (t *TradeService) FirstTrades(product string, limit int) ([]tacitus.Trade, error) {
rows, err := t.client.db.Query(trade_first, product, limit)
if err != nil {
return nil, err
}
return deserializeTrades(rows)
}
func (t *TradeService) LastTrades(product string, limit int) ([]tacitus.Trade, error) {
rows, err := t.client.db.Query(trade_last, product, limit)
if err != nil {
return nil, err
}
return deserializeTrades(rows)
}
func (t *TradeService) TradesAfter(product string, id, limit int) ([]tacitus.Trade, error) {
rows, err := t.client.db.Query(trade_after, product, id, limit)
if err != nil {
return nil, err
}
return deserializeTrades(rows)
}
func (t *TradeService) TradesBefore(product string, id, limit int) ([]tacitus.Trade, error) {
rows, err := t.client.db.Query(trade_before, product, id, limit)
if err != nil {
return nil, err
}
return deserializeTrades(rows)
}
func deserializeTrades(rows *sql.Rows) ([]tacitus.Trade, error) {
defer rows.Close() defer rows.Close()
trades := make([]tacitus.Trade, 0)
for rows.Next() { for rows.Next() {
var trade tacitus.Trade var trade tacitus.Trade
if err = rows.Scan(&trade.Id, &trade.TradeId, &trade.Product, &trade.Price, if err := rows.Scan(&trade.Id, &trade.TradeId, &trade.Product, &trade.Price,
&trade.Size, &trade.Buy, &trade.Sell, &trade.Timestamp); err != nil { &trade.Size, &trade.Buy, &trade.Sell, &trade.Timestamp); err != nil {
return nil, err return nil, err
@ -97,29 +145,3 @@ func (t *TradeService) TradesInDateRange(product string, start,
return trades, nil return trades, nil
} }
func (t *TradeService) FirstTrade(product string) (*tacitus.Trade, error) {
var tr tacitus.Trade
row := t.client.db.QueryRow(trade_first, product)
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) LastTrade(product string) (*tacitus.Trade, error) {
var tr tacitus.Trade
row := t.client.db.QueryRow(trade_last, product)
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
}

View File

@ -3,18 +3,21 @@ package tacitus
import ( import (
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"strings"
"time" "time"
) )
var PRODUCTS = [...]string{"BTCUSD", "ETHUSD"}
type Trade struct { type Trade struct {
Id int Id int `json:"-"`
TradeId int TradeId int `json:"trade_id,"`
Product string Product string `json:"product,"`
Price decimal.Decimal Price decimal.Decimal `json:"price,"`
Size decimal.Decimal Size decimal.Decimal `json:"size,"`
Buy bool Buy bool `json:"buy,"`
Sell bool Sell bool `json:"sell,"`
Timestamp time.Time Timestamp time.Time `json:"timestamp,"`
} }
type TradeService interface { type TradeService interface {
@ -23,4 +26,19 @@ type TradeService interface {
CreateTrade(t *Trade) error CreateTrade(t *Trade) error
DeleteTrade(id int) DeleteTrade(id int)
TradesInDateRange(product string, start, end time.Time) ([]Trade, error) TradesInDateRange(product string, start, end time.Time) ([]Trade, error)
FirstTrades(product string, limit int) ([]Trade, error)
LastTrades(product string, limit int) ([]Trade, error)
TradesAfter(product string, id, limit int) ([]Trade, error)
TradesBefore(product string, id, limit int) ([]Trade, error)
}
func ValidProduct(prod string) bool {
prod = strings.ToUpper(prod)
for _, p := range PRODUCTS {
if p == prod {
return true
}
}
return false
} }