Files
gocryptotrader/backtester/btcli/commands.go
Scott 017cdf1384 Backtester: Live trading upgrades (#1023)
* Modifications for a smoother live run

* Fixes data appending

* Successfully allows multi-currency live trading. Adds multiple currencies to live DCA strategy

* Attempting to get cash and carry working

* Poor attempts at sorting out data and appending it properly with USD in mind

* =designs new live data handler

* Updates cash and carry strat to work

* adds test coverage. begins closeallpositions function

* Updates cash and carry to work live

* New kline.Event type. Cancels orders on close. Rn types

* =Fixes USD funding issue

* =fixes tests

* fixes tests AGAIN

* adds coverage to close all orders

* crummy tests, should override

* more tests

* more tests

* more coverage

* removes scourge of currency.Pair maps. More tests

* missed currency stuff

* Fixes USD data issue & collateral issue. Needs to close ALL orders

* Now triggers updates on the very first data entry

* All my problems are solved now????

* fixes tests, extends coverage

* there is some really funky candle stuff going on

* my brain is melting

* better shutdown management, fixes freezing bug

* fixes data duplication issues, adds retries to requests

* reduces logging, adds verbose options

* expands coverage over all new functionality

* fixes fun bug from curr == curr to curr.Equal(curr)

* fixes setup issues and tests

* starts adding external wallet amounts for funding

* more setup for assets

* setup live fund calcs and placing orders

* successfully performs automated cash and carry

* merge fixes

* funding properly set at all times

* fixes some bugs, need to address currencystatistics still

* adds 'appeneded' trait, attempts to fix some stats

* fixes stat bugs, adds cool new fetchfees feature

* fixes terrible processing bugs

* tightens realorder stats, sadly loses some live stats

* this actually sets everything correctly for bothcd ..cd ..cd ..cd ..cd ..!

* fix tests

* coverage

* beautiful new test coverage

* docs

* adds new fee getter delayer

* commits from the correct directory

* Lint

* adds verbose to fund manager

* Fix bug in t2b2 strat. Update dca live config. Docs

* go mod tidy

* update buf

* buf + test improvement

* Post merge fixes

* fixes surprise offset bug

* fix sizing restrictions for cash and carry

* fix server lints

* merge fixes

* test fixesss

* lintle fixles

* slowloris

* rn run to task, bug fixes, close all on close

* rpc lint and fixes

* bugfix: order manager not processing orders properly

* somewhat addresses nits

* absolutely broken end of day commit

* absolutely massive knockon effects from nits

* massive knockon effects continue

* fixes things

* address remaining nits

* jk now fixes things

* addresses the easier nits

* more nit fixers

* more niterinos addressederinos

* refactors holdings and does some nits

* so buf

* addresses some nits, fixes holdings bugs

* cleanup

* attempts to fix alert chans to prevent many chans waiting?

* terrible code, will revert

* to be reviewed in detail tomorrow

* Fixes up channel system

* smashes those nits

* fixes extra candles, fixes collateral bug, tests

* fixes data races, introduces reflection

* more checks n tests

* Fixes cash and carry issues. Fixes more cool bugs

* fixes ~typer~ typo

* replace spot strats from ftx to binance

* fixes all the tests I just destroyed

* removes example path, rm verbose

* 1) what 2) removes FTX references from the Backtester

* renamed, non-working strategies

* Removes FTX references almost as fast as sbf removes funds

* regen docs, add contrib names,sort contrib names

* fixes merge renamings

* Addresses nits. Fixes setting API credentials. Fixes Binance limit retrieval

* Fixes live order bugs with real orders and without

* Apply suggestions from code review

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/engine/live.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/engine/live.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update backtester/config/strategyconfigbuilder/main.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* updates docs

* even better docs

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
2023-01-05 13:03:17 +11:00

565 lines
17 KiB
Go

