Files
gocryptotrader/backtester/config/strategyconfig_test.go
Scott fcc5ad4551 exchanges/qa: Add exchange wrapper testing suite (#1159)
* initial concept of a nice validation tester for exchanges

* adds some datahandler design

* expand testing

* more tests and fixes

* minor end of day fix for bithumb

* fixes implementation issues

* more test coverage and improvements, but not sure if i should continue

* fix more wrapper implementations

* adds error type, more fixes

* changes signature, fixes implementations

* fixes more wrapper implementations

* one more bit

* more cleanup

* WOW things work?

* lintle 1/1337

* mini bump

* fixes all linting

* neaten

* GetOrderInfo+ asset pair fixes+improvements

* adds new websocket test

* expand ws testing

* fix bug, expand tests, improve implementation

* code coverage of a lot of new codes

* fixes everything

* reverts accidental changes

* minor fixes from reviewing code

* removes Bitfinex cancelBatchOrder implementation

* fixes dumb baby typo for babies

* mini nit fixes

* so many nits to address

* addresses all the nits

* Titlecase

* switcheroo

* removes websocket testing for now

* fix appveyor, minor test fix

* fixes typo, re-kindles killed kode

* skip binance wrapper tests when running CI

* expired context, huobi okx fixes

* kodespull

* fix ordering

* time fix because why not

* fix exmo, others

* hopefully this fixes all of my life's problems

* last thing today

* huobi, more like hypotrophy

* golangci-lint, more like mypooroldknee-splint

* fix huobi times by removing them

* should fix okx currency issues

* blocks the application

* adds last little contingency for pairs

* addresses most nits and new problems

* lovely fixed before seeing why okx sucks

* fixes issues with okx websocket

* the classic receieieivaier

* lintle

* adds test and fixes existing tests

* expands error handling messages during setup

* fixes dumb okx bugs introduced

* quick fix for lint and exmo

* fixes nixes

* fix exmo deposit issue

* lint

* fixes issue with extra asset runs missing

* fix surprise race

* all the lint and merge fixes

* fixes surprise bugs in OKx

* fixes issues with times and chains

* fixing all the merge stuff

* merge fix

* rm logs and a panic potential

* lovely lint lament

* an easy demonstration of scenario, but not of initial purpose

* put it in the bin

* Revert "put it in the bin"

This reverts commit 15c6490f713233d43f10957367fcbf18e3818bdd.

* re-add after immediate error popup

* fix mini poor test design

* okx okay

* merge fixes

* fixes issues discovered in lovely test

* I FORGOT TO COMMIT THIS

* nit fixaroonaboo

* forgoetten test fix

* revert old okx asset intrument work

* fixes

* revert problems I didnt understand. update bybit

* fix merge bugs

* test cleanup

* further improvements

* reshuffle and lint

* rm redundant CI_TEST by rm the CI_TEST field that is redundant

* path fix

* move to its own section, dont run on 32 bit + appveyor

* lint

* fix lbank

* address nits

* let it rip

* fix failing test time range

* niteroo boogaloo

* mod tidy, use common.SimpleTimeFormat
2023-07-03 11:09:43 +10:00

1486 lines
40 KiB
Go

package config
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"
"time"
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/top2bottom2"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/file"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/database"
"github.com/thrasher-corp/gocryptotrader/database/drivers"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
)
const (
mainExchange = "binance"
dca = "dollarcostaverage"
// change this if you modify a config and want it to save to the example folder
saveConfig = false
)
var (
startDate = time.Date(time.Now().Year()-1, 8, 1, 0, 0, 0, 0, time.Local)
endDate = time.Date(time.Now().Year()-1, 12, 1, 0, 0, 0, 0, time.Local)
tradeEndDate = startDate.Add(time.Hour * 72)
makerFee = decimal.NewFromFloat(0.0002)
takerFee = decimal.NewFromFloat(0.0007)
minMax = MinMax{
MinimumSize: decimal.NewFromFloat(0.005),
MaximumSize: decimal.NewFromInt(2),
MaximumTotal: decimal.NewFromInt(40000),
}
// strictMinMax used for live order restrictions
strictMinMax = MinMax{
MinimumSize: decimal.NewFromFloat(0.001),
MaximumSize: decimal.NewFromFloat(0.05),
MaximumTotal: decimal.NewFromInt(100),
}
initialFunds1000000 *decimal.Decimal
initialFunds100000 *decimal.Decimal
initialFunds10 *decimal.Decimal
mainCurrencyPair = currency.NewPair(currency.BTC, currency.USDT)
)
func TestMain(m *testing.M) {
iF1 := decimal.NewFromInt(1000000)
iF2 := decimal.NewFromInt(100000)
iBF := decimal.NewFromInt(10)
initialFunds1000000 = &iF1
initialFunds100000 = &iF2
initialFunds10 = &iBF
os.Exit(m.Run())
}
func TestValidateDate(t *testing.T) {
t.Parallel()
c := Config{}
err := c.validateDate()
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
c.DataSettings = DataSettings{
DatabaseData: &DatabaseData{},
}
err = c.validateDate()
if !errors.Is(err, gctcommon.ErrDateUnset) {
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset)
}
c.DataSettings.DatabaseData.StartDate = time.Now()
c.DataSettings.DatabaseData.EndDate = c.DataSettings.DatabaseData.StartDate
err = c.validateDate()
if !errors.Is(err, gctcommon.ErrStartEqualsEnd) {
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrStartEqualsEnd)
}
c.DataSettings.DatabaseData.EndDate = c.DataSettings.DatabaseData.StartDate.Add(time.Minute)
err = c.validateDate()
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
c.DataSettings.APIData = &APIData{}
err = c.validateDate()
if !errors.Is(err, gctcommon.ErrDateUnset) {
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset)
}
c.DataSettings.APIData.StartDate = time.Now()
c.DataSettings.APIData.EndDate = c.DataSettings.APIData.StartDate
err = c.validateDate()
if !errors.Is(err, gctcommon.ErrStartEqualsEnd) {
t.Errorf("received: %v, expected: %v", err, gctcommon.ErrStartEqualsEnd)
}
c.DataSettings.APIData.EndDate = c.DataSettings.APIData.StartDate.Add(time.Minute)
err = c.validateDate()
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
}
func TestValidateCurrencySettings(t *testing.T) {
t.Parallel()
c := Config{}
err := c.validateCurrencySettings()
if !errors.Is(err, errNoCurrencySettings) {
t.Errorf("received: %v, expected: %v", err, errNoCurrencySettings)
}
c.CurrencySettings = append(c.CurrencySettings, CurrencySettings{})
err = c.validateCurrencySettings()
if !errors.Is(err, errUnsetCurrency) {
t.Errorf("received: %v, expected: %v", err, errUnsetCurrency)
}
leet := decimal.NewFromInt(1337)
c.CurrencySettings[0].SpotDetails = &SpotDetails{InitialQuoteFunds: &leet}
err = c.validateCurrencySettings()
if !errors.Is(err, errUnsetCurrency) {
t.Errorf("received: %v, expected: %v", err, errUnsetCurrency)
}
c.CurrencySettings[0].Base = currency.NewCode("lol")
err = c.validateCurrencySettings()
if !errors.Is(err, asset.ErrNotSupported) {
t.Errorf("received: %v, expected: %v", err, asset.ErrNotSupported)
}
c.CurrencySettings[0].Asset = asset.Spot
err = c.validateCurrencySettings()
if !errors.Is(err, errUnsetExchange) {
t.Errorf("received: %v, expected: %v", err, errUnsetExchange)
}
c.CurrencySettings[0].ExchangeName = "lol"
err = c.validateCurrencySettings()
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
c.CurrencySettings[0].Asset = asset.PerpetualSwap
err = c.validateCurrencySettings()
if !errors.Is(err, errPerpetualsUnsupported) {
t.Errorf("received: %v, expected: %v", err, errPerpetualsUnsupported)
}
c.CurrencySettings[0].Asset = asset.USDTMarginedFutures
c.CurrencySettings[0].Quote = currency.NewCode("PERP")
err = c.validateCurrencySettings()
if !errors.Is(err, errPerpetualsUnsupported) {
t.Errorf("received: %v, expected: %v", err, errPerpetualsUnsupported)
}
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(2)
c.CurrencySettings[0].MaximumSlippagePercent = decimal.NewFromInt(3)
c.CurrencySettings[0].Quote = currency.NewCode("USD")
err = c.validateCurrencySettings()
if !errors.Is(err, errFeatureIncompatible) {
t.Errorf("received: %v, expected: %v", err, errFeatureIncompatible)
}
c.CurrencySettings[0].Asset = asset.Spot
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(-1)
err = c.validateCurrencySettings()
if !errors.Is(err, errBadSlippageRates) {
t.Errorf("received: %v, expected: %v", err, errBadSlippageRates)
}
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(2)
c.CurrencySettings[0].MaximumSlippagePercent = decimal.NewFromInt(-1)
err = c.validateCurrencySettings()
if !errors.Is(err, errBadSlippageRates) {
t.Errorf("received: %v, expected: %v", err, errBadSlippageRates)
}
c.CurrencySettings[0].MinimumSlippagePercent = decimal.NewFromInt(2)
c.CurrencySettings[0].MaximumSlippagePercent = decimal.NewFromInt(1)
err = c.validateCurrencySettings()
if !errors.Is(err, errBadSlippageRates) {
t.Errorf("received: %v, expected: %v", err, errBadSlippageRates)
}
c.CurrencySettings[0].SpotDetails = &SpotDetails{}
err = c.validateCurrencySettings()
if !errors.Is(err, errBadInitialFunds) {
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
}
z := decimal.Zero
c.CurrencySettings[0].SpotDetails.InitialQuoteFunds = &z
c.CurrencySettings[0].SpotDetails.InitialBaseFunds = &z
err = c.validateCurrencySettings()
if !errors.Is(err, errBadInitialFunds) {
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
}
c.CurrencySettings[0].SpotDetails.InitialQuoteFunds = &leet
c.FundingSettings.UseExchangeLevelFunding = true
err = c.validateCurrencySettings()
if !errors.Is(err, errBadInitialFunds) {
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
}
c.CurrencySettings[0].SpotDetails.InitialQuoteFunds = &z
c.CurrencySettings[0].SpotDetails.InitialBaseFunds = &leet
c.FundingSettings.UseExchangeLevelFunding = true
err = c.validateCurrencySettings()
if !errors.Is(err, errBadInitialFunds) {
t.Errorf("received: %v, expected: %v", err, errBadInitialFunds)
}
}
func TestValidateMinMaxes(t *testing.T) {
t.Parallel()
c := &Config{}
err := c.validateMinMaxes()
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
c.CurrencySettings = []CurrencySettings{
{
SellSide: MinMax{
MinimumSize: decimal.NewFromInt(-1),
},
},
}
err = c.validateMinMaxes()
if !errors.Is(err, errSizeLessThanZero) {
t.Errorf("received %v expected %v", err, errSizeLessThanZero)
}
c.CurrencySettings = []CurrencySettings{
{
SellSide: MinMax{
MaximumTotal: decimal.NewFromInt(-1),
},
},
}
err = c.validateMinMaxes()
if !errors.Is(err, errSizeLessThanZero) {
t.Errorf("received %v expected %v", err, errSizeLessThanZero)
}
c.CurrencySettings = []CurrencySettings{
{
SellSide: MinMax{
MaximumSize: decimal.NewFromInt(-1),
},
},
}
err = c.validateMinMaxes()
if !errors.Is(err, errSizeLessThanZero) {
t.Errorf("received %v expected %v", err, errSizeLessThanZero)
}
c.CurrencySettings = []CurrencySettings{
{
BuySide: MinMax{
MinimumSize: decimal.NewFromInt(2),
MaximumTotal: decimal.NewFromInt(10),
MaximumSize: decimal.NewFromInt(1),
},
},
}
err = c.validateMinMaxes()
if !errors.Is(err, errMaxSizeMinSizeMismatch) {
t.Errorf("received %v expected %v", err, errMaxSizeMinSizeMismatch)
}
c.CurrencySettings = []CurrencySettings{
{
BuySide: MinMax{
MinimumSize: decimal.NewFromInt(2),
MaximumSize: decimal.NewFromInt(2),
},
},
}
err = c.validateMinMaxes()
if !errors.Is(err, errMinMaxEqual) {
t.Errorf("received %v expected %v", err, errMinMaxEqual)
}
c.CurrencySettings = []CurrencySettings{
{
BuySide: MinMax{
MinimumSize: decimal.NewFromInt(1),
MaximumTotal: decimal.NewFromInt(10),
MaximumSize: decimal.NewFromInt(2),
},
},
}
c.PortfolioSettings = PortfolioSettings{
BuySide: MinMax{
MinimumSize: decimal.NewFromInt(-1),
},
}
err = c.validateMinMaxes()
if !errors.Is(err, errSizeLessThanZero) {
t.Errorf("received %v expected %v", err, errSizeLessThanZero)
}
c.PortfolioSettings = PortfolioSettings{
SellSide: MinMax{
MinimumSize: decimal.NewFromInt(-1),
},
}
err = c.validateMinMaxes()
if !errors.Is(err, errSizeLessThanZero) {
t.Errorf("received %v expected %v", err, errSizeLessThanZero)
}
}
func TestValidateStrategySettings(t *testing.T) {
t.Parallel()
c := &Config{}
err := c.validateStrategySettings()
if !errors.Is(err, base.ErrStrategyNotFound) {
t.Errorf("received %v expected %v", err, base.ErrStrategyNotFound)
}
c.StrategySettings = StrategySettings{Name: dca}
err = c.validateStrategySettings()
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
c.StrategySettings.SimultaneousSignalProcessing = true
err = c.validateStrategySettings()
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
c.FundingSettings = FundingSettings{}
c.FundingSettings.UseExchangeLevelFunding = true
err = c.validateStrategySettings()
if !errors.Is(err, errExchangeLevelFundingDataRequired) {
t.Errorf("received %v expected %v", err, errExchangeLevelFundingDataRequired)
}
c.FundingSettings.ExchangeLevelFunding = []ExchangeLevelFunding{
{
InitialFunds: decimal.NewFromInt(-1),
},
}
err = c.validateStrategySettings()
if !errors.Is(err, errBadInitialFunds) {
t.Errorf("received %v expected %v", err, errBadInitialFunds)
}
c.StrategySettings.SimultaneousSignalProcessing = false
err = c.validateStrategySettings()
if !errors.Is(err, errSimultaneousProcessingRequired) {
t.Errorf("received %v expected %v", err, errSimultaneousProcessingRequired)
}
c.FundingSettings.UseExchangeLevelFunding = false
err = c.validateStrategySettings()
if !errors.Is(err, errExchangeLevelFundingRequired) {
t.Errorf("received %v expected %v", err, errExchangeLevelFundingRequired)
}
}
func TestPrintSettings(t *testing.T) {
t.Parallel()
cfg := Config{
Nickname: "super fun run",
Goal: "To demonstrate rendering of settings",
StrategySettings: StrategySettings{
Name: dca,
CustomSettings: map[string]interface{}{
"dca-dummy1": 30.0,
"dca-dummy2": 30.0,
"dca-dummy3": 30.0,
},
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds1000000,
InitialBaseFunds: initialFunds1000000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
FuturesDetails: &FuturesDetails{},
},
},
DataSettings: DataSettings{
Interval: kline.OneMin,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate,
InclusiveEndDate: true,
},
CSVData: &CSVData{
FullPath: "fake",
},
LiveData: &LiveData{},
DatabaseData: &DatabaseData{
StartDate: startDate,
EndDate: endDate,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
cfg.PrintSetting()
cfg.FundingSettings = FundingSettings{
UseExchangeLevelFunding: true,
ExchangeLevelFunding: []ExchangeLevelFunding{{}},
}
cfg.PrintSetting()
}
func TestValidate(t *testing.T) {
t.Parallel()
c := &Config{
StrategySettings: StrategySettings{Name: dca},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialBaseFunds: initialFunds10,
InitialQuoteFunds: initialFunds100000,
},
BuySide: MinMax{
MinimumSize: decimal.NewFromInt(1),
MaximumSize: decimal.NewFromInt(10),
MaximumTotal: decimal.NewFromInt(10),
},
},
},
}
err := c.Validate()
if !errors.Is(err, nil) {
t.Errorf("received %v expected %v", err, nil)
}
c = nil
err = c.Validate()
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received %v expected %v", err, gctcommon.ErrNilPointer)
}
}
func TestReadStrategyConfigFromFile(t *testing.T) {
tempDir := t.TempDir()
passFile, err := os.CreateTemp(tempDir, "*.start")
if err != nil {
t.Fatalf("Problem creating temp file at %v: %s\n", passFile, err)
}
_, err = passFile.WriteString("{}")
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
err = passFile.Close()
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
_, err = ReadStrategyConfigFromFile(passFile.Name())
if !errors.Is(err, nil) {
t.Errorf("received: %v, expected: %v", err, nil)
}
_, err = ReadStrategyConfigFromFile("test")
if !errors.Is(err, common.ErrFileNotFound) {
t.Errorf("received '%v' expected '%v'", err, common.ErrFileNotFound)
}
}
func TestGenerateConfigForDCAAPICandles(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyDCAAPICandles",
Goal: "To demonstrate DCA strategy using API candles",
StrategySettings: StrategySettings{
Name: dca,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: "bybit",
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate,
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForPluginStrategy(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExamplePluginStrategy",
Goal: "To demonstrate that custom strategies can be used",
StrategySettings: StrategySettings{
Name: "custom-strategy",
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds1000000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate,
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
Leverage: Leverage{
CanUseLeverage: false,
},
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "custom-plugin-strategy.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCAAPICandlesExchangeLevelFunding(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyDCAAPICandlesExchangeLevelFunding",
Goal: "To demonstrate DCA strategy using API candles using a shared pool of funds",
StrategySettings: StrategySettings{
Name: dca,
SimultaneousSignalProcessing: true,
DisableUSDTracking: true,
},
FundingSettings: FundingSettings{
UseExchangeLevelFunding: true,
ExchangeLevelFunding: []ExchangeLevelFunding{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Currency: mainCurrencyPair.Quote,
InitialFunds: decimal.NewFromInt(100000),
},
},
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.ETH,
Quote: mainCurrencyPair.Quote,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate,
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles-exchange-level-funding.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCAAPITrades(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyDCAAPITrades",
Goal: "To demonstrate running the DCA strategy using API trade data",
StrategySettings: StrategySettings{
Name: dca,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
SkipCandleVolumeFitting: true,
},
},
DataSettings: DataSettings{
Interval: kline.OneHour,
DataType: common.TradeStr,
APIData: &APIData{
StartDate: startDate,
EndDate: tradeEndDate,
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: MinMax{
MinimumSize: decimal.NewFromFloat(0.1),
MaximumSize: decimal.NewFromInt(1),
MaximumTotal: decimal.NewFromInt(10000),
},
SellSide: MinMax{
MinimumSize: decimal.NewFromFloat(0.1),
MaximumSize: decimal.NewFromInt(1),
MaximumTotal: decimal.NewFromInt(10000),
},
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-trades.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCAAPICandlesMultipleCurrencies(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyDCAAPICandlesMultipleCurrencies",
Goal: "To demonstrate running the DCA strategy using the API against multiple currencies candle data",
StrategySettings: StrategySettings{
Name: dca,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.ETH,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate,
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles-multiple-currencies.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCAAPICandlesSimultaneousProcessing(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyDCAAPICandlesSimultaneousProcessing",
Goal: "To demonstrate how simultaneous processing can work",
StrategySettings: StrategySettings{
Name: dca,
SimultaneousSignalProcessing: true,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds1000000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.ETH,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate,
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles-simultaneous-processing.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCALiveCandles(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyDCALiveCandles",
Goal: "To demonstrate live trading proof of concept against candle data",
StrategySettings: StrategySettings{
Name: dca,
DisableUSDTracking: true,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: strictMinMax,
SellSide: strictMinMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneMin,
DataType: common.CandleStr,
LiveData: &LiveData{
NewEventTimeout: time.Minute * 2,
DataCheckTimer: time.Second,
RealOrders: false,
DataRequestRetryTolerance: 3,
DataRequestRetryWaitTime: time.Millisecond * 500,
ExchangeCredentials: []Credentials{
{
Exchange: mainExchange,
Keys: account.Credentials{
Key: "",
Secret: "",
},
},
},
},
},
PortfolioSettings: PortfolioSettings{
BuySide: strictMinMax,
SellSide: strictMinMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-candles-live.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForRSIAPICustomSettings(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "TestGenerateRSICandleAPICustomSettingsStrat",
Goal: "To demonstrate the RSI strategy using API candle data and custom settings",
StrategySettings: StrategySettings{
Name: "rsi",
CustomSettings: map[string]interface{}{
"rsi-low": 30.0,
"rsi-high": 70.0,
"rsi-period": 14,
},
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.ThreeHour,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate.Add(time.Hour), // Now divisible by 3 hour candle
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "rsi-api-candles.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCACSVCandles(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
fp := filepath.Join("..", "testdata", "binance_BTCUSDT_24h_2019_01_01_2020_01_01.csv")
cfg := Config{
Nickname: "ExampleStrategyDCACSVCandles",
Goal: "To demonstrate the DCA strategy using CSV candle data",
StrategySettings: StrategySettings{
Name: dca,
DisableUSDTracking: true,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
CSVData: &CSVData{
FullPath: fp,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-csv-candles.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCACSVTrades(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
fp := filepath.Join("..", "testdata", "binance_BTCUSDT_24h-trades_2020_11_16.csv")
cfg := Config{
Nickname: "ExampleStrategyDCACSVTrades",
Goal: "To demonstrate the DCA strategy using CSV trade data",
StrategySettings: StrategySettings{
Name: dca,
DisableUSDTracking: true,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneMin,
DataType: common.TradeStr,
CSVData: &CSVData{
FullPath: fp,
},
},
PortfolioSettings: PortfolioSettings{},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-csv-trades.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForDCADatabaseCandles(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyDCADatabaseCandles",
Goal: "To demonstrate the DCA strategy using database candle data",
StrategySettings: StrategySettings{
Name: dca,
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
SpotDetails: &SpotDetails{
InitialQuoteFunds: initialFunds100000,
},
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
DatabaseData: &DatabaseData{
StartDate: startDate,
EndDate: endDate,
Config: database.Config{
Enabled: true,
Verbose: false,
Driver: "sqlite",
ConnectionDetails: drivers.ConnectionDetails{
Host: "localhost",
Database: "testsqlite.db",
},
},
InclusiveEndDate: false,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-database-candles.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForTop2Bottom2(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleStrategyTop2Bottom2",
Goal: "To demonstrate a complex strategy using exchange level funding and simultaneous processing of data signals",
StrategySettings: StrategySettings{
Name: top2bottom2.Name,
SimultaneousSignalProcessing: true,
CustomSettings: map[string]interface{}{
"mfi-low": 32,
"mfi-high": 68,
"mfi-period": 14,
},
},
FundingSettings: FundingSettings{
UseExchangeLevelFunding: true,
ExchangeLevelFunding: []ExchangeLevelFunding{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Currency: mainCurrencyPair.Base,
InitialFunds: decimal.NewFromFloat(3),
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Currency: mainCurrencyPair.Quote,
InitialFunds: decimal.NewFromInt(10000),
},
},
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.DOGE,
Quote: mainCurrencyPair.Quote,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.ETH,
Quote: mainCurrencyPair.Base,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.LTC,
Quote: mainCurrencyPair.Base,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.XRP,
Quote: mainCurrencyPair.Quote,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: currency.BNB,
Quote: mainCurrencyPair.Base,
BuySide: minMax,
SellSide: minMax,
MakerFee: &makerFee,
TakerFee: &takerFee,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: startDate,
EndDate: endDate,
},
},
PortfolioSettings: PortfolioSettings{
BuySide: minMax,
SellSide: minMax,
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "t2b2-api-candles-exchange-funding.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateBinanceCashAndCarryStrategy(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleCashAndCarry",
Goal: "To demonstrate a cash and carry strategy",
StrategySettings: StrategySettings{
Name: "binance-cash-carry",
SimultaneousSignalProcessing: true,
},
FundingSettings: FundingSettings{
UseExchangeLevelFunding: true,
ExchangeLevelFunding: []ExchangeLevelFunding{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Currency: mainCurrencyPair.Quote,
InitialFunds: *initialFunds100000,
},
},
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.USDTMarginedFutures,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
MakerFee: &makerFee,
TakerFee: &takerFee,
BuySide: minMax,
SellSide: minMax,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
MakerFee: &makerFee,
TakerFee: &takerFee,
BuySide: minMax,
SellSide: minMax,
},
},
DataSettings: DataSettings{
Interval: kline.OneDay,
DataType: common.CandleStr,
APIData: &APIData{
StartDate: time.Date(2021, 1, 14, 0, 0, 0, 0, time.UTC),
EndDate: time.Date(2021, 9, 24, 0, 0, 0, 0, time.UTC),
InclusiveEndDate: false,
},
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "binance-cash-and-carry.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}
func TestGenerateConfigForLiveCashAndCarry(t *testing.T) {
if !saveConfig {
t.Skip("saveConfig false, skipping")
}
cfg := Config{
Nickname: "ExampleBinanceLiveCashAndCarry",
Goal: "To demonstrate a cash and carry strategy using a live data source",
StrategySettings: StrategySettings{
Name: "binance-cash-carry",
SimultaneousSignalProcessing: true,
},
FundingSettings: FundingSettings{
UseExchangeLevelFunding: true,
ExchangeLevelFunding: []ExchangeLevelFunding{
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Currency: mainCurrencyPair.Quote,
InitialFunds: *initialFunds100000,
},
},
},
CurrencySettings: []CurrencySettings{
{
ExchangeName: mainExchange,
Asset: asset.USDTMarginedFutures,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
MakerFee: &makerFee,
TakerFee: &takerFee,
SkipCandleVolumeFitting: true,
BuySide: strictMinMax,
SellSide: strictMinMax,
},
{
ExchangeName: mainExchange,
Asset: asset.Spot,
Base: mainCurrencyPair.Base,
Quote: mainCurrencyPair.Quote,
MakerFee: &makerFee,
TakerFee: &takerFee,
SkipCandleVolumeFitting: true,
BuySide: strictMinMax,
SellSide: strictMinMax,
},
},
DataSettings: DataSettings{
Interval: kline.FifteenSecond,
DataType: common.CandleStr,
LiveData: &LiveData{
NewEventTimeout: time.Minute,
DataCheckTimer: time.Second,
RealOrders: false,
DataRequestRetryTolerance: 3,
ClosePositionsOnStop: true,
DataRequestRetryWaitTime: time.Millisecond * 500,
ExchangeCredentials: []Credentials{
{
Exchange: mainExchange,
Keys: account.Credentials{
Key: "",
Secret: "",
SubAccount: "",
},
},
},
},
},
StatisticSettings: StatisticSettings{
RiskFreeRate: decimal.NewFromFloat(0.03),
},
}
if saveConfig {
result, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
t.Fatal(err)
}
p, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(p, "strategyexamples", "binance-live-cash-and-carry.strat"), result, file.DefaultPermissionOctal)
if err != nil {
t.Error(err)
}
}
}