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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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()
	}
	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)
		}
	}
}
