From 18a7520fa7cddba240df80b21f7ad8b58a39731c Mon Sep 17 00:00:00 2001 From: chiahung Date: Mon, 22 May 2023 14:45:39 +0800 Subject: [PATCH] MINOR: add test for recovery --- pkg/strategy/grid2/recover_test.go | 284 ++++++++++++++++++ .../testcase1/closed_orders.csv | 1 + .../testcase1/open_orders.csv | 3 + .../grid2/recovery_testcase/testcase1/spec | 12 + .../recovery_testcase/testcase1/trades.csv | 1 + 5 files changed, 301 insertions(+) create mode 100644 pkg/strategy/grid2/recover_test.go create mode 100644 pkg/strategy/grid2/recovery_testcase/testcase1/closed_orders.csv create mode 100644 pkg/strategy/grid2/recovery_testcase/testcase1/open_orders.csv create mode 100644 pkg/strategy/grid2/recovery_testcase/testcase1/spec create mode 100644 pkg/strategy/grid2/recovery_testcase/testcase1/trades.csv diff --git a/pkg/strategy/grid2/recover_test.go b/pkg/strategy/grid2/recover_test.go new file mode 100644 index 000000000..55419430b --- /dev/null +++ b/pkg/strategy/grid2/recover_test.go @@ -0,0 +1,284 @@ +package grid2 + +import ( + "context" + "encoding/csv" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "sort" + "strconv" + "testing" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +type TestData struct { + Market types.Market `json:"market" yaml:"market"` + Strategy Strategy `json:"strategy" yaml:"strategy"` + OpenOrders []types.Order `json:"openOrders" yaml:"openOrders"` + ClosedOrders []types.Order `json:"closedOrders" yaml:"closedOrders"` + Trades []types.Trade `json:"trades" yaml:"trades"` +} + +type TestDataService struct { + Orders map[string]types.Order + Trades []types.Trade +} + +func (t *TestDataService) QueryTrades(ctx context.Context, symbol string, options *types.TradeQueryOptions) ([]types.Trade, error) { + var i int = 0 + if options.LastTradeID != 0 { + for idx, trade := range t.Trades { + if trade.ID < options.LastTradeID { + continue + } + + i = idx + break + } + } + + var trades []types.Trade + l := len(t.Trades) + for ; i < l && len(trades) < int(options.Limit); i++ { + trades = append(trades, t.Trades[i]) + } + + return trades, nil +} + +func (t *TestDataService) QueryOrder(ctx context.Context, q types.OrderQuery) (*types.Order, error) { + if len(q.OrderID) == 0 { + return nil, fmt.Errorf("order id should not be empty") + } + + order, exist := t.Orders[q.OrderID] + if !exist { + return nil, fmt.Errorf("order not found") + } + + return &order, nil +} + +func NewStrategy(t *TestData) *Strategy { + s := t.Strategy + s.Debug = true + s.Initialize() + s.Market = t.Market + s.Position = types.NewPositionFromMarket(t.Market) + s.orderExecutor = bbgo.NewGeneralOrderExecutor(&bbgo.ExchangeSession{}, t.Market.Symbol, ID, s.InstanceID(), s.Position) + return &s +} + +func NewTestDataService(t *TestData) *TestDataService { + var orders map[string]types.Order = make(map[string]types.Order) + for _, order := range t.OpenOrders { + orders[strconv.FormatUint(order.OrderID, 10)] = order + } + + for _, order := range t.ClosedOrders { + orders[strconv.FormatUint(order.OrderID, 10)] = order + } + + trades := t.Trades + sort.Slice(t.Trades, func(i, j int) bool { + return trades[i].ID < trades[j].ID + }) + + return &TestDataService{ + Orders: orders, + Trades: trades, + } +} + +func readSpec(fileName string) (*TestData, error) { + content, err := ioutil.ReadFile(fileName) + if err != nil { + return nil, err + } + + market := types.Market{} + if err := json.Unmarshal(content, &market); err != nil { + return nil, err + } + + strategy := Strategy{} + if err := json.Unmarshal(content, &strategy); err != nil { + return nil, err + } + + data := TestData{ + Market: market, + Strategy: strategy, + } + return &data, nil +} + +func readOrdersFromCSV(fileName string) ([]types.Order, error) { + csvFile, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer csvFile.Close() + csvReader := csv.NewReader(csvFile) + + keys, err := csvReader.Read() + if err != nil { + return nil, err + } + + var orders []types.Order + for { + row, err := csvReader.Read() + if err == io.EOF { + break + } + + if err != nil { + return nil, err + } + + if len(row) != len(keys) { + return nil, fmt.Errorf("length of row should be equal to length of keys") + } + + var m map[string]interface{} = make(map[string]interface{}) + for i, key := range keys { + if key == "orderID" { + x, err := strconv.ParseUint(row[i], 10, 64) + if err != nil { + return nil, err + } + m[key] = x + } else { + m[key] = row[i] + } + } + + b, err := json.Marshal(m) + if err != nil { + return nil, err + } + + order := types.Order{} + if err = json.Unmarshal(b, &order); err != nil { + return nil, err + } + + orders = append(orders, order) + } + + return orders, nil +} + +func readTradesFromCSV(fileName string) ([]types.Trade, error) { + csvFile, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer csvFile.Close() + csvReader := csv.NewReader(csvFile) + + keys, err := csvReader.Read() + if err != nil { + return nil, err + } + + var trades []types.Trade + for { + row, err := csvReader.Read() + if err == io.EOF { + break + } + + if err != nil { + return nil, err + } + + if len(row) != len(keys) { + return nil, fmt.Errorf("length of row should be equal to length of keys") + } + + var m map[string]interface{} = make(map[string]interface{}) + for i, key := range keys { + switch key { + case "id", "orderID": + x, err := strconv.ParseUint(row[i], 10, 64) + if err != nil { + return nil, err + } + m[key] = x + default: + m[key] = row[i] + } + } + + b, err := json.Marshal(m) + if err != nil { + return nil, err + } + + trade := types.Trade{} + if err = json.Unmarshal(b, &trade); err != nil { + return nil, err + } + + trades = append(trades, trade) + } + + return trades, nil +} + +func readTestDataFrom(fileDir string) (*TestData, error) { + data, err := readSpec(fmt.Sprintf("%s/spec", fileDir)) + if err != nil { + return nil, err + } + + openOrders, err := readOrdersFromCSV(fmt.Sprintf("%s/open_orders.csv", fileDir)) + if err != nil { + return nil, err + } + + closedOrders, err := readOrdersFromCSV(fmt.Sprintf("%s/closed_orders.csv", fileDir)) + if err != nil { + return nil, err + } + + trades, err := readTradesFromCSV(fmt.Sprintf("%s/trades.csv", fileDir)) + if err != nil { + return nil, err + } + + data.OpenOrders = openOrders + data.ClosedOrders = closedOrders + data.Trades = trades + return data, nil +} + +func TestRecoverByScanningTrades(t *testing.T) { + assert := assert.New(t) + + t.Run("test case 1", func(t *testing.T) { + fileDir := "recovery_testcase/testcase1/" + + data, err := readTestDataFrom(fileDir) + if !assert.NoError(err) { + return + } + + testService := NewTestDataService(data) + strategy := NewStrategy(data) + filledOrders, err := strategy.getFilledOrdersByScanningTrades(context.Background(), testService, testService, data.OpenOrders) + if !assert.NoError(err) { + return + } + + assert.Len(filledOrders, 0) + }) +} diff --git a/pkg/strategy/grid2/recovery_testcase/testcase1/closed_orders.csv b/pkg/strategy/grid2/recovery_testcase/testcase1/closed_orders.csv new file mode 100644 index 000000000..86178721a --- /dev/null +++ b/pkg/strategy/grid2/recovery_testcase/testcase1/closed_orders.csv @@ -0,0 +1 @@ +orderID,price,quantity,remaining_quantity,side,creationTime,updateTime \ No newline at end of file diff --git a/pkg/strategy/grid2/recovery_testcase/testcase1/open_orders.csv b/pkg/strategy/grid2/recovery_testcase/testcase1/open_orders.csv new file mode 100644 index 000000000..6748a09a3 --- /dev/null +++ b/pkg/strategy/grid2/recovery_testcase/testcase1/open_orders.csv @@ -0,0 +1,3 @@ +orderID,price,quantity,remaining_quantity,side,creationTime,updateTime +1,20000.0000000000000000,1.0000000000000000,1.0000000000000000,buy,2023-05-01T08:00:00.000Z,2023-05-01T08:00:00.000Z +2,25000.0000000000000000,1.0000000000000000,1.0000000000000000,buy,2023-05-01T08:00:00.000Z,2023-05-01T08:00:00.000Z \ No newline at end of file diff --git a/pkg/strategy/grid2/recovery_testcase/testcase1/spec b/pkg/strategy/grid2/recovery_testcase/testcase1/spec new file mode 100644 index 000000000..c4baf0965 --- /dev/null +++ b/pkg/strategy/grid2/recovery_testcase/testcase1/spec @@ -0,0 +1,12 @@ +{ + "symbol":"BTCUSDT", + "quoteCurrency": "USDT", + "baseCurrency": "BTC", + "mode":"arithmetic", + "lowerPrice":"20000.0", + "upperPrice":"30000.0", + "gridNumber":3, + "quoteInvestment":"45000.0", + "tickSize":0.1, + "stepSize":0.00000001 +} \ No newline at end of file diff --git a/pkg/strategy/grid2/recovery_testcase/testcase1/trades.csv b/pkg/strategy/grid2/recovery_testcase/testcase1/trades.csv new file mode 100644 index 000000000..ade45cd39 --- /dev/null +++ b/pkg/strategy/grid2/recovery_testcase/testcase1/trades.csv @@ -0,0 +1 @@ +id,price,quantity,side,fee,feeCurrency,orderID,created_at,tradedAt \ No newline at end of file