package main import ( "encoding/json" "errors" "flag" "fmt" "io/ioutil" "log" "os" "path/filepath" "sort" "strings" "sync" "text/template" "time" "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" "github.com/thrasher-corp/gocryptotrader/portfolio/banking" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) func main() { log.Println("Loading flags...") parseCLFlags() log.Println("Loading engine...") bot, err := engine.New() if err != nil { log.Fatalf("Failed to initialise engine. Err: %s", err) } engine.Bot = bot bot.Settings = engine.Settings{ DisableExchangeAutoPairUpdates: true, Verbose: verboseOverride, EnableExchangeHTTPRateLimiter: true, } log.Println("Loading config...") wrapperConfig, err := loadConfig() if err != nil { log.Printf("Error loading config: '%v', generating empty config", err) wrapperConfig = Config{ Exchanges: make(map[string]*config.APICredentialsConfig), } } log.Println("Loading exchanges..") var wg sync.WaitGroup for x := range exchange.Exchanges { name := exchange.Exchanges[x] if _, ok := wrapperConfig.Exchanges[name]; !ok { wrapperConfig.Exchanges[strings.ToLower(name)] = &config.APICredentialsConfig{} } if shouldLoadExchange(name) { err = bot.LoadExchange(name, true, &wg) if err != nil { log.Printf("Failed to load exchange %s. Err: %s", name, err) continue } } } wg.Wait() log.Println("Done.") if withdrawAddressOverride != "" { wrapperConfig.WalletAddress = withdrawAddressOverride } if orderTypeOverride != "LIMIT" { wrapperConfig.OrderSubmission.OrderType = orderTypeOverride } if orderSideOverride != "BUY" { wrapperConfig.OrderSubmission.OrderSide = orderSideOverride } if orderPriceOverride > 0 { wrapperConfig.OrderSubmission.Price = orderPriceOverride } if orderAmountOverride > 0 { wrapperConfig.OrderSubmission.Amount = orderAmountOverride } log.Println("Testing exchange wrappers..") var exchangeResponses []ExchangeResponses exchs := bot.GetExchanges() for x := range exchs { exchs[x].SetDefaults() base := exchs[x].GetBase() if !base.Config.Enabled { log.Printf("Exchange %v not enabled, skipping", base.GetName()) continue } base.Config.Verbose = verboseOverride base.Verbose = verboseOverride base.HTTPDebugging = false base.Config.HTTPDebugging = false wg.Add(1) go func(num int) { name := exchs[num].GetName() authenticated := setExchangeAPIKeys(name, wrapperConfig.Exchanges, base) wrapperResult := ExchangeResponses{ ID: fmt.Sprintf("Exchange%v", num), ExchangeName: name, APIKeysSet: authenticated, AssetPairResponses: testWrappers(exchs[num], base, &wrapperConfig), } for i := range wrapperResult.AssetPairResponses { wrapperResult.ErrorCount += wrapperResult.AssetPairResponses[i].ErrorCount } exchangeResponses = append(exchangeResponses, wrapperResult) wg.Done() }(x) } wg.Wait() log.Println("Done.") log.Println() sort.Slice(exchangeResponses, func(i, j int) bool { return exchangeResponses[i].ExchangeName < exchangeResponses[j].ExchangeName }) if strings.EqualFold(outputOverride, "Console") { outputToConsole(exchangeResponses) } if strings.EqualFold(outputOverride, "JSON") { outputToJSON(exchangeResponses) } if strings.EqualFold(outputOverride, "HTML") { outputToHTML(exchangeResponses) } saveConfig(&wrapperConfig) } func parseCLFlags() { flag.StringVar(&exchangesToUseOverride, "exchanges", "", "a + delimited list of exchange names to run tests against eg -exchanges=bitfinex+okex") flag.StringVar(&exchangesToExcludeOverride, "excluded-exchanges", "", "a + delimited list of exchange names to ignore when they're being temperamental eg -exchangesToExlude=lbank") flag.StringVar(&assetTypeOverride, "asset", "", "the asset type to run tests against (where applicable)") flag.StringVar(¤cyPairOverride, "currency", "", "the currency to run tests against (where applicable)") flag.StringVar(&outputOverride, "output", "HTML", "JSON, HTML or Console") flag.BoolVar(&authenticatedOnly, "auth-only", false, "skip any wrapper function that doesn't require auth") flag.BoolVar(&verboseOverride, "verbose", false, "verbose CL output - if console output is selected then wrapper response is included") flag.StringVar(&orderSideOverride, "orderside", "BUY", "the order type for all order based wrapper tests") flag.StringVar(&orderTypeOverride, "ordertype", "LIMIT", "the order type for all order based wrapper tests") flag.Float64Var(&orderAmountOverride, "orderamount", 0, "the order amount for all order based wrapper tests") flag.Float64Var(&orderPriceOverride, "orderprice", 0, "the order price for all order based wrapper tests") flag.StringVar(&withdrawAddressOverride, "withdraw-wallet", "", "withdraw wallet address") flag.StringVar(&outputFileName, "filename", "report", "name of the output file eg 'report'.html or 'report'.json") flag.Parse() if exchangesToUseOverride != "" { exchangesToUseList = strings.Split(exchangesToUseOverride, "+") } if exchangesToExcludeOverride != "" { exchangesToExcludeList = strings.Split(exchangesToExcludeOverride, "+") } } func shouldLoadExchange(name string) bool { shouldLoadExchange := true if len(exchangesToUseList) > 0 { var found bool for i := range exchangesToUseList { if strings.EqualFold(name, exchangesToUseList[i]) { found = true } } if !found { shouldLoadExchange = false } } if len(exchangesToExcludeList) > 0 { for i := range exchangesToExcludeList { if strings.EqualFold(name, exchangesToExcludeList[i]) { if shouldLoadExchange { shouldLoadExchange = false } } } } return shouldLoadExchange } func setExchangeAPIKeys(name string, keys map[string]*config.APICredentialsConfig, base *exchange.Base) bool { lowerExchangeName := strings.ToLower(name) if base.API.CredentialsValidator.RequiresKey && keys[lowerExchangeName].Key == "" { keys[lowerExchangeName].Key = config.DefaultAPIKey } if base.API.CredentialsValidator.RequiresSecret && keys[lowerExchangeName].Secret == "" { keys[lowerExchangeName].Secret = config.DefaultAPISecret } if base.API.CredentialsValidator.RequiresPEM && keys[lowerExchangeName].PEMKey == "" { keys[lowerExchangeName].PEMKey = "PEM" } if base.API.CredentialsValidator.RequiresClientID && keys[lowerExchangeName].ClientID == "" { keys[lowerExchangeName].ClientID = config.DefaultAPIClientID } if keys[lowerExchangeName].OTPSecret == "" { keys[lowerExchangeName].OTPSecret = "-" // Ensure OTP is available for use } base.API.Credentials.Key = keys[lowerExchangeName].Key base.Config.API.Credentials.Key = keys[lowerExchangeName].Key base.API.Credentials.Secret = keys[lowerExchangeName].Secret base.Config.API.Credentials.Secret = keys[lowerExchangeName].Secret base.API.Credentials.ClientID = keys[lowerExchangeName].ClientID base.Config.API.Credentials.ClientID = keys[lowerExchangeName].ClientID if keys[lowerExchangeName].OTPSecret != "-" { base.Config.API.Credentials.OTPSecret = keys[lowerExchangeName].OTPSecret } if keys[lowerExchangeName].Subaccount != "" { base.API.Credentials.Subaccount = keys[lowerExchangeName].Subaccount base.Config.API.Credentials.Subaccount = keys[lowerExchangeName].Subaccount } base.API.AuthenticatedSupport = true base.API.AuthenticatedWebsocketSupport = true base.Config.API.AuthenticatedSupport = true base.Config.API.AuthenticatedWebsocketSupport = true return base.ValidateAPICredentials() } func parseOrderSide(orderSide string) order.Side { switch orderSide { case order.AnySide.String(): return order.AnySide case order.Buy.String(): return order.Buy case order.Sell.String(): return order.Sell case order.Bid.String(): return order.Bid case order.Ask.String(): return order.Ask default: log.Printf("Orderside '%v' not recognised, defaulting to BUY", orderSide) return order.Buy } } func parseOrderType(orderType string) order.Type { switch orderType { case order.AnyType.String(): return order.AnyType case order.Limit.String(): return order.Limit case order.Market.String(): return order.Market case order.ImmediateOrCancel.String(): return order.ImmediateOrCancel case order.Stop.String(): return order.Stop case order.TrailingStop.String(): return order.TrailingStop case order.UnknownType.String(): return order.UnknownType default: log.Printf("OrderType '%v' not recognised, defaulting to LIMIT", orderTypeOverride) return order.Limit } } func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) []ExchangeAssetPairResponses { var response []ExchangeAssetPairResponses testOrderSide := parseOrderSide(config.OrderSubmission.OrderSide) testOrderType := parseOrderType(config.OrderSubmission.OrderType) assetTypes := base.GetAssetTypes() if assetTypeOverride != "" { a, err := asset.New(assetTypeOverride) if err != nil { log.Printf("%v Asset Type '%v' not recognised, defaulting to exchange defaults", base.GetName(), assetTypeOverride) } else { assetTypes = asset.Items{a} } } for i := range assetTypes { var msg string log.Printf("%v %v", base.GetName(), assetTypes[i]) if _, ok := base.Config.CurrencyPairs.Pairs[assetTypes[i]]; !ok { continue } var p currency.Pair switch { case currencyPairOverride != "": var err error p, err = currency.NewPairFromString(currencyPairOverride) if err != nil { log.Printf("%v Encountered error: '%v'", base.GetName(), err) continue } case len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled) == 0: if len(base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available) == 0 { log.Printf("%v has no enabled or available currencies. Skipping", base.GetName()) continue } p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Available.GetRandomPair() default: p = base.Config.CurrencyPairs.Pairs[assetTypes[i]].Enabled.GetRandomPair() } var err error p, err = disruptFormatting(p) if err != nil { log.Println("failed to disrupt currency pair formatting:", err) } responseContainer := ExchangeAssetPairResponses{ AssetType: assetTypes[i], Pair: p, } log.Printf("Setup config for %v %v %v", base.GetName(), assetTypes[i], p) err = e.Setup(base.Config) if err != nil { log.Printf("%v Encountered error reloading config: '%v'", base.GetName(), err) } log.Printf("Executing wrappers for %v %v %v", base.GetName(), assetTypes[i], p) if !authenticatedOnly { var fetchTickerResponse *ticker.Price fetchTickerResponse, err = e.FetchTicker(p, assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), Function: "FetchTicker", Error: msg, Response: jsonifyInterface([]interface{}{fetchTickerResponse}), }) var updateTickerResponse *ticker.Price updateTickerResponse, err = e.UpdateTicker(p, assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), Function: "UpdateTicker", Error: msg, Response: jsonifyInterface([]interface{}{updateTickerResponse}), }) var fetchOrderbookResponse *orderbook.Base fetchOrderbookResponse, err = e.FetchOrderbook(p, assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), Function: "FetchOrderbook", Error: msg, Response: jsonifyInterface([]interface{}{fetchOrderbookResponse}), }) var updateOrderbookResponse *orderbook.Base updateOrderbookResponse, err = e.UpdateOrderbook(p, assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), Function: "UpdateOrderbook", Error: msg, Response: jsonifyInterface([]interface{}{updateOrderbookResponse}), }) var fetchTradablePairsResponse []string fetchTradablePairsResponse, err = e.FetchTradablePairs(assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{assetTypes[i]}), Function: "FetchTradablePairs", Error: msg, Response: jsonifyInterface([]interface{}{fetchTradablePairsResponse}), }) // r6 err = e.UpdateTradablePairs(false) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{false}), Function: "UpdateTradablePairs", Error: msg, Response: jsonifyInterface([]interface{}{nil}), }) var getHistoricTradesResponse []trade.Data getHistoricTradesResponse, err = e.GetHistoricTrades(p, assetTypes[i], time.Now().Add(-time.Hour), time.Now()) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], time.Now().Add(-time.Hour), time.Now()}), Function: "GetHistoricTrades", Error: msg, Response: jsonifyInterface([]interface{}{getHistoricTradesResponse}), }) var getRecentTradesResponse []trade.Data getRecentTradesResponse, err = e.GetRecentTrades(p, assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{p, assetTypes[i]}), Function: "GetRecentTrades", Error: msg, Response: jsonifyInterface([]interface{}{getRecentTradesResponse}), }) var getHistoricCandlesResponse kline.Item startTime, endTime := time.Now().AddDate(0, 0, -1), time.Now() getHistoricCandlesResponse, err = e.GetHistoricCandles(p, assetTypes[i], startTime, endTime, kline.OneDay) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ Function: "GetHistoricCandles", Error: msg, Response: getHistoricCandlesResponse, SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], startTime, endTime, kline.OneDay}), }) var getHisotirCandlesExtendedResponse kline.Item getHisotirCandlesExtendedResponse, err = e.GetHistoricCandlesExtended(p, assetTypes[i], startTime, endTime, kline.OneDay) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ Function: "GetHistoricCandlesExtended", Error: msg, Response: getHisotirCandlesExtendedResponse, SentParams: jsonifyInterface([]interface{}{p, assetTypes[i], startTime, endTime, kline.OneDay}), }) err = e.UpdateOrderExecutionLimits(assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{assetTypes[i]}), Function: "UpdateOrderExecutionLimits", Error: msg, Response: jsonifyInterface([]interface{}{""}), }) } var fetchAccountInfoResponse account.Holdings fetchAccountInfoResponse, err = e.FetchAccountInfo(assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ Function: "FetchAccountInfo", Error: msg, Response: jsonifyInterface([]interface{}{fetchAccountInfoResponse}), }) var getFundingHistoryResponse []exchange.FundHistory getFundingHistoryResponse, err = e.GetFundingHistory() msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ Function: "GetFundingHistory", Error: msg, Response: jsonifyInterface([]interface{}{getFundingHistoryResponse}), }) feeType := exchange.FeeBuilder{ FeeType: exchange.CryptocurrencyTradeFee, Pair: p, PurchasePrice: config.OrderSubmission.Price, Amount: config.OrderSubmission.Amount, } var getFeeByTypeResponse float64 getFeeByTypeResponse, err = e.GetFeeByType(&feeType) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{feeType}), Function: "GetFeeByType-Trade", Error: msg, Response: jsonifyInterface([]interface{}{getFeeByTypeResponse}), }) s := &order.Submit{ Pair: p, Side: testOrderSide, Type: testOrderType, Amount: config.OrderSubmission.Amount, Price: config.OrderSubmission.Price, ClientID: config.OrderSubmission.OrderID, AssetType: assetTypes[i], } var submitOrderResponse order.SubmitResponse submitOrderResponse, err = e.SubmitOrder(s) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{*s}), Function: "SubmitOrder", Error: msg, Response: jsonifyInterface([]interface{}{submitOrderResponse}), }) modifyRequest := order.Modify{ ID: config.OrderSubmission.OrderID, Type: testOrderType, Side: testOrderSide, Pair: p, Price: config.OrderSubmission.Price, Amount: config.OrderSubmission.Amount, } var modifyOrderResponse string modifyOrderResponse, err = e.ModifyOrder(&modifyRequest) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{modifyRequest}), Function: "ModifyOrder", Error: msg, Response: modifyOrderResponse, }) cancelRequest := order.Cancel{ Side: testOrderSide, Pair: p, ID: config.OrderSubmission.OrderID, AssetType: assetTypes[i], } err = e.CancelOrder(&cancelRequest) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{cancelRequest}), Function: "CancelOrder", Error: msg, Response: jsonifyInterface([]interface{}{nil}), }) var request []order.Cancel request = append(request, order.Cancel{ Side: testOrderSide, Pair: p, ID: config.OrderSubmission.OrderID, AssetType: assetTypes[i], }) var CancelBatchOrdersResponse order.CancelBatchResponse CancelBatchOrdersResponse, err = e.CancelBatchOrders(request) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{cancelRequest}), Function: "CancelBatchOrders", Error: msg, Response: jsonifyInterface([]interface{}{CancelBatchOrdersResponse}), }) var cancellAllOrdersResponse order.CancelAllResponse cancellAllOrdersResponse, err = e.CancelAllOrders(&cancelRequest) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{cancelRequest}), Function: "CancelAllOrders", Error: msg, Response: jsonifyInterface([]interface{}{cancellAllOrdersResponse}), }) var r15 order.Detail r15, err = e.GetOrderInfo(config.OrderSubmission.OrderID, p, assetTypes[i]) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{config.OrderSubmission.OrderID}), Function: "GetOrderInfo", Error: msg, Response: jsonifyInterface([]interface{}{r15}), }) historyRequest := order.GetOrdersRequest{ Type: testOrderType, Side: testOrderSide, Pairs: []currency.Pair{p}, } var getOrderHistoryResponse []order.Detail getOrderHistoryResponse, err = e.GetOrderHistory(&historyRequest) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{historyRequest}), Function: "GetOrderHistory", Error: msg, Response: jsonifyInterface([]interface{}{getOrderHistoryResponse}), }) orderRequest := order.GetOrdersRequest{ Type: testOrderType, Side: testOrderSide, Pairs: []currency.Pair{p}, } var getActiveOrdersResponse []order.Detail getActiveOrdersResponse, err = e.GetActiveOrders(&orderRequest) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{orderRequest}), Function: "GetActiveOrders", Error: msg, Response: jsonifyInterface([]interface{}{getActiveOrdersResponse}), }) var getDepositAddressResponse string getDepositAddressResponse, err = e.GetDepositAddress(p.Base, "") msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{p.Base, ""}), Function: "GetDepositAddress", Error: msg, Response: getDepositAddressResponse, }) feeType = exchange.FeeBuilder{ FeeType: exchange.CryptocurrencyWithdrawalFee, Pair: p, PurchasePrice: config.OrderSubmission.Price, Amount: config.OrderSubmission.Amount, } var GetFeeByTypeResponse float64 GetFeeByTypeResponse, err = e.GetFeeByType(&feeType) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{feeType}), Function: "GetFeeByType-Crypto-Withdraw", Error: msg, Response: jsonifyInterface([]interface{}{GetFeeByTypeResponse}), }) withdrawRequest := withdraw.Request{ Currency: p.Quote, Crypto: withdraw.CryptoRequest{ Address: withdrawAddressOverride, }, Amount: config.OrderSubmission.Amount, } msg = "" err = withdrawRequest.Validate() if err != nil { msg = err.Error() responseContainer.ErrorCount++ } withdrawCryptocurrencyFundsResponse, err := e.WithdrawCryptocurrencyFunds(&withdrawRequest) if err != nil { msg += ", " + err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{withdrawRequest}), Function: "WithdrawCryptocurrencyFunds", Error: msg, Response: withdrawCryptocurrencyFundsResponse, }) feeType = exchange.FeeBuilder{ FeeType: exchange.InternationalBankWithdrawalFee, Pair: p, PurchasePrice: config.OrderSubmission.Price, Amount: config.OrderSubmission.Amount, FiatCurrency: currency.AUD, BankTransactionType: exchange.WireTransfer, } var getFeeByTypeFiatResponse float64 getFeeByTypeFiatResponse, err = e.GetFeeByType(&feeType) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{feeType}), Function: "GetFeeByType-FIAT-Withdraw", Error: msg, Response: jsonifyInterface([]interface{}{getFeeByTypeFiatResponse}), }) withdrawRequestFiat := withdraw.Request{ Currency: p.Quote, Amount: config.OrderSubmission.Amount, Fiat: withdraw.FiatRequest{ Bank: banking.Account{ AccountName: config.BankDetails.BankAccountName, AccountNumber: config.BankDetails.BankAccountNumber, SWIFTCode: config.BankDetails.SwiftCode, IBAN: config.BankDetails.Iban, BankPostalCity: config.BankDetails.BankCity, BankName: config.BankDetails.BankName, BankAddress: config.BankDetails.BankAddress, BankCountry: config.BankDetails.BankCountry, BankPostalCode: config.BankDetails.BankPostalCode, BankCode: config.BankDetails.BankCode, }, IsExpressWire: config.BankDetails.IsExpressWire, RequiresIntermediaryBank: config.BankDetails.RequiresIntermediaryBank, IntermediaryBankName: config.BankDetails.IntermediaryBankName, IntermediaryBankAccountNumber: config.BankDetails.IntermediaryBankAccountNumber, IntermediarySwiftCode: config.BankDetails.IntermediarySwiftCode, IntermediaryIBAN: config.BankDetails.IntermediaryIban, IntermediaryBankCity: config.BankDetails.IntermediaryBankCity, IntermediaryBankAddress: config.BankDetails.IntermediaryBankAddress, IntermediaryBankCountry: config.BankDetails.IntermediaryBankCountry, IntermediaryBankPostalCode: config.BankDetails.IntermediaryBankPostalCode, IntermediaryBankCode: config.BankDetails.IntermediaryBankCode, }, } withdrawFiatFundsResponse, err := e.WithdrawFiatFunds(&withdrawRequestFiat) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{withdrawRequestFiat}), Function: "WithdrawFiatFunds", Error: msg, Response: withdrawFiatFundsResponse, }) withdrawFiatFundsInternationalResponse, err := e.WithdrawFiatFundsToInternationalBank(&withdrawRequestFiat) msg = "" if err != nil { msg = err.Error() responseContainer.ErrorCount++ } responseContainer.EndpointResponses = append(responseContainer.EndpointResponses, EndpointResponse{ SentParams: jsonifyInterface([]interface{}{withdrawRequestFiat}), Function: "WithdrawFiatFundsToInternationalBank", Error: msg, Response: withdrawFiatFundsInternationalResponse, }) response = append(response, responseContainer) } return response } func jsonifyInterface(params []interface{}) json.RawMessage { response, _ := json.MarshalIndent(params, "", " ") return response } func loadConfig() (Config, error) { var config Config keys, err := ioutil.ReadFile("wrapperconfig.json") if err != nil { return config, err } err = json.Unmarshal(keys, &config) return config, err } func saveConfig(config *Config) { log.Println("JSONifying config...") jsonOutput, err := json.MarshalIndent(config, "", " ") if err != nil { log.Fatalf("Encountered error encoding JSON: %v", err) } dir, err := os.Getwd() if err != nil { log.Printf("Encountered error retrieving output directory: %v", err) return } log.Printf("Outputting to: %v", filepath.Join(dir, "wrapperconfig.json")) err = file.Write(filepath.Join(dir, "wrapperconfig.json"), jsonOutput) if err != nil { log.Printf("Encountered error writing to disk: %v", err) return } } func outputToJSON(exchangeResponses []ExchangeResponses) { log.Println("JSONifying results...") jsonOutput, err := json.MarshalIndent(exchangeResponses, "", " ") if err != nil { log.Fatalf("Encountered error encoding JSON: %v", err) } dir, err := os.Getwd() if err != nil { log.Printf("Encountered error retrieving output directory: %v", err) return } log.Printf("Outputting to: %v", filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName))) err = file.Write(filepath.Join(dir, fmt.Sprintf("%v.json", outputFileName)), jsonOutput) if err != nil { log.Printf("Encountered error writing to disk: %v", err) return } } func outputToHTML(exchangeResponses []ExchangeResponses) { log.Println("Generating HTML report...") dir, err := os.Getwd() if err != nil { log.Print(err) return } tmpl, err := template.New("report.tmpl").ParseFiles(filepath.Join(dir, "report.tmpl")) if err != nil { log.Print(err) return } log.Printf("Outputting to: %v", filepath.Join(dir, fmt.Sprintf("%v.html", outputFileName))) file, err := os.Create(filepath.Join(dir, fmt.Sprintf("%v.html", outputFileName))) if err != nil { log.Print(err) return } err = tmpl.Execute(file, exchangeResponses) if err != nil { log.Print(err) } file.Close() } func outputToConsole(exchangeResponses []ExchangeResponses) { var totalErrors int64 for i := range exchangeResponses { log.Printf("------------%v Results-------------\n", exchangeResponses[i].ExchangeName) for j := range exchangeResponses[i].AssetPairResponses { for k := range exchangeResponses[i].AssetPairResponses[j].EndpointResponses { log.Printf("%v Result: %v", exchangeResponses[i].ExchangeName, k) log.Printf("Function:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Function) log.Printf("AssetType:\t%v", exchangeResponses[i].AssetPairResponses[j].AssetType) log.Printf("Currency:\t%v\n", exchangeResponses[i].AssetPairResponses[j].Pair) log.Printf("Wrapper Params:\t%s\n", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].SentParams) if exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error != "" { totalErrors++ log.Printf("Error:\t%v", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Error) } else { log.Print("Error:\tnone") } if verboseOverride { log.Printf("Wrapper Response:\t%s", exchangeResponses[i].AssetPairResponses[j].EndpointResponses[k].Response) } log.Println() } } log.Println() } } // disruptFormatting adds in an unused delimiter and strange casing features to // ensure format currency pair is used throughout the code base. func disruptFormatting(p currency.Pair) (currency.Pair, error) { base := p.Base.String() if base == "" { return currency.Pair{}, errors.New("cannot disrupt formatting as base is not populated") } quote := p.Quote.String() if quote == "" { return currency.Pair{}, errors.New("cannot disrupt formatting as quote is not populated") } return currency.Pair{ Base: p.Base.Upper(), Quote: p.Quote.Lower(), Delimiter: "-TEST-DELIM-", }, nil }