Files
gocryptotrader/backtester/config/configbuilder/main.go
Scott 6eaa2e4073 Backtester: USD tracking (#818)
* 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
2021-11-08 12:10:15 +11:00

726 lines
22 KiB
Go

package main
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/backtester/common"
"github.com/thrasher-corp/gocryptotrader/backtester/config"
"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies"
gctcommon "github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/database"
dbPSQL "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres"
dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite3"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
)
const (
yes = "yes"
y = "y"
)
var dataOptions = []string{
"API",
"CSV",
"Database",
"Live",
}
func main() {
fmt.Print(common.ASCIILogo)
fmt.Println("Welcome to the config generator!")
reader := bufio.NewReader(os.Stdin)
cfg := config.Config{
StrategySettings: config.StrategySettings{
Name: "",
SimultaneousSignalProcessing: false,
UseExchangeLevelFunding: false,
ExchangeLevelFunding: nil,
CustomSettings: nil,
},
CurrencySettings: []config.CurrencySettings{},
DataSettings: config.DataSettings{
Interval: 0,
DataType: "",
APIData: nil,
DatabaseData: nil,
LiveData: nil,
CSVData: nil,
},
PortfolioSettings: config.PortfolioSettings{
Leverage: config.Leverage{},
BuySide: config.MinMax{},
SellSide: config.MinMax{},
},
StatisticSettings: config.StatisticSettings{},
}
fmt.Println("-----Strategy Settings-----")
var err error
firstRun := true
for err != nil || firstRun {
firstRun = false
err = parseStrategySettings(&cfg, reader)
if err != nil {
log.Println(err)
}
}
fmt.Println("-----Exchange Settings-----")
firstRun = true
for err != nil || firstRun {
firstRun = false
err = parseExchangeSettings(reader, &cfg)
if err != nil {
log.Println(err)
}
}
fmt.Println("-----Portfolio Settings-----")
firstRun = true
for err != nil || firstRun {
firstRun = false
err = parsePortfolioSettings(reader, &cfg)
if err != nil {
log.Println(err)
}
}
fmt.Println("-----Data Settings-----")
firstRun = true
for err != nil || firstRun {
firstRun = false
err = parseDataSettings(&cfg, reader)
if err != nil {
log.Println(err)
}
}
fmt.Println("-----Statistics Settings-----")
firstRun = true
for err != nil || firstRun {
firstRun = false
err = parseStatisticsSettings(&cfg, reader)
if err != nil {
log.Println(err)
}
}
var resp []byte
resp, err = json.MarshalIndent(cfg, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println("Write strategy config to file? If no, the output will be on screen y/n")
yn := quickParse(reader)
if yn == y || yn == yes {
var wd string
wd, err = os.Getwd()
if err != nil {
log.Fatal(err)
}
fn := cfg.StrategySettings.Name
if cfg.Nickname != "" {
fn += "-" + cfg.Nickname
}
fn += ".strat" // nolint:misspell // its shorthand for strategy
wd = filepath.Join(wd, fn)
fmt.Printf("Enter output file. If blank, will output to \"%v\"\n", wd)
path := quickParse(reader)
if path == "" {
path = wd
}
err = ioutil.WriteFile(path, resp, 0770)
if err != nil {
log.Fatal(err)
}
} else {
log.Print(string(resp))
}
log.Println("Config creation complete!")
}
func parseStatisticsSettings(cfg *config.Config, reader *bufio.Reader) error {
fmt.Println("Enter the risk free rate. eg 0.03")
rfr, err := strconv.ParseFloat(quickParse(reader), 64)
if err != nil {
return err
}
cfg.StatisticSettings.RiskFreeRate = decimal.NewFromFloat(rfr)
return nil
}
func parseDataSettings(cfg *config.Config, reader *bufio.Reader) error {
var err error
fmt.Println("Will you be using \"candle\" or \"trade\" data?")
cfg.DataSettings.DataType = quickParse(reader)
if cfg.DataSettings.DataType == common.TradeStr {
fmt.Println("Trade data will be converted into candles")
}
fmt.Println("What candle time interval will you use?")
cfg.DataSettings.Interval, err = parseKlineInterval(reader)
if err != nil {
return err
}
fmt.Println("Where will this data be sourced?")
var choice string
choice, err = parseDataChoice(reader, len(cfg.CurrencySettings) > 1)
if err != nil {
return err
}
switch choice {
case "API":
err = parseAPI(reader, cfg)
case "Database":
err = parseDatabase(reader, cfg)
case "CSV":
parseCSV(reader, cfg)
case "Live":
parseLive(reader, cfg)
}
return err
}
func parsePortfolioSettings(reader *bufio.Reader, cfg *config.Config) error {
var err error
fmt.Println("Will there be global portfolio buy-side limits? y/n")
yn := quickParse(reader)
if yn == y || yn == yes {
cfg.PortfolioSettings.BuySide, err = minMaxParse("buy", reader)
if err != nil {
return err
}
}
fmt.Println("Will there be global portfolio sell-side limits? y/n")
yn = quickParse(reader)
if yn == y || yn == yes {
cfg.PortfolioSettings.SellSide, err = minMaxParse("sell", reader)
if err != nil {
return err
}
}
return nil
}
func parseExchangeSettings(reader *bufio.Reader, cfg *config.Config) error {
var err error
addCurrency := y
for strings.Contains(addCurrency, y) {
var currencySetting *config.CurrencySettings
currencySetting, err = addCurrencySetting(reader, cfg.StrategySettings.UseExchangeLevelFunding)
if err != nil {
return err
}
cfg.CurrencySettings = append(cfg.CurrencySettings, *currencySetting)
fmt.Println("Add another exchange currency setting? y/n")
addCurrency = quickParse(reader)
}
return nil
}
func parseStrategySettings(cfg *config.Config, reader *bufio.Reader) error {
fmt.Println("Firstly, please select which strategy you wish to use")
strats := strategies.GetStrategies()
var strategiesToUse []string
for i := range strats {
fmt.Printf("%v. %s\n", i+1, strats[i].Name())
strategiesToUse = append(strategiesToUse, strats[i].Name())
}
var err error
cfg.StrategySettings.Name, err = parseStratName(quickParse(reader), strategiesToUse)
if err != nil {
return err
}
fmt.Println("What is the goal of your strategy?")
cfg.Goal = quickParse(reader)
fmt.Println("Enter a nickname, it can help distinguish between different configs using the same strategy")
cfg.Nickname = quickParse(reader)
fmt.Println("Does this strategy have custom settings? y/n")
customSettings := quickParse(reader)
if strings.Contains(customSettings, y) {
cfg.StrategySettings.CustomSettings = customSettingsLoop(reader)
}
fmt.Println("Do you wish to have strategy performance tracked against USD? y/n")
yn := quickParse(reader)
cfg.StrategySettings.DisableUSDTracking = !strings.Contains(yn, y)
fmt.Println("Will this strategy use simultaneous processing? y/n")
yn = quickParse(reader)
cfg.StrategySettings.SimultaneousSignalProcessing = strings.Contains(yn, y)
if !cfg.StrategySettings.SimultaneousSignalProcessing {
return nil
}
fmt.Println("Will this strategy be able to share funds at an exchange level? y/n")
yn = quickParse(reader)
cfg.StrategySettings.UseExchangeLevelFunding = strings.Contains(yn, y)
if !cfg.StrategySettings.UseExchangeLevelFunding {
return nil
}
addFunding := y
for strings.Contains(addFunding, y) {
fund := config.ExchangeLevelFunding{}
fmt.Println("What is the exchange name to add funding to?")
fund.ExchangeName = quickParse(reader)
fmt.Println("What is the asset to add funding to?")
supported := asset.Supported()
for i := range supported {
fmt.Printf("%v. %s\n", i+1, supported[i])
}
response := quickParse(reader)
num, err := strconv.ParseFloat(response, 64)
if err == nil {
intNum := int(num)
if intNum > len(supported) || intNum <= 0 {
return errors.New("unknown option")
}
fund.Asset = supported[intNum-1].String()
} else {
for i := range supported {
if strings.EqualFold(response, supported[i].String()) {
fund.Asset = supported[i].String()
break
}
}
if fund.Asset == "" {
return errors.New("unrecognised data option")
}
}
fmt.Println("What is the individual currency to add funding to? eg BTC")
fund.Currency = quickParse(reader)
fmt.Printf("How much funding for %v?\n", fund.Currency)
fund.InitialFunds, err = decimal.NewFromString(quickParse(reader))
if err != nil {
return err
}
fmt.Println("If your strategy utilises fund transfer, what is the transfer fee?")
fee := quickParse(reader)
if fee != "" {
fund.TransferFee, err = decimal.NewFromString(fee)
if err != nil {
return err
}
}
cfg.StrategySettings.ExchangeLevelFunding = append(cfg.StrategySettings.ExchangeLevelFunding, fund)
fmt.Println("Add another source of funds? y/n")
addFunding = quickParse(reader)
}
return nil
}
func parseAPI(reader *bufio.Reader, cfg *config.Config) error {
cfg.DataSettings.APIData = &config.APIData{}
var startDate, endDate, inclusive string
var err error
defaultStart := time.Now().Add(-time.Hour * 24 * 365)
defaultEnd := time.Now()
fmt.Printf("What is the start date? Leave blank for \"%v\"\n", defaultStart.Format(gctcommon.SimpleTimeFormat))
startDate = quickParse(reader)
if startDate != "" {
cfg.DataSettings.APIData.StartDate, err = time.Parse(startDate, gctcommon.SimpleTimeFormat)
if err != nil {
return err
}
} else {
cfg.DataSettings.APIData.StartDate = defaultStart
}
fmt.Printf("What is the end date? Leave blank for \"%v\"\n", defaultStart.Format(gctcommon.SimpleTimeFormat))
endDate = quickParse(reader)
if endDate != "" {
cfg.DataSettings.APIData.EndDate, err = time.Parse(endDate, gctcommon.SimpleTimeFormat)
if err != nil {
return err
}
} else {
cfg.DataSettings.APIData.EndDate = defaultEnd
}
fmt.Println("Is the end date inclusive? y/n")
inclusive = quickParse(reader)
cfg.DataSettings.APIData.InclusiveEndDate = inclusive == y || inclusive == yes
return nil
}
func parseCSV(reader *bufio.Reader, cfg *config.Config) {
cfg.DataSettings.CSVData = &config.CSVData{}
fmt.Println("What is path of the CSV file to read?")
cfg.DataSettings.CSVData.FullPath = quickParse(reader)
}
func parseDatabase(reader *bufio.Reader, cfg *config.Config) error {
cfg.DataSettings.DatabaseData = &config.DatabaseData{}
var input string
var err error
defaultStart := time.Now().Add(-time.Hour * 24 * 365)
defaultEnd := time.Now()
fmt.Printf("What is the start date? Leave blank for \"%v\"\n", defaultStart.Format(gctcommon.SimpleTimeFormat))
startDate := quickParse(reader)
if startDate != "" {
cfg.DataSettings.DatabaseData.StartDate, err = time.Parse(startDate, gctcommon.SimpleTimeFormat)
if err != nil {
return err
}
} else {
cfg.DataSettings.DatabaseData.StartDate = defaultStart
}
fmt.Printf("What is the end date? Leave blank for \"%v\"\n", defaultStart.Format(gctcommon.SimpleTimeFormat))
if endDate := quickParse(reader); endDate != "" {
cfg.DataSettings.DatabaseData.EndDate, err = time.Parse(endDate, gctcommon.SimpleTimeFormat)
if err != nil {
return err
}
} else {
cfg.DataSettings.DatabaseData.EndDate = defaultEnd
}
fmt.Println("Is the end date inclusive? y/n")
input = quickParse(reader)
cfg.DataSettings.DatabaseData.InclusiveEndDate = input == y || input == yes
cfg.DataSettings.DatabaseData.Config = database.Config{
Enabled: true,
}
fmt.Println("Do you want database verbose output? y/n")
input = quickParse(reader)
cfg.DataSettings.DatabaseData.Config.Verbose = input == y || input == yes
fmt.Printf("What database driver to use? %v %v or %v\n", database.DBPostgreSQL, database.DBSQLite, database.DBSQLite3)
cfg.DataSettings.DatabaseData.Config.Driver = quickParse(reader)
if cfg.DataSettings.DatabaseData.Config.Driver == database.DBSQLite || cfg.DataSettings.DatabaseData.Config.Driver == database.DBSQLite3 {
fmt.Printf("What is the path to the database directory? Leaving blank will use: '%v'", filepath.Join(gctcommon.GetDefaultDataDir(runtime.GOOS), "database"))
cfg.DataSettings.DatabaseData.Path = quickParse(reader)
}
fmt.Println("What is the database host?")
cfg.DataSettings.DatabaseData.Config.Host = quickParse(reader)
fmt.Println("What is the database username?")
cfg.DataSettings.DatabaseData.Config.Username = quickParse(reader)
fmt.Println("What is the database password? eg 1234")
cfg.DataSettings.DatabaseData.Config.Password = quickParse(reader)
fmt.Println("What is the database? eg database.db")
cfg.DataSettings.DatabaseData.Config.Database = quickParse(reader)
if cfg.DataSettings.DatabaseData.Config.Driver == database.DBPostgreSQL {
fmt.Println("What is the database SSLMode? eg disable")
cfg.DataSettings.DatabaseData.Config.SSLMode = quickParse(reader)
}
fmt.Println("What is the database Port? eg 1337")
input = quickParse(reader)
var port float64
if input != "" {
port, err = strconv.ParseFloat(input, 64)
if err != nil {
return err
}
}
cfg.DataSettings.DatabaseData.Config.Port = uint16(port)
err = database.DB.SetConfig(&cfg.DataSettings.DatabaseData.Config)
if err != nil {
return fmt.Errorf("database failed to set config: %w", err)
}
if cfg.DataSettings.DatabaseData.Config.Driver == database.DBPostgreSQL {
_, err = dbPSQL.Connect(&cfg.DataSettings.DatabaseData.Config)
if err != nil {
return fmt.Errorf("database failed to connect: %v", err)
}
} else if cfg.DataSettings.DatabaseData.Config.Driver == database.DBSQLite ||
cfg.DataSettings.DatabaseData.Config.Driver == database.DBSQLite3 {
_, err = dbsqlite3.Connect(cfg.DataSettings.DatabaseData.Config.Database)
if err != nil {
return fmt.Errorf("database failed to connect: %v", err)
}
}
return nil
}
func parseLive(reader *bufio.Reader, cfg *config.Config) {
cfg.DataSettings.LiveData = &config.LiveData{}
fmt.Println("Do you wish to use live trading? It's highly recommended that you do not. y/n")
input := quickParse(reader)
cfg.DataSettings.LiveData.RealOrders = input == y || input == yes
if cfg.DataSettings.LiveData.RealOrders {
fmt.Printf("Do you want to override GoCryptoTrader's API credentials for %s? y/n\n", cfg.CurrencySettings[0].ExchangeName)
input = quickParse(reader)
if input == y || input == yes {
fmt.Println("What is the API key?")
cfg.DataSettings.LiveData.APIKeyOverride = quickParse(reader)
fmt.Println("What is the API secret?")
cfg.DataSettings.LiveData.APISecretOverride = quickParse(reader)
fmt.Println("What is the Client ID?")
cfg.DataSettings.LiveData.APIClientIDOverride = quickParse(reader)
fmt.Println("What is the 2FA seed?")
cfg.DataSettings.LiveData.API2FAOverride = quickParse(reader)
fmt.Println("What is the subaccount to use?")
cfg.DataSettings.LiveData.APISubAccountOverride = quickParse(reader)
}
}
}
func parseDataChoice(reader *bufio.Reader, multiCurrency bool) (string, error) {
if multiCurrency {
// live trading does not support multiple currencies
dataOptions = dataOptions[:3]
}
for i := range dataOptions {
fmt.Printf("%v. %s\n", i+1, dataOptions[i])
}
response := quickParse(reader)
num, err := strconv.ParseFloat(response, 64)
if err == nil {
intNum := int(num)
if intNum > len(dataOptions) || intNum <= 0 {
return "", errors.New("unknown option")
}
return dataOptions[intNum-1], nil
}
for i := range dataOptions {
if strings.EqualFold(response, dataOptions[i]) {
return dataOptions[i], nil
}
}
return "", errors.New("unrecognised data option")
}
func parseKlineInterval(reader *bufio.Reader) (time.Duration, error) {
allCandles := gctkline.SupportedIntervals
for i := range allCandles {
fmt.Printf("%v. %s\n", i+1, allCandles[i].Word())
}
response := quickParse(reader)
num, err := strconv.ParseFloat(response, 64)
if err == nil {
intNum := int(num)
if intNum > len(allCandles) || intNum <= 0 {
return 0, errors.New("unknown option")
}
return allCandles[intNum-1].Duration(), nil
}
for i := range allCandles {
if strings.EqualFold(response, allCandles[i].Word()) {
return allCandles[i].Duration(), nil
}
}
return 0, errors.New("unrecognised interval")
}
func parseStratName(name string, strategiesToUse []string) (string, error) {
num, err := strconv.ParseFloat(name, 64)
if err == nil {
intNum := int(num)
if intNum > len(strategiesToUse) || intNum <= 0 {
return "", errors.New("unknown option")
}
return strategiesToUse[intNum-1], nil
}
for i := range strategiesToUse {
if strings.EqualFold(name, strategiesToUse[i]) {
return strategiesToUse[i], nil
}
}
return "", errors.New("unrecognised strategy")
}
func customSettingsLoop(reader *bufio.Reader) map[string]interface{} {
resp := make(map[string]interface{})
customSettingField := "loopTime!"
for customSettingField != "" {
fmt.Println("Enter a custom setting name. Enter nothing to stop")
customSettingField = quickParse(reader)
if customSettingField != "" {
fmt.Println("Enter a custom setting value")
resp[customSettingField] = quickParse(reader)
}
}
return resp
}
func addCurrencySetting(reader *bufio.Reader, usingExchangeLevelFunding bool) (*config.CurrencySettings, error) {
setting := config.CurrencySettings{
BuySide: config.MinMax{},
SellSide: config.MinMax{},
}
fmt.Println("Enter the exchange name. eg Binance")
setting.ExchangeName = quickParse(reader)
fmt.Println("Please select an asset")
supported := asset.Supported()
for i := range supported {
fmt.Printf("%v. %s\n", i+1, supported[i])
}
response := quickParse(reader)
num, err := strconv.ParseFloat(response, 64)
if err == nil {
intNum := int(num)
if intNum > len(supported) || intNum <= 0 {
return nil, errors.New("unknown option")
}
setting.Asset = supported[intNum-1].String()
}
for i := range supported {
if strings.EqualFold(response, supported[i].String()) {
setting.Asset = supported[i].String()
}
}
var f float64
fmt.Println("Enter the currency base. eg BTC")
setting.Base = quickParse(reader)
if !usingExchangeLevelFunding {
fmt.Println("Enter the initial base funds. eg 0")
parseNum := quickParse(reader)
if parseNum != "" {
f, err = strconv.ParseFloat(parseNum, 64)
if err != nil {
return nil, err
}
iqf := decimal.NewFromFloat(f)
setting.InitialBaseFunds = &iqf
}
}
fmt.Println("Enter the currency quote. eg USDT")
setting.Quote = quickParse(reader)
if !usingExchangeLevelFunding {
fmt.Println("Enter the initial quote funds. eg 10000")
parseNum := quickParse(reader)
if parseNum != "" {
f, err = strconv.ParseFloat(parseNum, 64)
if err != nil {
return nil, err
}
iqf := decimal.NewFromFloat(f)
setting.InitialQuoteFunds = &iqf
}
}
fmt.Println("Enter the maker-fee. eg 0.001")
parseNum := quickParse(reader)
if parseNum != "" {
f, err = strconv.ParseFloat(parseNum, 64)
if err != nil {
return nil, err
}
setting.MakerFee = decimal.NewFromFloat(f)
}
fmt.Println("Enter the taker-fee. eg 0.01")
parseNum = quickParse(reader)
if parseNum != "" {
f, err = strconv.ParseFloat(parseNum, 64)
if err != nil {
return nil, err
}
setting.TakerFee = decimal.NewFromFloat(f)
}
fmt.Println("Will there be buy-side limits? y/n")
yn := quickParse(reader)
if yn == y || yn == yes {
setting.BuySide, err = minMaxParse("buy", reader)
if err != nil {
return nil, err
}
}
fmt.Println("Will there be sell-side limits? y/n")
yn = quickParse(reader)
if yn == y || yn == yes {
setting.SellSide, err = minMaxParse("sell", reader)
if err != nil {
return nil, err
}
}
fmt.Println("Will the in-sample data amounts conform to current exchange defined order execution limits? i.e. If amount is 1337.001345 and the step size is 0.01 order amount will be re-adjusted to 1337. y/n")
yn = quickParse(reader)
if yn == y || yn == yes {
setting.CanUseExchangeLimits = true
}
fmt.Println("Should order size shrink to fit within candle volume? y/n")
yn = quickParse(reader)
if yn == y || yn == yes {
setting.SkipCandleVolumeFitting = true
}
fmt.Println("Do you wish to include slippage? y/n")
yn = quickParse(reader)
if yn == y || yn == yes {
fmt.Println("Slippage is randomly determined between the lower and upper bounds.")
fmt.Println("If the lower bound is 80, then the price can change up to 80% of itself. eg if the price is 100 and the lower bound is 80, then the lowest slipped price is $80")
fmt.Println("If the upper bound is 100, then the price can be unaffected. A minimum of 80 and a maximum of 100 means that the price will randomly be set between those bounds as a way of emulating slippage")
fmt.Println("What is the lower bounds of slippage? eg 80")
f, err = strconv.ParseFloat(quickParse(reader), 64)
if err != nil {
return nil, err
}
setting.MinimumSlippagePercent = decimal.NewFromFloat(f)
fmt.Println("What is the upper bounds of slippage? eg 100")
f, err = strconv.ParseFloat(quickParse(reader), 64)
if err != nil {
return nil, err
}
setting.MaximumSlippagePercent = decimal.NewFromFloat(f)
}
return &setting, nil
}
func minMaxParse(buySell string, reader *bufio.Reader) (config.MinMax, error) {
resp := config.MinMax{}
fmt.Printf("What is the maximum %s size? eg 1\n", buySell)
parseNum := quickParse(reader)
if parseNum != "" {
f, err := strconv.ParseFloat(parseNum, 64)
if err != nil {
return resp, err
}
resp.MaximumSize = decimal.NewFromFloat(f)
}
fmt.Printf("What is the minimum %s size? eg 0.1\n", buySell)
parseNum = quickParse(reader)
if parseNum != "" {
f, err := strconv.ParseFloat(parseNum, 64)
if err != nil {
return resp, err
}
resp.MinimumSize = decimal.NewFromFloat(f)
}
fmt.Printf("What is the maximum spend %s buy? eg 12000\n", buySell)
parseNum = quickParse(reader)
if parseNum != "" {
f, err := strconv.ParseFloat(parseNum, 64)
if err != nil {
return resp, err
}
resp.MaximumTotal = decimal.NewFromFloat(f)
}
return resp, nil
}
func quickParse(reader *bufio.Reader) string {
customSettingField, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}
customSettingField = strings.Replace(customSettingField, "\r", "", -1)
return strings.Replace(customSettingField, "\n", "", -1)
}