mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-19 23:16:48 +00:00
* Initial concept for creating price tracking pairs * Completes coverage, even with a slow test * I dont know what point to hook this stuff up * Bit of a broken way of handling tracking pairs * Correctly calculates USD rates against all currencies * Removes dependency on GCT config * Failed currency statistics redesign * initial Update chart to use highcharts * Minor changes to stats * Creats funding stats to handle the stat calculations. Needs more work * tracks USD snapshots and BREAKS THINGS FURTHER * Fixed! * Adds ratio calculations and such, but its WRONG. do it at totals level dummy * End of day basic lint * Remaining lints * USD totals statistics * Minor panic fixes * Printing of funding stats, but its bad * Properly calculates overall benchmark, moves funding stat output * Adds some template charge, removes duplicate fields * New charts! * Darkcharts. funding protection when disabled * Now works with usd tracking/funding disabled! * Attempting to only show working stats based on settings. * Spruces up the goose/reporting * Completes report HTML rendering * lint and test fixes * funding statistics testing * slightly more test coverage * Test coverage * Initial documentation * Fixes tests * Database testing and rendering improvements and breakages * report and cmd rendering, linting. fix comma output. rm gct cfg * PR mode 🎉 Path field, config builder support,testing,linting,docs * minor calculation improvement * Secret lint that did not show up locally * Disable USD tracking for example configs * ShazNitNoScope * Forgotten errors * "" * literally Logarithmically logically renders the date 👀 * Fixes typos, fixes parallel test, fixes chart gui and exporting
325 lines
14 KiB
Go
325 lines
14 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
"github.com/shopspring/decimal"
|
|
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies"
|
|
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base"
|
|
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
|
|
"github.com/thrasher-corp/gocryptotrader/common/file"
|
|
"github.com/thrasher-corp/gocryptotrader/log"
|
|
)
|
|
|
|
// ReadConfigFromFile will take a config from a path
|
|
func ReadConfigFromFile(path string) (*Config, error) {
|
|
if !file.Exists(path) {
|
|
return nil, errors.New("file not found")
|
|
}
|
|
|
|
fileData, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return LoadConfig(fileData)
|
|
}
|
|
|
|
// LoadConfig unmarshalls byte data into a config struct
|
|
func LoadConfig(data []byte) (resp *Config, err error) {
|
|
err = json.Unmarshal(data, &resp)
|
|
return resp, err
|
|
}
|
|
|
|
// PrintSetting prints relevant settings to the console for easy reading
|
|
func (c *Config) PrintSetting() {
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------Backtester Settings------------------------")
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------Strategy Settings--------------------------")
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Infof(log.BackTester, "Strategy: %s", c.StrategySettings.Name)
|
|
if len(c.StrategySettings.CustomSettings) > 0 {
|
|
log.Info(log.BackTester, "Custom strategy variables:")
|
|
for k, v := range c.StrategySettings.CustomSettings {
|
|
log.Infof(log.BackTester, "%s: %v", k, v)
|
|
}
|
|
} else {
|
|
log.Info(log.BackTester, "Custom strategy variables: unset")
|
|
}
|
|
log.Infof(log.BackTester, "Simultaneous Signal Processing: %v", c.StrategySettings.SimultaneousSignalProcessing)
|
|
log.Infof(log.BackTester, "Use Exchange Level Funding: %v", c.StrategySettings.UseExchangeLevelFunding)
|
|
log.Infof(log.BackTester, "USD value tracking: %v", !c.StrategySettings.DisableUSDTracking)
|
|
if c.StrategySettings.UseExchangeLevelFunding && c.StrategySettings.SimultaneousSignalProcessing {
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------Funding Settings---------------------------")
|
|
for i := range c.StrategySettings.ExchangeLevelFunding {
|
|
log.Infof(log.BackTester, "Initial funds for %v %v %v: %v",
|
|
c.StrategySettings.ExchangeLevelFunding[i].ExchangeName,
|
|
c.StrategySettings.ExchangeLevelFunding[i].Asset,
|
|
c.StrategySettings.ExchangeLevelFunding[i].Currency,
|
|
c.StrategySettings.ExchangeLevelFunding[i].InitialFunds.Round(8))
|
|
}
|
|
}
|
|
|
|
for i := range c.CurrencySettings {
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
currStr := fmt.Sprintf("------------------%v %v-%v Currency Settings---------------------------------------------------------",
|
|
c.CurrencySettings[i].Asset,
|
|
c.CurrencySettings[i].Base,
|
|
c.CurrencySettings[i].Quote)
|
|
log.Infof(log.BackTester, currStr[:61])
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Infof(log.BackTester, "Exchange: %v", c.CurrencySettings[i].ExchangeName)
|
|
if !c.StrategySettings.UseExchangeLevelFunding {
|
|
if c.CurrencySettings[i].InitialBaseFunds != nil {
|
|
log.Infof(log.BackTester, "Initial base funds: %v %v",
|
|
c.CurrencySettings[i].InitialBaseFunds.Round(8),
|
|
c.CurrencySettings[i].Base)
|
|
}
|
|
if c.CurrencySettings[i].InitialQuoteFunds != nil {
|
|
log.Infof(log.BackTester, "Initial quote funds: %v %v",
|
|
c.CurrencySettings[i].InitialQuoteFunds.Round(8),
|
|
c.CurrencySettings[i].Quote)
|
|
}
|
|
}
|
|
log.Infof(log.BackTester, "Maker fee: %v", c.CurrencySettings[i].TakerFee.Round(8))
|
|
log.Infof(log.BackTester, "Taker fee: %v", c.CurrencySettings[i].MakerFee.Round(8))
|
|
log.Infof(log.BackTester, "Minimum slippage percent %v", c.CurrencySettings[i].MinimumSlippagePercent.Round(8))
|
|
log.Infof(log.BackTester, "Maximum slippage percent: %v", c.CurrencySettings[i].MaximumSlippagePercent.Round(8))
|
|
log.Infof(log.BackTester, "Buy rules: %+v", c.CurrencySettings[i].BuySide)
|
|
log.Infof(log.BackTester, "Sell rules: %+v", c.CurrencySettings[i].SellSide)
|
|
log.Infof(log.BackTester, "Leverage rules: %+v", c.CurrencySettings[i].Leverage)
|
|
log.Infof(log.BackTester, "Can use exchange defined order execution limits: %+v", c.CurrencySettings[i].CanUseExchangeLimits)
|
|
}
|
|
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------Portfolio Settings-------------------------")
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Infof(log.BackTester, "Buy rules: %+v", c.PortfolioSettings.BuySide)
|
|
log.Infof(log.BackTester, "Sell rules: %+v", c.PortfolioSettings.SellSide)
|
|
log.Infof(log.BackTester, "Leverage rules: %+v", c.PortfolioSettings.Leverage)
|
|
if c.DataSettings.LiveData != nil {
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------Live Settings------------------------------")
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Infof(log.BackTester, "Data type: %v", c.DataSettings.DataType)
|
|
log.Infof(log.BackTester, "Interval: %v", c.DataSettings.Interval)
|
|
log.Infof(log.BackTester, "REAL ORDERS: %v", c.DataSettings.LiveData.RealOrders)
|
|
log.Infof(log.BackTester, "Overriding GCT API settings: %v", c.DataSettings.LiveData.APIClientIDOverride != "")
|
|
}
|
|
if c.DataSettings.APIData != nil {
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------API Settings-------------------------------")
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Infof(log.BackTester, "Data type: %v", c.DataSettings.DataType)
|
|
log.Infof(log.BackTester, "Interval: %v", c.DataSettings.Interval)
|
|
log.Infof(log.BackTester, "Start date: %v", c.DataSettings.APIData.StartDate.Format(gctcommon.SimpleTimeFormat))
|
|
log.Infof(log.BackTester, "End date: %v", c.DataSettings.APIData.EndDate.Format(gctcommon.SimpleTimeFormat))
|
|
}
|
|
if c.DataSettings.CSVData != nil {
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------CSV Settings-------------------------------")
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Infof(log.BackTester, "Data type: %v", c.DataSettings.DataType)
|
|
log.Infof(log.BackTester, "Interval: %v", c.DataSettings.Interval)
|
|
log.Infof(log.BackTester, "CSV file: %v", c.DataSettings.CSVData.FullPath)
|
|
}
|
|
if c.DataSettings.DatabaseData != nil {
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Info(log.BackTester, "------------------Database Settings--------------------------")
|
|
log.Info(log.BackTester, "-------------------------------------------------------------")
|
|
log.Infof(log.BackTester, "Data type: %v", c.DataSettings.DataType)
|
|
log.Infof(log.BackTester, "Interval: %v", c.DataSettings.Interval)
|
|
log.Infof(log.BackTester, "Start date: %v", c.DataSettings.DatabaseData.StartDate.Format(gctcommon.SimpleTimeFormat))
|
|
log.Infof(log.BackTester, "End date: %v", c.DataSettings.DatabaseData.EndDate.Format(gctcommon.SimpleTimeFormat))
|
|
}
|
|
log.Info(log.BackTester, "-------------------------------------------------------------\n\n")
|
|
}
|
|
|
|
// Validate checks all config settings
|
|
func (c *Config) Validate() error {
|
|
err := c.validateDate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = c.validateStrategySettings()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = c.validateCurrencySettings()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.validateMinMaxes()
|
|
}
|
|
|
|
// validate ensures no one sets bad config values on purpose
|
|
func (m *MinMax) validate() error {
|
|
if m.MaximumSize.IsNegative() {
|
|
return fmt.Errorf("invalid maximum size %w", errSizeLessThanZero)
|
|
}
|
|
if m.MinimumSize.IsNegative() {
|
|
return fmt.Errorf("invalid minimum size %w", errSizeLessThanZero)
|
|
}
|
|
if m.MaximumTotal.IsNegative() {
|
|
return fmt.Errorf("invalid maximum total set to %w", errSizeLessThanZero)
|
|
}
|
|
if m.MaximumSize.LessThan(m.MinimumSize) && !m.MinimumSize.IsZero() && !m.MaximumSize.IsZero() {
|
|
return fmt.Errorf("%w maximum size %v vs minimum size %v",
|
|
errMaxSizeMinSizeMismatch,
|
|
m.MaximumSize,
|
|
m.MinimumSize)
|
|
}
|
|
if m.MaximumSize.Equal(m.MinimumSize) && !m.MinimumSize.IsZero() && !m.MaximumSize.IsZero() {
|
|
return fmt.Errorf("%w %v",
|
|
errMinMaxEqual,
|
|
m.MinimumSize)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) validateMinMaxes() (err error) {
|
|
for i := range c.CurrencySettings {
|
|
err = c.CurrencySettings[i].BuySide.validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = c.CurrencySettings[i].SellSide.validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err = c.PortfolioSettings.BuySide.validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = c.PortfolioSettings.SellSide.validate()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) validateStrategySettings() error {
|
|
if c.StrategySettings.UseExchangeLevelFunding && !c.StrategySettings.SimultaneousSignalProcessing {
|
|
return errSimultaneousProcessingRequired
|
|
}
|
|
if len(c.StrategySettings.ExchangeLevelFunding) > 0 && !c.StrategySettings.UseExchangeLevelFunding {
|
|
return errExchangeLevelFundingRequired
|
|
}
|
|
if c.StrategySettings.UseExchangeLevelFunding && len(c.StrategySettings.ExchangeLevelFunding) == 0 {
|
|
return errExchangeLevelFundingDataRequired
|
|
}
|
|
if c.StrategySettings.UseExchangeLevelFunding {
|
|
for i := range c.StrategySettings.ExchangeLevelFunding {
|
|
if c.StrategySettings.ExchangeLevelFunding[i].InitialFunds.IsNegative() {
|
|
return fmt.Errorf("%w for %v %v %v",
|
|
errBadInitialFunds,
|
|
c.StrategySettings.ExchangeLevelFunding[i].ExchangeName,
|
|
c.StrategySettings.ExchangeLevelFunding[i].Asset,
|
|
c.StrategySettings.ExchangeLevelFunding[i].Currency,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
strats := strategies.GetStrategies()
|
|
for i := range strats {
|
|
if strings.EqualFold(strats[i].Name(), c.StrategySettings.Name) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("strategty %v %w", c.StrategySettings.Name, base.ErrStrategyNotFound)
|
|
}
|
|
|
|
// validateDate checks whether someone has set a date poorly in their config
|
|
func (c *Config) validateDate() error {
|
|
if c.DataSettings.DatabaseData != nil {
|
|
if c.DataSettings.DatabaseData.StartDate.IsZero() ||
|
|
c.DataSettings.DatabaseData.EndDate.IsZero() {
|
|
return errStartEndUnset
|
|
}
|
|
if c.DataSettings.DatabaseData.StartDate.After(c.DataSettings.DatabaseData.EndDate) ||
|
|
c.DataSettings.DatabaseData.StartDate.Equal(c.DataSettings.DatabaseData.EndDate) {
|
|
return errBadDate
|
|
}
|
|
}
|
|
if c.DataSettings.APIData != nil {
|
|
if c.DataSettings.APIData.StartDate.IsZero() ||
|
|
c.DataSettings.APIData.EndDate.IsZero() {
|
|
return errStartEndUnset
|
|
}
|
|
if c.DataSettings.APIData.StartDate.After(c.DataSettings.APIData.EndDate) ||
|
|
c.DataSettings.APIData.StartDate.Equal(c.DataSettings.APIData.EndDate) {
|
|
return errBadDate
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateCurrencySettings checks whether someone has set invalid currency setting data in their config
|
|
func (c *Config) validateCurrencySettings() error {
|
|
if len(c.CurrencySettings) == 0 {
|
|
return errNoCurrencySettings
|
|
}
|
|
for i := range c.CurrencySettings {
|
|
if c.CurrencySettings[i].InitialLegacyFunds > 0 {
|
|
// temporarily migrate legacy start config value
|
|
log.Warn(log.BackTester, "config field 'initial-funds' no longer supported, please use 'initial-quote-funds'")
|
|
log.Warnf(log.BackTester, "temporarily setting 'initial-quote-funds' to 'initial-funds' value of %v", c.CurrencySettings[i].InitialLegacyFunds)
|
|
iqf := decimal.NewFromFloat(c.CurrencySettings[i].InitialLegacyFunds)
|
|
c.CurrencySettings[i].InitialQuoteFunds = &iqf
|
|
}
|
|
if c.StrategySettings.UseExchangeLevelFunding {
|
|
if c.CurrencySettings[i].InitialQuoteFunds != nil &&
|
|
c.CurrencySettings[i].InitialQuoteFunds.GreaterThan(decimal.Zero) {
|
|
return fmt.Errorf("non-nil quote %w", errBadInitialFunds)
|
|
}
|
|
if c.CurrencySettings[i].InitialBaseFunds != nil &&
|
|
c.CurrencySettings[i].InitialBaseFunds.GreaterThan(decimal.Zero) {
|
|
return fmt.Errorf("non-nil base %w", errBadInitialFunds)
|
|
}
|
|
} else {
|
|
if c.CurrencySettings[i].InitialQuoteFunds == nil &&
|
|
c.CurrencySettings[i].InitialBaseFunds == nil {
|
|
return fmt.Errorf("nil base and quote %w", errBadInitialFunds)
|
|
}
|
|
if c.CurrencySettings[i].InitialQuoteFunds != nil &&
|
|
c.CurrencySettings[i].InitialBaseFunds != nil &&
|
|
c.CurrencySettings[i].InitialBaseFunds.IsZero() &&
|
|
c.CurrencySettings[i].InitialQuoteFunds.IsZero() {
|
|
return fmt.Errorf("base or quote funds set to zero %w", errBadInitialFunds)
|
|
}
|
|
if c.CurrencySettings[i].InitialQuoteFunds == nil {
|
|
c.CurrencySettings[i].InitialQuoteFunds = &decimal.Zero
|
|
}
|
|
if c.CurrencySettings[i].InitialBaseFunds == nil {
|
|
c.CurrencySettings[i].InitialBaseFunds = &decimal.Zero
|
|
}
|
|
}
|
|
|
|
if c.CurrencySettings[i].Base == "" {
|
|
return errUnsetCurrency
|
|
}
|
|
if c.CurrencySettings[i].Asset == "" {
|
|
return errUnsetAsset
|
|
}
|
|
if c.CurrencySettings[i].ExchangeName == "" {
|
|
return errUnsetExchange
|
|
}
|
|
if c.CurrencySettings[i].MinimumSlippagePercent.LessThan(decimal.Zero) ||
|
|
c.CurrencySettings[i].MaximumSlippagePercent.LessThan(decimal.Zero) ||
|
|
c.CurrencySettings[i].MinimumSlippagePercent.GreaterThan(c.CurrencySettings[i].MaximumSlippagePercent) {
|
|
return errBadSlippageRates
|
|
}
|
|
c.CurrencySettings[i].ExchangeName = strings.ToLower(c.CurrencySettings[i].ExchangeName)
|
|
}
|
|
return nil
|
|
}
|