2020-11-06 13:40:48 +00:00
package service
import (
"context"
2021-12-13 23:15:18 +00:00
"fmt"
2021-12-14 16:59:28 +00:00
"os"
2021-12-07 07:21:37 +00:00
"strconv"
2020-11-06 13:40:48 +00:00
"strings"
"time"
2022-06-02 08:40:24 +00:00
sq "github.com/Masterminds/squirrel"
2020-11-06 13:40:48 +00:00
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
2022-05-30 16:59:33 +00:00
"github.com/c9s/bbgo/pkg/exchange/batch"
2020-11-06 13:40:48 +00:00
"github.com/c9s/bbgo/pkg/types"
)
type BacktestService struct {
DB * sqlx . DB
}
2021-03-14 03:04:56 +00:00
func ( s * BacktestService ) SyncKLineByInterval ( ctx context . Context , exchange types . Exchange , symbol string , interval types . Interval , startTime , endTime time . Time ) error {
log . Infof ( "synchronizing lastKLine for interval %s from exchange %s" , interval , exchange . Name ( ) )
2020-11-06 13:40:48 +00:00
2022-06-02 08:40:24 +00:00
// TODO: use isFutures here
_ , _ , isIsolated , isolatedSymbol := getExchangeAttributes ( exchange )
// override symbol if isolatedSymbol is not empty
if isIsolated && len ( isolatedSymbol ) > 0 {
symbol = isolatedSymbol
}
tasks := [ ] SyncTask {
{
Type : types . KLine { } ,
Time : func ( obj interface { } ) time . Time {
2022-06-02 08:53:17 +00:00
return obj . ( types . KLine ) . StartTime . Time ( ) . UTC ( )
2022-06-02 08:40:24 +00:00
} ,
ID : func ( obj interface { } ) string {
kline := obj . ( types . KLine )
return kline . Symbol + kline . Interval . String ( ) + strconv . FormatInt ( kline . StartTime . UnixMilli ( ) , 10 )
} ,
Select : SelectLastKLines ( exchange . Name ( ) , symbol , interval , 100 ) ,
BatchQuery : func ( ctx context . Context , startTime , endTime time . Time ) ( interface { } , chan error ) {
q := & batch . KLineBatchQuery { Exchange : exchange }
return q . Query ( ctx , symbol , interval , startTime , endTime )
} ,
Insert : func ( obj interface { } ) error {
kline := obj . ( types . KLine )
return s . Insert ( kline )
} ,
} ,
}
2020-11-06 16:49:17 +00:00
2022-06-02 08:40:24 +00:00
for _ , sel := range tasks {
if err := sel . execute ( ctx , s . DB , startTime ) ; err != nil {
2021-03-14 03:04:56 +00:00
return err
2020-11-06 13:40:48 +00:00
}
2021-03-14 03:04:56 +00:00
}
2022-06-02 08:40:24 +00:00
return nil
2021-03-14 03:04:56 +00:00
}
2020-11-06 16:49:17 +00:00
2022-05-11 05:59:44 +00:00
func ( s * BacktestService ) Verify ( symbols [ ] string , startTime time . Time , endTime time . Time , sourceExchange types . Exchange , verboseCnt int ) error {
2021-12-14 16:59:28 +00:00
var corruptCnt = 0
for _ , symbol := range symbols {
log . Infof ( "verifying backtesting data..." )
for interval := range types . SupportedIntervals {
log . Infof ( "verifying %s %s kline data..." , symbol , interval )
2022-05-11 05:59:44 +00:00
klineC , errC := s . QueryKLinesCh ( startTime , endTime , sourceExchange , [ ] string { symbol } , [ ] types . Interval { interval } )
2021-12-14 16:59:28 +00:00
var emptyKLine types . KLine
var prevKLine types . KLine
for k := range klineC {
if verboseCnt > 1 {
fmt . Fprint ( os . Stderr , "." )
}
if prevKLine != emptyKLine {
if prevKLine . StartTime . Unix ( ) == k . StartTime . Unix ( ) {
s . _deleteDuplicatedKLine ( k )
log . Errorf ( "found kline data duplicated at time: %s kline: %+v , deleted it" , k . StartTime , k )
2021-12-15 05:04:01 +00:00
} else if prevKLine . StartTime . Time ( ) . Add ( interval . Duration ( ) ) . Unix ( ) != k . StartTime . Time ( ) . Unix ( ) {
2021-12-14 16:59:28 +00:00
corruptCnt ++
log . Errorf ( "found kline data corrupted at time: %s kline: %+v" , k . StartTime , k )
log . Errorf ( "between %d and %d" ,
prevKLine . StartTime . Unix ( ) ,
k . StartTime . Unix ( ) )
}
}
prevKLine = k
}
2021-12-13 23:15:18 +00:00
2021-12-14 16:59:28 +00:00
if verboseCnt > 1 {
fmt . Fprintln ( os . Stderr )
}
2021-12-13 23:15:18 +00:00
2021-12-14 16:59:28 +00:00
if err := <- errC ; err != nil {
2022-05-11 05:59:44 +00:00
return err
2021-12-14 16:59:28 +00:00
}
}
2021-12-13 23:15:18 +00:00
}
2021-12-14 16:59:28 +00:00
log . Infof ( "backtest verification completed" )
if corruptCnt > 0 {
log . Errorf ( "found %d corruptions" , corruptCnt )
} else {
log . Infof ( "found %d corruptions" , corruptCnt )
2020-11-06 13:40:48 +00:00
}
2022-05-11 05:59:44 +00:00
return nil
2021-12-14 16:59:28 +00:00
}
func ( s * BacktestService ) Sync ( ctx context . Context , exchange types . Exchange , symbol string ,
startTime time . Time , endTime time . Time , interval types . Interval ) error {
return s . SyncKLineByInterval ( ctx , exchange , symbol , interval , startTime , endTime )
2020-11-06 13:40:48 +00:00
}
2021-12-07 07:21:37 +00:00
func ( s * BacktestService ) QueryFirstKLine ( ex types . ExchangeName , symbol string , interval types . Interval ) ( * types . KLine , error ) {
return s . QueryKLine ( ex , symbol , interval , "ASC" , 1 )
}
// QueryKLine queries the klines from the database
func ( s * BacktestService ) QueryKLine ( ex types . ExchangeName , symbol string , interval types . Interval , orderBy string , limit int ) ( * types . KLine , error ) {
2020-11-06 13:40:48 +00:00
log . Infof ( "querying last kline exchange = %s AND symbol = %s AND interval = %s" , ex , symbol , interval )
2021-12-13 23:15:18 +00:00
tableName := s . _targetKlineTable ( ex )
2020-11-06 13:40:48 +00:00
// make the SQL syntax IDE friendly, so that it can analyze it.
2021-12-13 23:15:18 +00:00
sql := fmt . Sprintf ( "SELECT * FROM `%s` WHERE `symbol` = :symbol AND `interval` = :interval and exchange = :exchange ORDER BY end_time " + orderBy + " LIMIT " + strconv . Itoa ( limit ) , tableName )
2020-11-06 13:40:48 +00:00
rows , err := s . DB . NamedQuery ( sql , map [ string ] interface { } {
2021-12-13 23:15:18 +00:00
"exchange" : ex . String ( ) ,
2020-11-06 13:40:48 +00:00
"interval" : interval ,
"symbol" : symbol ,
} )
if err != nil {
2021-12-07 07:21:37 +00:00
return nil , errors . Wrap ( err , "query kline error" )
2020-11-06 13:40:48 +00:00
}
if rows . Err ( ) != nil {
return nil , rows . Err ( )
}
defer rows . Close ( )
if rows . Next ( ) {
var kline types . KLine
err = rows . StructScan ( & kline )
return & kline , err
}
return nil , rows . Err ( )
}
2022-06-02 09:24:54 +00:00
// QueryKLinesForward is used for querying klines to back-testing
2021-05-07 16:57:25 +00:00
func ( s * BacktestService ) QueryKLinesForward ( exchange types . ExchangeName , symbol string , interval types . Interval , startTime time . Time , limit int ) ( [ ] types . KLine , error ) {
2021-12-13 23:15:18 +00:00
tableName := s . _targetKlineTable ( exchange )
sql := "SELECT * FROM `binance_klines` WHERE `end_time` >= :start_time AND `symbol` = :symbol AND `interval` = :interval and exchange = :exchange ORDER BY end_time ASC LIMIT :limit"
sql = strings . ReplaceAll ( sql , "binance_klines" , tableName )
2020-11-08 04:13:34 +00:00
rows , err := s . DB . NamedQuery ( sql , map [ string ] interface { } {
2021-05-07 16:57:25 +00:00
"start_time" : startTime ,
2021-10-16 05:49:00 +00:00
"limit" : limit ,
"symbol" : symbol ,
"interval" : interval ,
2021-12-13 23:15:18 +00:00
"exchange" : exchange . String ( ) ,
2020-11-08 04:13:34 +00:00
} )
if err != nil {
return nil , err
}
return s . scanRows ( rows )
}
2021-05-07 16:57:25 +00:00
func ( s * BacktestService ) QueryKLinesBackward ( exchange types . ExchangeName , symbol string , interval types . Interval , endTime time . Time , limit int ) ( [ ] types . KLine , error ) {
2021-12-13 23:15:18 +00:00
tableName := s . _targetKlineTable ( exchange )
sql := "SELECT * FROM `binance_klines` WHERE `end_time` <= :end_time and exchange = :exchange AND `symbol` = :symbol AND `interval` = :interval ORDER BY end_time DESC LIMIT :limit"
sql = strings . ReplaceAll ( sql , "binance_klines" , tableName )
2021-10-16 05:49:00 +00:00
sql = "SELECT t.* FROM (" + sql + ") AS t ORDER BY t.end_time ASC"
2020-11-08 04:13:34 +00:00
rows , err := s . DB . NamedQuery ( sql , map [ string ] interface { } {
2021-05-07 16:57:25 +00:00
"limit" : limit ,
2021-10-16 05:49:00 +00:00
"end_time" : endTime ,
2020-11-08 04:13:34 +00:00
"symbol" : symbol ,
"interval" : interval ,
2021-12-13 23:15:18 +00:00
"exchange" : exchange . String ( ) ,
2020-11-08 04:13:34 +00:00
} )
if err != nil {
return nil , err
}
return s . scanRows ( rows )
}
2020-11-10 11:06:20 +00:00
func ( s * BacktestService ) QueryKLinesCh ( since , until time . Time , exchange types . Exchange , symbols [ ] string , intervals [ ] types . Interval ) ( chan types . KLine , chan error ) {
2021-12-13 23:15:18 +00:00
if len ( symbols ) == 0 {
2021-12-20 12:28:22 +00:00
return returnError ( errors . Errorf ( "symbols is empty when querying kline, plesae check your strategy setting. " ) )
2021-12-13 23:15:18 +00:00
}
tableName := s . _targetKlineTable ( exchange . Name ( ) )
sql := "SELECT * FROM `binance_klines` WHERE `end_time` BETWEEN :since AND :until AND `symbol` IN (:symbols) AND `interval` IN (:intervals) and exchange = :exchange ORDER BY end_time ASC"
sql = strings . ReplaceAll ( sql , "binance_klines" , tableName )
2020-11-06 13:40:48 +00:00
2020-11-06 16:49:17 +00:00
sql , args , err := sqlx . Named ( sql , map [ string ] interface { } {
2020-11-06 13:40:48 +00:00
"since" : since ,
2020-11-10 11:06:20 +00:00
"until" : until ,
2020-11-07 12:11:07 +00:00
"symbols" : symbols ,
2020-11-06 16:49:17 +00:00
"intervals" : types . IntervalSlice ( intervals ) ,
2021-12-13 23:15:18 +00:00
"exchange" : exchange . Name ( ) . String ( ) ,
2020-11-06 13:40:48 +00:00
} )
2020-11-10 11:06:20 +00:00
2020-11-06 16:49:17 +00:00
sql , args , err = sqlx . In ( sql , args ... )
2021-12-20 12:28:22 +00:00
if err != nil {
return returnError ( err )
}
2020-11-06 16:49:17 +00:00
sql = s . DB . Rebind ( sql )
rows , err := s . DB . Queryx ( sql , args ... )
2020-11-06 13:40:48 +00:00
if err != nil {
2021-12-20 12:28:22 +00:00
return returnError ( err )
2020-11-06 13:40:48 +00:00
}
2020-11-06 16:49:17 +00:00
return s . scanRowsCh ( rows )
2020-11-06 13:40:48 +00:00
}
2021-12-20 12:28:22 +00:00
func returnError ( err error ) ( chan types . KLine , chan error ) {
ch := make ( chan types . KLine , 0 )
close ( ch )
log . WithError ( err ) . Error ( "backtest query error" )
errC := make ( chan error , 1 )
// avoid blocking
go func ( ) {
errC <- err
close ( errC )
} ( )
return ch , errC
}
2020-11-06 13:40:48 +00:00
// scanRowsCh scan rows into channel
2020-11-06 16:49:17 +00:00
func ( s * BacktestService ) scanRowsCh ( rows * sqlx . Rows ) ( chan types . KLine , chan error ) {
2020-11-07 12:11:07 +00:00
ch := make ( chan types . KLine , 500 )
2020-11-06 16:49:17 +00:00
errC := make ( chan error , 1 )
2020-11-06 13:40:48 +00:00
go func ( ) {
2020-11-06 16:49:17 +00:00
defer close ( errC )
2020-11-07 12:11:07 +00:00
defer close ( ch )
2020-11-06 13:40:48 +00:00
defer rows . Close ( )
for rows . Next ( ) {
var kline types . KLine
if err := rows . StructScan ( & kline ) ; err != nil {
2020-11-06 16:49:17 +00:00
errC <- err
return
2020-11-06 13:40:48 +00:00
}
ch <- kline
}
if err := rows . Err ( ) ; err != nil {
2020-11-06 16:49:17 +00:00
errC <- err
return
2020-11-06 13:40:48 +00:00
}
2020-11-07 12:11:07 +00:00
2020-11-06 13:40:48 +00:00
} ( )
2020-11-06 16:49:17 +00:00
return ch , errC
2020-11-06 13:40:48 +00:00
}
func ( s * BacktestService ) scanRows ( rows * sqlx . Rows ) ( klines [ ] types . KLine , err error ) {
for rows . Next ( ) {
var kline types . KLine
if err := rows . StructScan ( & kline ) ; err != nil {
return nil , err
}
klines = append ( klines , kline )
}
return klines , rows . Err ( )
}
2021-12-13 23:15:18 +00:00
func ( s * BacktestService ) _targetKlineTable ( exchangeName types . ExchangeName ) string {
2022-06-02 13:27:28 +00:00
return strings . ToLower ( exchangeName . String ( ) ) + "_klines"
2021-12-13 23:15:18 +00:00
}
2022-06-02 13:27:28 +00:00
var errExchangeFieldIsUnset = errors . New ( "kline.Exchange field should not be empty" )
2020-11-06 13:40:48 +00:00
func ( s * BacktestService ) Insert ( kline types . KLine ) error {
if len ( kline . Exchange ) == 0 {
2022-06-02 13:27:28 +00:00
return errExchangeFieldIsUnset
2020-11-06 13:40:48 +00:00
}
2021-12-13 23:15:18 +00:00
tableName := s . _targetKlineTable ( kline . Exchange )
sql := fmt . Sprintf ( "INSERT INTO `%s` (`exchange`, `start_time`, `end_time`, `symbol`, `interval`, `open`, `high`, `low`, `close`, `closed`, `volume`, `quote_volume`, `taker_buy_base_volume`, `taker_buy_quote_volume`)" +
"VALUES (:exchange, :start_time, :end_time, :symbol, :interval, :open, :high, :low, :close, :closed, :volume, :quote_volume, :taker_buy_base_volume, :taker_buy_quote_volume)" , tableName )
_ , err := s . DB . NamedExec ( sql , kline )
return err
}
2021-12-14 16:59:28 +00:00
func ( s * BacktestService ) _deleteDuplicatedKLine ( k types . KLine ) error {
2021-12-14 11:34:43 +00:00
if len ( k . Exchange ) == 0 {
return errors . New ( "kline.Exchange field should not be empty" )
}
tableName := s . _targetKlineTable ( k . Exchange )
2022-05-30 10:11:17 +00:00
sql := fmt . Sprintf ( "DELETE FROM `%s` WHERE gid = :gid " , tableName )
2021-12-14 11:34:43 +00:00
_ , err := s . DB . NamedExec ( sql , k )
return err
}
2021-12-14 16:59:28 +00:00
func ( s * BacktestService ) SyncExist ( ctx context . Context , exchange types . Exchange , symbol string ,
fromTime time . Time , endTime time . Time , interval types . Interval ) error {
klineC , errC := s . QueryKLinesCh ( fromTime , endTime , exchange , [ ] string { symbol } , [ ] types . Interval { interval } )
nowStartTime := fromTime
for k := range klineC {
2021-12-19 04:10:46 +00:00
if nowStartTime . Unix ( ) < k . StartTime . Unix ( ) {
2021-12-14 16:59:28 +00:00
log . Infof ( "syncing %s interval %s syncing %s ~ %s " , symbol , interval , nowStartTime , k . EndTime )
2022-05-20 10:57:41 +00:00
if err := s . Sync ( ctx , exchange , symbol , nowStartTime , k . EndTime . Time ( ) . Add ( - 1 * interval . Duration ( ) ) , interval ) ; err != nil {
log . WithError ( err ) . Errorf ( "sync error" )
}
2021-12-14 16:59:28 +00:00
}
2021-12-19 04:10:46 +00:00
nowStartTime = k . StartTime . Time ( ) . Add ( interval . Duration ( ) )
2021-12-14 16:59:28 +00:00
}
2021-12-20 12:28:22 +00:00
if nowStartTime . Unix ( ) < endTime . Unix ( ) && nowStartTime . Unix ( ) < time . Now ( ) . Unix ( ) {
2022-05-20 10:57:41 +00:00
if err := s . Sync ( ctx , exchange , symbol , nowStartTime , endTime , interval ) ; err != nil {
log . WithError ( err ) . Errorf ( "sync error" )
}
2021-12-20 12:28:22 +00:00
}
2021-12-19 04:10:46 +00:00
2021-12-14 16:59:28 +00:00
if err := <- errC ; err != nil {
return err
}
return nil
}
2022-06-02 08:40:24 +00:00
// TODO: add is_futures column since the klines data is different
func SelectLastKLines ( ex types . ExchangeName , symbol string , interval types . Interval , limit uint64 ) sq . SelectBuilder {
return sq . Select ( "*" ) .
From ( strings . ToLower ( ex . String ( ) ) + "_klines" ) .
Where ( sq . And {
sq . Eq { "symbol" : symbol } ,
sq . Eq { "exchange" : ex } ,
sq . Eq { "`interval`" : interval . String ( ) } ,
} ) .
OrderBy ( "start_time DESC" ) .
Limit ( limit )
}