package main
import (
"fmt"
"path/filepath"
"github.com/thrasher-corp/gocryptotrader/backtester/btrpc"
"github.com/thrasher-corp/gocryptotrader/backtester/config"
"github.com/urfave/cli/v2"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
doNotRunFlag = &cli.BoolFlag{
Name: "donotrunimmediately",
Aliases: []string{"dnr"},
Usage: "if true, will load the strategy, but will not execute until another command is sent",
}
doNotStoreFlag = &cli.BoolFlag{
Name: "donotstore",
Aliases: []string{"dns"},
Usage: "if true, will not store the run internally - cannot be run in conjunction with dnr",
}
)
var executeStrategyFromFileCommand = &cli.Command{
Name: "executestrategyfromfile",
Usage: "runs the strategy from a config file",
ArgsUsage: "<path>",
Action: executeStrategyFromFile,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "path",
Aliases: []string{"p"},
Usage: "the filepath to a strategy to execute",
},
doNotRunFlag,
doNotStoreFlag,
},
}
func executeStrategyFromFile(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
var path string
if c.IsSet("path") {
path = c.String("path")
} else {
path = c.Args().First()
}
var dnr bool
if c.IsSet("donotrunimmediately") {
dnr = c.Bool("donotrunimmediately")
}
var dns bool
if c.IsSet("donotstore") {
dns = c.Bool("donotstore")
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ExecuteStrategyFromFile(
c.Context,
&btrpc.ExecuteStrategyFromFileRequest{
StrategyFilePath: path,
DoNotRunImmediately: dnr,
DoNotStore: dns,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var listAllTasksCommand = &cli.Command{
Name: "listalltasks",
Usage: "returns a list of all loaded strategy tasks",
Action: listAllTasks,
}
func listAllTasks(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ListAllTasks(
c.Context,
&btrpc.ListAllTasksRequest{},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var startTaskCommand = &cli.Command{
Name: "starttask",
Usage: "executes a strategy task loaded into the server",
ArgsUsage: "<id>",
Action: startTask,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
Usage: "the id of the strategy task",
},
},
}
func startTask(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
var id string
if c.IsSet("id") {
id = c.String("id")
} else {
id = c.Args().First()
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StartTask(
c.Context,
&btrpc.StartTaskRequest{
Id: id,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var startAllTasksCommand = &cli.Command{
Name: "startalltasks",
Usage: "executes all strategies loaded into the server that have not been run",
Action: startAllTasks,
}
func startAllTasks(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StartAllTasks(
c.Context,
&btrpc.StartAllTasksRequest{},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var stopTaskCommand = &cli.Command{
Name: "stoptask",
Usage: "stops a strategy loaded into the server",
ArgsUsage: "<id>",
Action: stopTask,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
Usage: "the id of the strategy task",
},
},
}
func stopTask(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
var id string
if c.IsSet("id") {
id = c.String("id")
} else {
id = c.Args().First()
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StopTask(
c.Context,
&btrpc.StopTaskRequest{
Id: id,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var stopAllTasksCommand = &cli.Command{
Name: "stopalltasks",
Usage: "stops all strategies loaded into the server",
Action: stopAllTasks,
}
func stopAllTasks(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.StopAllTasks(
c.Context,
&btrpc.StopAllTasksRequest{},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var clearTaskCommand = &cli.Command{
Name: "cleartask",
Usage: "clears/deletes a strategy loaded into the server - if it is not running",
ArgsUsage: "<id>",
Action: clearTask,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
Usage: "the id of the strategy task",
},
},
}
func clearTask(c *cli.Context) error {
if c.NArg() == 0 && c.NumFlags() == 0 {
return cli.ShowSubcommandHelp(c)
}
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
var id string
if c.IsSet("id") {
id = c.String("id")
} else {
id = c.Args().First()
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ClearTask(
c.Context,
&btrpc.ClearTaskRequest{
Id: id,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var clearAllTasksCommand = &cli.Command{
Name: "clearalltasks",
Usage: "clears all strategies loaded into the server. Only tasks not actively running will be cleared",
Action: clearAllTasks,
}
func clearAllTasks(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ClearAllTasks(
c.Context,
&btrpc.ClearAllTasksRequest{},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}
var executeStrategyFromConfigCommand = &cli.Command{
Name: "executestrategyfromconfig",
Usage: fmt.Sprintf("runs the default strategy config but via passing in as a struct instead of a filepath - this is a proof-of-concept implementation using %v", filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat")),
Description: "the cli is not a good place to manage this type of command with n variables to pass in from a command line",
Action: executeStrategyFromConfig,
Flags: []cli.Flag{
doNotRunFlag,
doNotStoreFlag,
},
}
// executeStrategyFromConfig this is a proof of concept command
// it demonstrates that a user can send complex strategies via GRPC
// and have them execute. The ultimate goal is to allow a user to continuously
// tweak values and send them via GRPC and determine the best returns then test them across
// large ranges of data to avoid over fitting
func executeStrategyFromConfig(c *cli.Context) error {
conn, cancel, err := setupClient(c)
if err != nil {
return err
}
defer closeConn(conn, cancel)
defaultPath := filepath.Join(
"..",
"config",
"strategyexamples",
"dca-api-candles.strat")
defaultConfig, err := config.ReadStrategyConfigFromFile(defaultPath)
if err != nil {
return err
}
customSettings := make([]*btrpc.CustomSettings, len(defaultConfig.StrategySettings.CustomSettings))
x := 0
for k, v := range defaultConfig.StrategySettings.CustomSettings {
customSettings[x] = &btrpc.CustomSettings{
KeyField: k,
KeyValue: fmt.Sprintf("%v", v),
}
x++
}
currencySettings := make([]*btrpc.CurrencySettings, len(defaultConfig.CurrencySettings))
for i := range defaultConfig.CurrencySettings {
var sd btrpc.SpotDetails
if defaultConfig.CurrencySettings[i].SpotDetails != nil {
if defaultConfig.CurrencySettings[i].SpotDetails.InitialBaseFunds != nil {
sd.InitialBaseFunds = defaultConfig.CurrencySettings[i].SpotDetails.InitialBaseFunds.String()
}
if defaultConfig.CurrencySettings[i].SpotDetails.InitialQuoteFunds != nil {
sd.InitialQuoteFunds = defaultConfig.CurrencySettings[i].SpotDetails.InitialQuoteFunds.String()
}
}
var fd btrpc.FuturesDetails
if defaultConfig.CurrencySettings[i].FuturesDetails != nil {
fd.Leverage = &btrpc.Leverage{
CanUseLeverage: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.CanUseLeverage,
MaximumOrdersWithLeverageRatio: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrdersWithLeverageRatio.String(),
MaximumLeverageRate: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrderLeverageRate.String(),
MaximumCollateralLeverageRate: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.MaximumCollateralLeverageRate.String(),
}
}
currencySettings[i] = &btrpc.CurrencySettings{
ExchangeName: defaultConfig.CurrencySettings[i].ExchangeName,
Asset: defaultConfig.CurrencySettings[i].Asset.String(),
Base: defaultConfig.CurrencySettings[i].Base.String(),
Quote: defaultConfig.CurrencySettings[i].Quote.String(),
BuySide: &btrpc.PurchaseSide{
MinimumSize: defaultConfig.CurrencySettings[i].BuySide.MinimumSize.String(),
MaximumSize: defaultConfig.CurrencySettings[i].BuySide.MaximumSize.String(),
MaximumTotal: defaultConfig.CurrencySettings[i].BuySide.MaximumTotal.String(),
},
SellSide: &btrpc.PurchaseSide{
MinimumSize: defaultConfig.CurrencySettings[i].SellSide.MinimumSize.String(),
MaximumSize: defaultConfig.CurrencySettings[i].SellSide.MaximumSize.String(),
MaximumTotal: defaultConfig.CurrencySettings[i].SellSide.MaximumTotal.String(),
},
MinSlippagePercent: defaultConfig.CurrencySettings[i].MinimumSlippagePercent.String(),
MaxSlippagePercent: defaultConfig.CurrencySettings[i].MaximumSlippagePercent.String(),
MakerFeeOverride: defaultConfig.CurrencySettings[i].MakerFee.String(),
TakerFeeOverride: defaultConfig.CurrencySettings[i].TakerFee.String(),
MaximumHoldingsRatio: defaultConfig.CurrencySettings[i].MaximumHoldingsRatio.String(),
SkipCandleVolumeFitting: defaultConfig.CurrencySettings[i].SkipCandleVolumeFitting,
UseExchangeOrderLimits: defaultConfig.CurrencySettings[i].CanUseExchangeLimits,
UseExchangePnlCalculation: defaultConfig.CurrencySettings[i].UseExchangePNLCalculation,
}
if sd.InitialQuoteFunds != "" || sd.InitialBaseFunds != "" {
currencySettings[i].SpotDetails = &sd
}
if fd.Leverage != nil {
currencySettings[i].FuturesDetails = &fd
}
}
exchangeLevelFunding := make([]*btrpc.ExchangeLevelFunding, len(defaultConfig.FundingSettings.ExchangeLevelFunding))
for i := range defaultConfig.FundingSettings.ExchangeLevelFunding {
exchangeLevelFunding[i] = &btrpc.ExchangeLevelFunding{
ExchangeName: defaultConfig.FundingSettings.ExchangeLevelFunding[i].ExchangeName,
Asset: defaultConfig.FundingSettings.ExchangeLevelFunding[i].Asset.String(),
Currency: defaultConfig.FundingSettings.ExchangeLevelFunding[i].Currency.String(),
InitialFunds: defaultConfig.FundingSettings.ExchangeLevelFunding[i].InitialFunds.String(),
TransferFee: defaultConfig.FundingSettings.ExchangeLevelFunding[i].TransferFee.String(),
}
}
dataSettings := &btrpc.DataSettings{
Interval: uint64(defaultConfig.DataSettings.Interval.Duration().Nanoseconds()),
Datatype: defaultConfig.DataSettings.DataType,
}
if defaultConfig.DataSettings.APIData != nil {
dataSettings.ApiData = &btrpc.ApiData{
StartDate: timestamppb.New(defaultConfig.DataSettings.APIData.StartDate),
EndDate: timestamppb.New(defaultConfig.DataSettings.APIData.EndDate),
InclusiveEndDate: defaultConfig.DataSettings.APIData.InclusiveEndDate,
}
}
if defaultConfig.DataSettings.LiveData != nil {
creds := make([]*btrpc.ExchangeCredentials, len(defaultConfig.DataSettings.LiveData.ExchangeCredentials))
for i := range defaultConfig.DataSettings.LiveData.ExchangeCredentials {
creds[i] = &btrpc.ExchangeCredentials{
Exchange: defaultConfig.DataSettings.LiveData.ExchangeCredentials[i].Exchange,
Keys: &btrpc.ExchangeKeys{
Key: defaultConfig.DataSettings.LiveData.ExchangeCredentials[i].Keys.Key,
Secret: defaultConfig.DataSettings.LiveData.ExchangeCredentials[i].Keys.Secret,
ClientId: defaultConfig.DataSettings.LiveData.ExchangeCredentials[i].Keys.ClientID,
PemKey: defaultConfig.DataSettings.LiveData.ExchangeCredentials[i].Keys.PEMKey,
SubAccount: defaultConfig.DataSettings.LiveData.ExchangeCredentials[i].Keys.SubAccount,
OneTimePassword: defaultConfig.DataSettings.LiveData.ExchangeCredentials[i].Keys.OneTimePassword,
},
}
}
dataSettings.LiveData = &btrpc.LiveData{
NewEventTimeout: defaultConfig.DataSettings.LiveData.NewEventTimeout.Nanoseconds(),
DataCheckTimer: defaultConfig.DataSettings.LiveData.DataCheckTimer.Nanoseconds(),
RealOrders: defaultConfig.DataSettings.LiveData.RealOrders,
ClosePositionsOnStop: defaultConfig.DataSettings.LiveData.ClosePositionsOnStop,
DataRequestRetryTolerance: defaultConfig.DataSettings.LiveData.DataRequestRetryTolerance,
DataRequestRetryWaitTime: defaultConfig.DataSettings.LiveData.DataRequestRetryWaitTime.Nanoseconds(),
Credentials: creds,
}
}
if defaultConfig.DataSettings.CSVData != nil {
dataSettings.CsvData = &btrpc.CSVData{
Path: defaultConfig.DataSettings.CSVData.FullPath,
}
}
if defaultConfig.DataSettings.DatabaseData != nil {
dbConnectionDetails := &btrpc.DatabaseConnectionDetails{
Host: defaultConfig.DataSettings.DatabaseData.Config.Host,
Port: uint32(defaultConfig.DataSettings.DatabaseData.Config.Port),
Password: defaultConfig.DataSettings.DatabaseData.Config.Password,
Database: defaultConfig.DataSettings.DatabaseData.Config.Database,
SslMode: defaultConfig.DataSettings.DatabaseData.Config.SSLMode,
UserName: defaultConfig.DataSettings.DatabaseData.Config.Username,
}
dbConfig := &btrpc.DatabaseConfig{
Config: dbConnectionDetails,
}
dataSettings.DatabaseData = &btrpc.DatabaseData{
StartDate: timestamppb.New(defaultConfig.DataSettings.DatabaseData.StartDate),
EndDate: timestamppb.New(defaultConfig.DataSettings.DatabaseData.EndDate),
Config: dbConfig,
Path: defaultConfig.DataSettings.DatabaseData.Path,
InclusiveEndDate: defaultConfig.DataSettings.DatabaseData.InclusiveEndDate,
}
}
cfg := &btrpc.Config{
Nickname: defaultConfig.Nickname,
Goal: defaultConfig.Goal,
StrategySettings: &btrpc.StrategySettings{
Name: defaultConfig.StrategySettings.Name,
UseSimultaneousSignalProcessing: defaultConfig.StrategySettings.SimultaneousSignalProcessing,
DisableUsdTracking: defaultConfig.StrategySettings.DisableUSDTracking,
CustomSettings: customSettings,
},
FundingSettings: &btrpc.FundingSettings{
UseExchangeLevelFunding: defaultConfig.FundingSettings.UseExchangeLevelFunding,
ExchangeLevelFunding: exchangeLevelFunding,
},
CurrencySettings: currencySettings,
DataSettings: dataSettings,
PortfolioSettings: &btrpc.PortfolioSettings{
Leverage: &btrpc.Leverage{
CanUseLeverage: defaultConfig.PortfolioSettings.Leverage.CanUseLeverage,
MaximumOrdersWithLeverageRatio: defaultConfig.PortfolioSettings.Leverage.MaximumOrdersWithLeverageRatio.String(),
MaximumLeverageRate: defaultConfig.PortfolioSettings.Leverage.MaximumOrderLeverageRate.String(),
MaximumCollateralLeverageRate: defaultConfig.PortfolioSettings.Leverage.MaximumCollateralLeverageRate.String(),
},
BuySide: &btrpc.PurchaseSide{
MinimumSize: defaultConfig.PortfolioSettings.BuySide.MinimumSize.String(),
MaximumSize: defaultConfig.PortfolioSettings.BuySide.MaximumSize.String(),
MaximumTotal: defaultConfig.PortfolioSettings.BuySide.MaximumTotal.String(),
},
SellSide: &btrpc.PurchaseSide{
MinimumSize: defaultConfig.PortfolioSettings.SellSide.MinimumSize.String(),
MaximumSize: defaultConfig.PortfolioSettings.SellSide.MaximumSize.String(),
MaximumTotal: defaultConfig.PortfolioSettings.SellSide.MaximumTotal.String(),
},
},
StatisticSettings: &btrpc.StatisticSettings{
RiskFreeRate: defaultConfig.StatisticSettings.RiskFreeRate.String(),
},
}
var dnr bool
if c.IsSet("donotrunimmediately") {
dnr = c.Bool("donotrunimmediately")
}
var dns bool
if c.IsSet("donotstore") {
dns = c.Bool("donotstore")
}
client := btrpc.NewBacktesterServiceClient(conn)
result, err := client.ExecuteStrategyFromConfig(
c.Context,
&btrpc.ExecuteStrategyFromConfigRequest{
Config: cfg,
DoNotRunImmediately: dnr,
DoNotStore: dns,
},
)
if err != nil {
return err
}
jsonOutput(result)
return nil
}