2020-11-03 09:55:20 +00:00
package service
import (
2021-03-14 03:03:02 +00:00
"context"
2021-01-29 05:15:44 +00:00
"strconv"
"strings"
2021-03-14 03:03:02 +00:00
"time"
2021-01-29 05:15:44 +00:00
2020-11-03 09:55:20 +00:00
"github.com/jmoiron/sqlx"
2020-11-05 03:00:51 +00:00
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
2020-11-03 09:55:20 +00:00
2021-03-14 03:03:02 +00:00
"github.com/c9s/bbgo/pkg/exchange/batch"
2020-11-03 09:55:20 +00:00
"github.com/c9s/bbgo/pkg/types"
)
type OrderService struct {
DB * sqlx . DB
}
2021-03-14 03:03:02 +00:00
func ( s * OrderService ) Sync ( ctx context . Context , exchange types . Exchange , symbol string , startTime time . Time ) error {
2022-06-01 10:30:24 +00:00
isMargin , isFutures , isIsolated , isolatedSymbol := getExchangeAttributes ( exchange )
// override symbol if isolatedSymbol is not empty
if isIsolated && len ( isolatedSymbol ) > 0 {
symbol = isolatedSymbol
2021-12-05 07:59:23 +00:00
}
records , err := s . QueryLast ( exchange . Name ( ) , symbol , isMargin , isFutures , isIsolated , 50 )
2021-03-14 03:03:02 +00:00
if err != nil {
return err
}
orderKeys := make ( map [ uint64 ] struct { } )
var lastID uint64 = 0
if len ( records ) > 0 {
for _ , record := range records {
orderKeys [ record . OrderID ] = struct { } { }
}
lastID = records [ 0 ] . OrderID
startTime = records [ 0 ] . CreationTime . Time ( )
}
2022-05-30 16:59:33 +00:00
exchangeTradeHistoryService , ok := exchange . ( types . ExchangeTradeHistoryService )
if ! ok {
return nil
}
b := & batch . ClosedOrderBatchQuery {
ExchangeTradeHistoryService : exchangeTradeHistoryService ,
}
2021-03-14 03:03:02 +00:00
ordersC , errC := b . Query ( ctx , symbol , startTime , time . Now ( ) , lastID )
for order := range ordersC {
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
case err := <- errC :
if err != nil {
return err
}
default :
}
if _ , exists := orderKeys [ order . OrderID ] ; exists {
continue
}
2021-12-31 17:28:29 +00:00
// skip canceled and not filled orders
2022-02-03 04:55:25 +00:00
if order . Status == types . OrderStatusCanceled && order . ExecutedQuantity . IsZero ( ) {
2021-12-31 17:28:29 +00:00
continue
}
2021-03-14 03:03:02 +00:00
if err := s . Insert ( order ) ; err != nil {
return err
}
}
return <- errC
}
2020-11-05 03:00:51 +00:00
// QueryLast queries the last order from the database
2021-12-05 07:59:23 +00:00
func ( s * OrderService ) QueryLast ( ex types . ExchangeName , symbol string , isMargin , isFutures , isIsolated bool , limit int ) ( [ ] types . Order , error ) {
log . Infof ( "querying last order exchange = %s AND symbol = %s AND is_margin = %v AND is_futures = %v AND is_isolated = %v" , ex , symbol , isMargin , isFutures , isIsolated )
2021-01-19 18:09:12 +00:00
2021-12-05 07:59:23 +00:00
sql := ` SELECT * FROM orders WHERE exchange = :exchange AND symbol = :symbol AND is_margin = :is_margin AND is_futures = :is_futures AND is_isolated = :is_isolated ORDER BY gid DESC LIMIT :limit `
2021-02-25 05:47:59 +00:00
rows , err := s . DB . NamedQuery ( sql , map [ string ] interface { } {
2021-01-19 18:09:12 +00:00
"exchange" : ex ,
"symbol" : symbol ,
"is_margin" : isMargin ,
2021-12-05 07:59:23 +00:00
"is_futures" : isFutures ,
2021-01-19 18:09:12 +00:00
"is_isolated" : isIsolated ,
2021-02-25 05:47:59 +00:00
"limit" : limit ,
2020-11-05 03:00:51 +00:00
} )
if err != nil {
return nil , errors . Wrap ( err , "query last order error" )
}
defer rows . Close ( )
2021-02-25 05:47:59 +00:00
return s . scanRows ( rows )
2020-11-05 03:00:51 +00:00
}
2021-01-29 09:52:13 +00:00
type AggOrder struct {
types . Order
AveragePrice * float64 ` json:"averagePrice" db:"average_price" `
}
2021-01-29 10:48:00 +00:00
type QueryOrdersOptions struct {
2021-01-29 05:15:44 +00:00
Exchange types . ExchangeName
Symbol string
LastGID int64
2021-01-29 10:48:00 +00:00
Ordering string
2021-01-29 05:15:44 +00:00
}
2021-01-29 10:48:00 +00:00
func ( s * OrderService ) Query ( options QueryOrdersOptions ) ( [ ] AggOrder , error ) {
2021-02-03 09:12:06 +00:00
sql := genOrderSQL ( options )
rows , err := s . DB . NamedQuery ( sql , map [ string ] interface { } {
"exchange" : options . Exchange ,
"symbol" : options . Symbol ,
"gid" : options . LastGID ,
} )
if err != nil {
return nil , err
}
defer rows . Close ( )
return s . scanAggRows ( rows )
}
func genOrderSQL ( options QueryOrdersOptions ) string {
2021-01-29 05:15:44 +00:00
// ascending
ordering := "ASC"
2021-01-29 10:48:00 +00:00
switch v := strings . ToUpper ( options . Ordering ) ; v {
case "DESC" , "ASC" :
ordering = options . Ordering
2021-01-29 05:15:44 +00:00
}
var where [ ] string
if options . LastGID > 0 {
switch ordering {
case "ASC" :
where = append ( where , "gid > :gid" )
case "DESC" :
where = append ( where , "gid < :gid" )
}
}
if len ( options . Exchange ) > 0 {
where = append ( where , "exchange = :exchange" )
}
if len ( options . Symbol ) > 0 {
where = append ( where , "symbol = :symbol" )
}
2021-01-29 09:52:13 +00:00
sql := ` SELECT orders.*, IFNULL(SUM(t.price * t.quantity)/SUM(t.quantity), orders.price) AS average_price FROM orders ` +
` LEFT JOIN trades AS t ON (t.order_id = orders.order_id) `
2021-01-29 05:15:44 +00:00
if len ( where ) > 0 {
sql += ` WHERE ` + strings . Join ( where , " AND " )
}
2021-01-29 09:52:13 +00:00
sql += ` GROUP BY orders.gid `
sql += ` ORDER BY orders.gid ` + ordering
2021-01-29 05:15:44 +00:00
sql += ` LIMIT ` + strconv . Itoa ( 500 )
2021-01-29 10:34:03 +00:00
log . Info ( sql )
2021-02-03 09:12:06 +00:00
return sql
2021-01-29 09:52:13 +00:00
}
func ( s * OrderService ) scanAggRows ( rows * sqlx . Rows ) ( orders [ ] AggOrder , err error ) {
for rows . Next ( ) {
var order AggOrder
if err := rows . StructScan ( & order ) ; err != nil {
return nil , err
}
orders = append ( orders , order )
}
return orders , rows . Err ( )
2020-11-03 09:55:20 +00:00
}
func ( s * OrderService ) scanRows ( rows * sqlx . Rows ) ( orders [ ] types . Order , err error ) {
for rows . Next ( ) {
var order types . Order
if err := rows . StructScan ( & order ) ; err != nil {
return nil , err
}
orders = append ( orders , order )
}
return orders , rows . Err ( )
}
2021-02-06 08:05:21 +00:00
func ( s * OrderService ) Insert ( order types . Order ) ( err error ) {
if s . DB . DriverName ( ) == "mysql" {
_ , err = s . DB . NamedExec ( `
2021-12-05 07:59:23 +00:00
INSERT INTO orders ( exchange , order_id , client_order_id , order_type , status , symbol , price , stop_price , quantity , executed_quantity , side , is_working , time_in_force , created_at , updated_at , is_margin , is_futures , is_isolated )
VALUES ( : exchange , : order_id , : client_order_id , : order_type , : status , : symbol , : price , : stop_price , : quantity , : executed_quantity , : side , : is_working , : time_in_force , : created_at , : updated_at , : is_margin , : is_futures , : is_isolated )
2020-11-05 05:35:04 +00:00
ON DUPLICATE KEY UPDATE status = : status , executed_quantity = : executed_quantity , is_working = : is_working , updated_at = : updated_at ` , order )
2021-02-06 08:05:21 +00:00
return err
}
_ , err = s . DB . NamedExec ( `
2021-12-05 07:59:23 +00:00
INSERT INTO orders ( exchange , order_id , client_order_id , order_type , status , symbol , price , stop_price , quantity , executed_quantity , side , is_working , time_in_force , created_at , updated_at , is_margin , is_futures , is_isolated )
VALUES ( : exchange , : order_id , : client_order_id , : order_type , : status , : symbol , : price , : stop_price , : quantity , : executed_quantity , : side , : is_working , : time_in_force , : created_at , : updated_at , : is_margin , : is_futures , : is_isolated )
2021-02-06 08:05:21 +00:00
` , order )
2020-11-03 09:55:20 +00:00
return err
}