From 017cdf1384a2f11ceaf1810862a9e11f5ac89bd9 Mon Sep 17 00:00:00 2001 From: Scott Date: Thu, 5 Jan 2023 13:03:17 +1100 Subject: [PATCH] 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 * Update backtester/engine/live.go Co-authored-by: Adrian Gallagher * Update backtester/engine/live.go Co-authored-by: Adrian Gallagher * Update backtester/config/strategyconfigbuilder/main.go Co-authored-by: Adrian Gallagher * updates docs * even better docs Co-authored-by: Adrian Gallagher --- .gitignore | 1 + CONTRIBUTORS | 26 +- LICENSE | 2 +- README.md | 34 +- backtester/README.md | 11 +- backtester/btcli/commands.go | 182 +- backtester/btcli/main.go | 14 +- backtester/btrpc/btrpc.pb.go | 1505 ++++++----- backtester/btrpc/btrpc.pb.gw.go | 242 +- backtester/btrpc/btrpc.swagger.json | 348 +-- backtester/btrpc/btrpc_grpc.pb.go | 196 +- backtester/common/common.go | 12 +- backtester/common/common_test.go | 14 + backtester/common/common_types.go | 19 +- backtester/config/README.md | 278 +- backtester/config/backtesterconfig.go | 1 + backtester/config/backtesterconfig_types.go | 17 +- ...nfig_test.go => batcktesterconfig_test.go} | 0 backtester/config/strategyconfig.go | 40 +- backtester/config/strategyconfig_test.go | 471 ++-- backtester/config/strategyconfig_types.go | 33 +- .../config/strategyconfigbuilder/main.go | 40 +- backtester/config/strategyexamples/README.md | 11 +- ...rry.strat => binance-cash-and-carry.strat} | 43 +- .../binance-live-cash-and-carry.strat | 118 + .../custom-plugin-strategy.strat | 3 +- ...a-api-candles-exchange-level-funding.strat | 7 +- .../dca-api-candles-multiple-currencies.strat | 5 +- ...-api-candles-simultaneous-processing.strat | 5 +- .../strategyexamples/dca-api-candles.strat | 3 +- .../strategyexamples/dca-api-trades.strat | 3 +- .../strategyexamples/dca-candles-live.strat | 52 +- .../strategyexamples/dca-csv-candles.strat | 3 +- .../strategyexamples/dca-csv-trades.strat | 3 +- .../dca-database-candles.strat | 3 +- .../strategyexamples/rsi-api-candles.strat | 3 +- .../t2b2-api-candles-exchange-funding.strat | 17 +- backtester/data/data.go | 331 ++- backtester/data/data_test.go | 929 +++++-- backtester/data/data_types.go | 94 +- backtester/data/kline/csv/csv.go | 18 +- backtester/data/kline/csv/csv_test.go | 8 +- backtester/data/kline/database/database.go | 2 +- .../data/kline/database/database_test.go | 24 +- backtester/data/kline/kline.go | 243 +- backtester/data/kline/kline_test.go | 227 +- backtester/data/kline/kline_types.go | 3 +- backtester/data/kline/live/README.md | 2 +- backtester/data/kline/live/live.go | 51 +- backtester/data/kline/live/live_test.go | 20 +- backtester/engine/backtest.go | 676 +++-- backtester/engine/backtest_test.go | 1759 +++++++++---- backtester/engine/backtest_types.go | 88 +- backtester/engine/fakeinterfaces_test.go | 338 +++ backtester/engine/grpcserver.go | 218 +- backtester/engine/grpcserver_test.go | 221 +- backtester/engine/live.go | 601 ++++- backtester/engine/live_test.go | 658 ++++- backtester/engine/live_types.go | 107 + backtester/engine/runmanager.go | 214 -- backtester/engine/setup.go | 487 ++-- backtester/engine/setup_test.go | 411 --- backtester/engine/taskmanager.go | 216 ++ ...runmanager_test.go => taskmanager_test.go} | 179 +- .../eventhandlers/eventholder/eventholder.go | 21 +- .../eventholder/eventholder_test.go | 21 +- .../eventholder/eventholder_types.go | 8 +- backtester/eventhandlers/exchange/exchange.go | 135 +- .../eventhandlers/exchange/exchange_test.go | 217 +- .../eventhandlers/exchange/exchange_types.go | 6 +- .../portfolio/compliance/compliance_test.go | 26 +- .../portfolio/holdings/holdings.go | 31 +- .../portfolio/holdings/holdings_test.go | 56 +- .../portfolio/holdings/holdings_types.go | 2 +- .../eventhandlers/portfolio/portfolio.go | 538 ++-- .../eventhandlers/portfolio/portfolio_test.go | 924 ++++--- .../portfolio/portfolio_types.go | 43 +- .../eventhandlers/portfolio/risk/risk.go | 7 +- .../eventhandlers/portfolio/risk/risk_test.go | 33 +- .../portfolio/risk/risk_types.go | 2 +- backtester/eventhandlers/portfolio/setup.go | 59 +- .../eventhandlers/portfolio/size/size.go | 31 +- .../eventhandlers/portfolio/size/size_test.go | 56 +- backtester/eventhandlers/statistics/common.go | 7 +- .../statistics/currencystatistics.go | 25 +- .../statistics/currencystatistics_test.go | 26 +- .../statistics/fundingstatistics.go | 77 +- .../statistics/fundingstatistics_test.go | 73 +- .../eventhandlers/statistics/printresults.go | 198 +- .../eventhandlers/statistics/statistics.go | 250 +- .../statistics/statistics_test.go | 274 +- .../statistics/statistics_types.go | 76 +- .../eventhandlers/strategies/base/base.go | 24 +- .../strategies/base/base_test.go | 46 +- .../strategies/base/base_types.go | 2 + .../README.md | 17 +- .../binancecashandcarry.go} | 139 +- .../binancecashandcarry_test.go} | 184 +- .../binancecashandcarry_types.go} | 11 +- .../dollarcostaverage/dollarcostaverage.go | 14 +- .../dollarcostaverage_test.go | 56 +- .../eventhandlers/strategies/rsi/rsi.go | 29 +- .../eventhandlers/strategies/rsi/rsi_test.go | 52 +- .../eventhandlers/strategies/strategies.go | 76 +- .../strategies/strategies_test.go | 80 +- .../strategies/strategies_types.go | 2 + .../strategies/top2bottom2/top2bottom2.go | 38 +- .../top2bottom2/top2bottom2_test.go | 29 +- backtester/eventtypes/event/event.go | 2 +- backtester/eventtypes/fill/fill_types.go | 2 +- backtester/eventtypes/kline/kline.go | 12 + backtester/eventtypes/kline/kline_types.go | 9 +- backtester/eventtypes/order/order_types.go | 2 +- backtester/eventtypes/signal/signal.go | 36 + backtester/eventtypes/signal/signal_test.go | 49 +- backtester/eventtypes/signal/signal_types.go | 9 +- backtester/funding/README.md | 41 +- backtester/funding/funding.go | 384 ++- backtester/funding/funding_test.go | 418 ++- backtester/funding/funding_types.go | 51 +- .../trackingcurrencies/trackingcurrencies.go | 2 +- .../trackingcurrencies_test.go | 53 +- backtester/main.go | 116 +- backtester/report/chart.go | 115 +- backtester/report/chart_test.go | 54 +- backtester/report/report.go | 36 +- backtester/report/report_test.go | 97 +- backtester/report/report_types.go | 3 +- backtester/report/tpl.gohtml | 584 ++--- .../backtester_config_examples_readme.tmpl | 5 +- .../backtester_config_readme.tmpl | 278 +- .../backtester_data_kline_live_readme.tmpl | 2 +- ...trategies_binancecashandcarry_readme.tmpl} | 16 +- .../backtester_funding_readme.tmpl | 41 +- .../backtester_readme.tmpl | 11 +- cmd/documentation/documentation.go | 111 +- common/math/math.go | 12 +- common/math/math_test.go | 28 + currency/code.go | 26 + engine/currency_state_manager.md | 72 +- engine/helpers.go | 4 +- engine/helpers_test.go | 18 +- engine/order_manager.go | 30 +- engine/portfolio_manager.go | 20 +- engine/rpcserver.go | 23 +- engine/rpcserver_test.go | 8 +- exchanges/account/account.go | 6 +- exchanges/account/account_test.go | 50 +- exchanges/account/account_types.go | 4 +- exchanges/account/credentials.go | 2 +- exchanges/alphapoint/alphapoint_wrapper.go | 8 +- exchanges/binance/binance.go | 5 +- exchanges/binance/binance_wrapper.go | 28 +- exchanges/binanceus/binanceus_wrapper.go | 10 +- exchanges/bitfinex/bitfinex_wrapper.go | 10 +- exchanges/bithumb/bithumb_wrapper.go | 8 +- exchanges/bitmex/bitmex_wrapper.go | 4 +- exchanges/bitstamp/bitstamp_wrapper.go | 10 +- exchanges/bittrex/bittrex_wrapper.go | 10 +- exchanges/btcmarkets/btcmarkets_wrapper.go | 10 +- exchanges/btse/btse_wrapper.go | 8 +- exchanges/bybit/bybit_wrapper.go | 26 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 4 +- exchanges/coinut/coinut_wrapper.go | 56 +- exchanges/exchange.go | 2 +- exchanges/exchange_test.go | 38 + exchanges/exmo/exmo_wrapper.go | 2 +- exchanges/ftx/ftx.go | 11 +- exchanges/ftx/ftx_test.go | 47 +- exchanges/ftx/ftx_types.go | 2 +- exchanges/ftx/ftx_wrapper.go | 72 +- exchanges/gateio/gateio_wrapper.go | 18 +- exchanges/gemini/gemini_wrapper.go | 8 +- exchanges/hitbtc/hitbtc_wrapper.go | 10 +- exchanges/huobi/huobi_wrapper.go | 46 +- exchanges/itbit/itbit_wrapper.go | 8 +- exchanges/kline/kline.go | 30 +- exchanges/kline/kline_test.go | 33 + exchanges/kline/kline_types.go | 8 +- exchanges/kraken/kraken_wrapper.go | 8 +- exchanges/lbank/lbank_wrapper.go | 10 +- .../localbitcoins/localbitcoins_wrapper.go | 8 +- exchanges/okcoin/okcoin_wrapper.go | 12 +- exchanges/okx/okx_wrapper.go | 10 +- exchanges/order/order_test.go | 13 + exchanges/order/order_types.go | 10 +- exchanges/order/orders.go | 18 +- exchanges/poloniex/poloniex_wrapper.go | 4 +- exchanges/yobit/yobit_wrapper.go | 3 +- exchanges/zb/zb_wrapper.go | 8 +- gctrpc/rpc.pb.go | 190 +- gctrpc/rpc.pb.gw.go | 2244 +++++++++-------- gctscript/modules/gct/exchange.go | 2 +- gctscript/wrappers/validator/validator.go | 2 +- main.go | 4 +- 195 files changed, 13783 insertions(+), 8048 deletions(-) rename backtester/config/{btcktesterconfig_test.go => batcktesterconfig_test.go} (100%) rename backtester/config/strategyexamples/{ftx-cash-carry.strat => binance-cash-and-carry.strat} (73%) mode change 100644 => 100755 create mode 100755 backtester/config/strategyexamples/binance-live-cash-and-carry.strat create mode 100644 backtester/engine/fakeinterfaces_test.go create mode 100644 backtester/engine/live_types.go delete mode 100644 backtester/engine/runmanager.go delete mode 100644 backtester/engine/setup_test.go create mode 100644 backtester/engine/taskmanager.go rename backtester/engine/{runmanager_test.go => taskmanager_test.go} (71%) rename backtester/eventhandlers/strategies/{ftxcashandcarry => binancecashandcarry}/README.md (87%) rename backtester/eventhandlers/strategies/{ftxcashandcarry/ftxcashandcarry.go => binancecashandcarry/binancecashandcarry.go} (62%) rename backtester/eventhandlers/strategies/{ftxcashandcarry/ftxcashandcarry_test.go => binancecashandcarry/binancecashandcarry_test.go} (72%) rename backtester/eventhandlers/strategies/{ftxcashandcarry/ftxcashandcarry_types.go => binancecashandcarry/binancecashandcarry_types.go} (72%) rename cmd/documentation/backtester_templates/{backtester_eventhandlers_strategies_ftxcashandcarry_readme.tmpl => backtester_eventhandlers_strategies_binancecashandcarry_readme.tmpl} (78%) diff --git a/.gitignore b/.gitignore index 8d672d97..9ffa1a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ vendor/ # Binaries for programs and plugins gocryptotrader +/backtester/backtester cmd/gctcli/gctcli backtester/backtester backtester/btcli/btcli diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f5eb3f74..2e1e766d 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -22,6 +22,7 @@ khcchiu | https://github.com/khcchiu woshidama323 | https://github.com/woshidama323 yangrq1018 | https://github.com/yangrq1018 TaltaM | https://github.com/TaltaM +samuael | https://github.com/samuael crackcomm | https://github.com/crackcomm azhang | https://github.com/azhang andreygrehov | https://github.com/andreygrehov @@ -30,27 +31,26 @@ Christian-Achilli | https://github.com/Christian-Achilli MarkDzulko | https://github.com/MarkDzulko gam-phon | https://github.com/gam-phon cornelk | https://github.com/cornelk -if1live | https://github.com/if1live herenow | https://github.com/herenow +if1live | https://github.com/if1live +lozdog245 | https://github.com/lozdog245 mshogin | https://github.com/mshogin soxipy | https://github.com/soxipy tk42 | https://github.com/tk42 +blombard | https://github.com/blombard +cavapoo2 | https://github.com/cavapoo2 +CodeLingoTeam | https://github.com/CodeLingoTeam +CodeLingoBot | https://github.com/CodeLingoBot +Daanikus | https://github.com/Daanikus daniel-cohen | https://github.com/daniel-cohen DirectX | https://github.com/DirectX frankzougc | https://github.com/frankzougc idoall | https://github.com/idoall -mattkanwisher | https://github.com/mattkanwisher -mKurrels | https://github.com/mKurrels -m1kola | https://github.com/m1kola -cavapoo2 | https://github.com/cavapoo2 -zeldrinn | https://github.com/zeldrinn -starit | https://github.com/starit Jimexist | https://github.com/Jimexist lookfirst | https://github.com/lookfirst +m1kola | https://github.com/m1kola +mattkanwisher | https://github.com/mattkanwisher merkeld | https://github.com/merkeld -CodeLingoTeam | https://github.com/CodeLingoTeam -Daanikus | https://github.com/Daanikus -CodeLingoBot | https://github.com/CodeLingoBot -blombard | https://github.com/blombard -soxipy | https://github.com/soxipy -lozdog245 | https://github.com/lozdog245 +mKurrels | https://github.com/mKurrels +starit | https://github.com/starit +zeldrinn | https://github.com/zeldrinn diff --git a/LICENSE b/LICENSE index 8dfe090d..6a735d58 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2022 The GoCryptoTrader Developers +Copyright (c) 2014-2023 The GoCryptoTrader Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index e98a90fb..05ba2571 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,9 @@ Binaries will be published once the codebase reaches a stable condition. |User|Contribution Amount| |--|--| | [thrasher-](https://github.com/thrasher-) | 670 | -| [shazbert](https://github.com/shazbert) | 268 | -| [gloriousCode](https://github.com/gloriousCode) | 202 | -| [dependabot[bot]](https://github.com/apps/dependabot) | 130 | +| [shazbert](https://github.com/shazbert) | 269 | +| [gloriousCode](https://github.com/gloriousCode) | 205 | +| [dependabot[bot]](https://github.com/apps/dependabot) | 139 | | [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 | | [xtda](https://github.com/xtda) | 47 | | [lrascao](https://github.com/lrascao) | 27 | @@ -160,13 +160,14 @@ Binaries will be published once the codebase reaches a stable condition. | [vadimzhukck](https://github.com/vadimzhukck) | 10 | | [140am](https://github.com/140am) | 8 | | [marcofranssen](https://github.com/marcofranssen) | 8 | -| [geseq](https://github.com/geseq) | 7 | +| [geseq](https://github.com/geseq) | 8 | | [dackroyd](https://github.com/dackroyd) | 5 | | [cranktakular](https://github.com/cranktakular) | 5 | | [khcchiu](https://github.com/khcchiu) | 5 | | [woshidama323](https://github.com/woshidama323) | 3 | | [yangrq1018](https://github.com/yangrq1018) | 3 | | [TaltaM](https://github.com/TaltaM) | 3 | +| [samuael](https://github.com/samuael) | 3 | | [crackcomm](https://github.com/crackcomm) | 3 | | [azhang](https://github.com/azhang) | 2 | | [andreygrehov](https://github.com/andreygrehov) | 2 | @@ -175,27 +176,26 @@ Binaries will be published once the codebase reaches a stable condition. | [MarkDzulko](https://github.com/MarkDzulko) | 2 | | [gam-phon](https://github.com/gam-phon) | 2 | | [cornelk](https://github.com/cornelk) | 2 | -| [if1live](https://github.com/if1live) | 2 | | [herenow](https://github.com/herenow) | 2 | +| [if1live](https://github.com/if1live) | 2 | +| [lozdog245](https://github.com/lozdog245) | 2 | | [mshogin](https://github.com/mshogin) | 2 | | [soxipy](https://github.com/soxipy) | 2 | | [tk42](https://github.com/tk42) | 2 | +| [blombard](https://github.com/blombard) | 1 | +| [cavapoo2](https://github.com/cavapoo2) | 1 | +| [CodeLingoTeam](https://github.com/CodeLingoTeam) | 1 | +| [CodeLingoBot](https://github.com/CodeLingoBot) | 1 | +| [Daanikus](https://github.com/Daanikus) | 1 | | [daniel-cohen](https://github.com/daniel-cohen) | 1 | | [DirectX](https://github.com/DirectX) | 1 | | [frankzougc](https://github.com/frankzougc) | 1 | | [idoall](https://github.com/idoall) | 1 | -| [mattkanwisher](https://github.com/mattkanwisher) | 1 | -| [mKurrels](https://github.com/mKurrels) | 1 | -| [m1kola](https://github.com/m1kola) | 1 | -| [cavapoo2](https://github.com/cavapoo2) | 1 | -| [zeldrinn](https://github.com/zeldrinn) | 1 | -| [starit](https://github.com/starit) | 1 | | [Jimexist](https://github.com/Jimexist) | 1 | | [lookfirst](https://github.com/lookfirst) | 1 | +| [m1kola](https://github.com/m1kola) | 1 | +| [mattkanwisher](https://github.com/mattkanwisher) | 1 | | [merkeld](https://github.com/merkeld) | 1 | -| [CodeLingoTeam](https://github.com/CodeLingoTeam) | 1 | -| [Daanikus](https://github.com/Daanikus) | 1 | -| [CodeLingoBot](https://github.com/CodeLingoBot) | 1 | -| [blombard](https://github.com/blombard) | 1 | -| [soxipy](https://github.com/soxipy) | 2 | -| [lozdog245](https://github.com/lozdog245) | 2 | +| [mKurrels](https://github.com/mKurrels) | 1 | +| [starit](https://github.com/starit) | 1 | +| [zeldrinn](https://github.com/zeldrinn) | 1 | diff --git a/backtester/README.md b/backtester/README.md index ef787f65..d27ae401 100644 --- a/backtester/README.md +++ b/backtester/README.md @@ -45,20 +45,23 @@ An event-driven backtesting tool to test and iterate trading strategies using hi - Fund transfer. At a strategy level, transfer funds between exchanges to allow for complex strategy design - Backtesting support for futures asset types - Example cash and carry spot futures strategy -- Long-running application -- GRPC server implementation +- Long-running application as a GRPC server +- Custom strategy plugins +- Live data source trading. Traders can move their back tested strategies and use them against current live data ## Planned Features We welcome pull requests on any feature for the Backtester! We will be especially appreciative of any contribution towards the following planned features: | Feature | Description | |---------|-------------| +| Perpetual futures support | Accounting for hourly funding rates in user's overall positions allows for much greater strategic depth | +| Margin borrowing support | Allowing strategies to utilise margin borrowing to have larger positions and handling borrow rate payments | | Leverage support | Leverage is a good way to enhance profit and loss and is important to include in strategies | +| Live ticker data | A potential feature as live trading works off candle data which is only processed at intervals. Adding ticker data as a strategic source allows for faster decision making | +| Live orderbook data | Processing orders based off the latest orderbook data allows for much more accurate order placement and reduces surprise slippage | | Enhance config-builder | Create an application that can create strategy configs in a more visual manner and execute them via GRPC to allow for faster customisation of strategies | | Save Backtester results to database | This will allow for easier comparison of results over time | | Backtester result comparison report | Providing an executive summary of Backtester database results | -| Currency correlation | Compare multiple exchange, asset, currencies for a candle interval against indicators to highlight correlated pairs for use in pairs trading | -| Improve live trading functionality | Live trading is currently only a proof Of concept. Adding live support for running multiple currencies and running off orderbook data will allow for esteemed traders to use their backtested strategies | ## How does it work? diff --git a/backtester/btcli/commands.go b/backtester/btcli/commands.go index 1d7694e1..e4eef766 100644 --- a/backtester/btcli/commands.go +++ b/backtester/btcli/commands.go @@ -40,16 +40,16 @@ var executeStrategyFromFileCommand = &cli.Command{ } 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) - if c.NArg() == 0 && c.NumFlags() == 0 { - return cli.ShowSubcommandHelp(c) - } - var path string if c.IsSet("path") { path = c.String("path") @@ -84,13 +84,13 @@ func executeStrategyFromFile(c *cli.Context) error { return nil } -var listAllRunsCommand = &cli.Command{ - Name: "listallruns", - Usage: "returns a list of all loaded backtest/livestrategy runs", - Action: listAllRuns, +var listAllTasksCommand = &cli.Command{ + Name: "listalltasks", + Usage: "returns a list of all loaded strategy tasks", + Action: listAllTasks, } -func listAllRuns(c *cli.Context) error { +func listAllTasks(c *cli.Context) error { conn, cancel, err := setupClient(c) if err != nil { return err @@ -98,9 +98,9 @@ func listAllRuns(c *cli.Context) error { defer closeConn(conn, cancel) client := btrpc.NewBacktesterServiceClient(conn) - result, err := client.ListAllRuns( + result, err := client.ListAllTasks( c.Context, - &btrpc.ListAllRunsRequest{}, + &btrpc.ListAllTasksRequest{}, ) if err != nil { @@ -111,26 +111,20 @@ func listAllRuns(c *cli.Context) error { return nil } -var startRunCommand = &cli.Command{ - Name: "startrun", - Usage: "executes a strategy loaded into the server", +var startTaskCommand = &cli.Command{ + Name: "starttask", + Usage: "executes a strategy task loaded into the server", ArgsUsage: "", - Action: startRun, + Action: startTask, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", - Usage: "the id of the backtest/livestrategy run", + Usage: "the id of the strategy task", }, }, } -func startRun(c *cli.Context) error { - conn, cancel, err := setupClient(c) - if err != nil { - return err - } - defer closeConn(conn, cancel) - +func startTask(c *cli.Context) error { if c.NArg() == 0 && c.NumFlags() == 0 { return cli.ShowSubcommandHelp(c) } @@ -142,10 +136,15 @@ func startRun(c *cli.Context) error { 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.StartRun( + result, err := client.StartTask( c.Context, - &btrpc.StartRunRequest{ + &btrpc.StartTaskRequest{ Id: id, }, ) @@ -158,13 +157,13 @@ func startRun(c *cli.Context) error { return nil } -var startAllRunsCommand = &cli.Command{ - Name: "startallruns", +var startAllTasksCommand = &cli.Command{ + Name: "startalltasks", Usage: "executes all strategies loaded into the server that have not been run", - Action: startAllRuns, + Action: startAllTasks, } -func startAllRuns(c *cli.Context) error { +func startAllTasks(c *cli.Context) error { conn, cancel, err := setupClient(c) if err != nil { return err @@ -172,9 +171,9 @@ func startAllRuns(c *cli.Context) error { defer closeConn(conn, cancel) client := btrpc.NewBacktesterServiceClient(conn) - result, err := client.StartAllRuns( + result, err := client.StartAllTasks( c.Context, - &btrpc.StartAllRunsRequest{}, + &btrpc.StartAllTasksRequest{}, ) if err != nil { @@ -185,30 +184,30 @@ func startAllRuns(c *cli.Context) error { return nil } -var stopRunCommand = &cli.Command{ - Name: "stoprun", +var stopTaskCommand = &cli.Command{ + Name: "stoptask", Usage: "stops a strategy loaded into the server", ArgsUsage: "", - Action: stopRun, + Action: stopTask, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", - Usage: "the id of the backtest/livestrategy run", + Usage: "the id of the strategy task", }, }, } -func stopRun(c *cli.Context) error { +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) - if c.NArg() == 0 && c.NumFlags() == 0 { - return cli.ShowSubcommandHelp(c) - } - var id string if c.IsSet("id") { id = c.String("id") @@ -217,9 +216,9 @@ func stopRun(c *cli.Context) error { } client := btrpc.NewBacktesterServiceClient(conn) - result, err := client.StopRun( + result, err := client.StopTask( c.Context, - &btrpc.StopRunRequest{ + &btrpc.StopTaskRequest{ Id: id, }, ) @@ -232,13 +231,13 @@ func stopRun(c *cli.Context) error { return nil } -var stopAllRunsCommand = &cli.Command{ - Name: "stopallruns", +var stopAllTasksCommand = &cli.Command{ + Name: "stopalltasks", Usage: "stops all strategies loaded into the server", - Action: stopAllRuns, + Action: stopAllTasks, } -func stopAllRuns(c *cli.Context) error { +func stopAllTasks(c *cli.Context) error { conn, cancel, err := setupClient(c) if err != nil { return err @@ -246,9 +245,9 @@ func stopAllRuns(c *cli.Context) error { defer closeConn(conn, cancel) client := btrpc.NewBacktesterServiceClient(conn) - result, err := client.StopAllRuns( + result, err := client.StopAllTasks( c.Context, - &btrpc.StopAllRunsRequest{}, + &btrpc.StopAllTasksRequest{}, ) if err != nil { @@ -259,30 +258,30 @@ func stopAllRuns(c *cli.Context) error { return nil } -var clearRunCommand = &cli.Command{ - Name: "clearrun", +var clearTaskCommand = &cli.Command{ + Name: "cleartask", Usage: "clears/deletes a strategy loaded into the server - if it is not running", ArgsUsage: "", - Action: clearRun, + Action: clearTask, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", - Usage: "the id of the backtest/livestrategy run", + Usage: "the id of the strategy task", }, }, } -func clearRun(c *cli.Context) error { +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) - if c.NArg() == 0 && c.NumFlags() == 0 { - return cli.ShowSubcommandHelp(c) - } - var id string if c.IsSet("id") { id = c.String("id") @@ -291,9 +290,9 @@ func clearRun(c *cli.Context) error { } client := btrpc.NewBacktesterServiceClient(conn) - result, err := client.ClearRun( + result, err := client.ClearTask( c.Context, - &btrpc.ClearRunRequest{ + &btrpc.ClearTaskRequest{ Id: id, }, ) @@ -306,13 +305,13 @@ func clearRun(c *cli.Context) error { return nil } -var clearAllRunsCommand = &cli.Command{ - Name: "clearallruns", - Usage: "clears all strategies loaded into the server. Only runs not actively running will be cleared", - Action: clearAllRuns, +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 clearAllRuns(c *cli.Context) error { +func clearAllTasks(c *cli.Context) error { conn, cancel, err := setupClient(c) if err != nil { return err @@ -320,9 +319,9 @@ func clearAllRuns(c *cli.Context) error { defer closeConn(conn, cancel) client := btrpc.NewBacktesterServiceClient(conn) - result, err := client.ClearAllRuns( + result, err := client.ClearAllTasks( c.Context, - &btrpc.ClearAllRunsRequest{}, + &btrpc.ClearAllTasksRequest{}, ) if err != nil { @@ -335,7 +334,7 @@ func clearAllRuns(c *cli.Context) error { var executeStrategyFromConfigCommand = &cli.Command{ Name: "executestrategyfromconfig", - Usage: "runs the default strategy config but via passing in as a struct instead of a filepath - this is a proof-of-concept implementation", + 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{ @@ -359,7 +358,7 @@ func executeStrategyFromConfig(c *cli.Context) error { "..", "config", "strategyexamples", - "ftx-cash-carry.strat") + "dca-api-candles.strat") defaultConfig, err := config.ReadStrategyConfigFromFile(defaultPath) if err != nil { return err @@ -376,12 +375,16 @@ func executeStrategyFromConfig(c *cli.Context) error { currencySettings := make([]*btrpc.CurrencySettings, len(defaultConfig.CurrencySettings)) for i := range defaultConfig.CurrencySettings { - var sd *btrpc.SpotDetails + var sd btrpc.SpotDetails if defaultConfig.CurrencySettings[i].SpotDetails != nil { - sd.InitialBaseFunds = defaultConfig.CurrencySettings[i].SpotDetails.InitialBaseFunds.String() - sd.InitialQuoteFunds = defaultConfig.CurrencySettings[i].SpotDetails.InitialQuoteFunds.String() + 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 + var fd btrpc.FuturesDetails if defaultConfig.CurrencySettings[i].FuturesDetails != nil { fd.Leverage = &btrpc.Leverage{ CanUseLeverage: defaultConfig.CurrencySettings[i].FuturesDetails.Leverage.CanUseLeverage, @@ -413,8 +416,12 @@ func executeStrategyFromConfig(c *cli.Context) error { SkipCandleVolumeFitting: defaultConfig.CurrencySettings[i].SkipCandleVolumeFitting, UseExchangeOrderLimits: defaultConfig.CurrencySettings[i].CanUseExchangeLimits, UseExchangePnlCalculation: defaultConfig.CurrencySettings[i].UseExchangePNLCalculation, - SpotDetails: sd, - FuturesDetails: fd, + } + if sd.InitialQuoteFunds != "" || sd.InitialBaseFunds != "" { + currencySettings[i].SpotDetails = &sd + } + if fd.Leverage != nil { + currencySettings[i].FuturesDetails = &fd } } @@ -441,13 +448,28 @@ func executeStrategyFromConfig(c *cli.Context) error { } } 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{ - ApiKeyOverride: defaultConfig.DataSettings.LiveData.APIKeyOverride, - ApiSecretOverride: defaultConfig.DataSettings.LiveData.APISecretOverride, - ApiClientIdOverride: defaultConfig.DataSettings.LiveData.APIClientIDOverride, - Api_2FaOverride: defaultConfig.DataSettings.LiveData.API2FAOverride, - ApiSubAccountOverride: defaultConfig.DataSettings.LiveData.APISubAccountOverride, - UseRealOrders: defaultConfig.DataSettings.LiveData.RealOrders, + 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 { diff --git a/backtester/btcli/main.go b/backtester/btcli/main.go index 40e4f180..e5327555 100644 --- a/backtester/btcli/main.go +++ b/backtester/btcli/main.go @@ -112,13 +112,13 @@ func main() { app.Commands = []*cli.Command{ executeStrategyFromFileCommand, executeStrategyFromConfigCommand, - listAllRunsCommand, - startRunCommand, - startAllRunsCommand, - stopRunCommand, - stopAllRunsCommand, - clearRunCommand, - clearAllRunsCommand, + listAllTasksCommand, + startTaskCommand, + startAllTasksCommand, + stopTaskCommand, + stopAllTasksCommand, + clearTaskCommand, + clearAllTasksCommand, } ctx, cancel := context.WithCancel(context.Background()) diff --git a/backtester/btrpc/btrpc.pb.go b/backtester/btrpc/btrpc.pb.go index 5e211aa2..9c16a38d 100644 --- a/backtester/btrpc/btrpc.pb.go +++ b/backtester/btrpc/btrpc.pb.go @@ -1199,23 +1199,166 @@ func (x *CSVData) GetPath() string { return "" } +type ExchangeKeys struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` + ClientId string `protobuf:"bytes,3,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"` + PemKey string `protobuf:"bytes,4,opt,name=pem_key,json=pemKey,proto3" json:"pem_key,omitempty"` + SubAccount string `protobuf:"bytes,5,opt,name=sub_account,json=subAccount,proto3" json:"sub_account,omitempty"` + OneTimePassword string `protobuf:"bytes,6,opt,name=one_time_password,json=oneTimePassword,proto3" json:"one_time_password,omitempty"` +} + +func (x *ExchangeKeys) Reset() { + *x = ExchangeKeys{} + if protoimpl.UnsafeEnabled { + mi := &file_btrpc_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExchangeKeys) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExchangeKeys) ProtoMessage() {} + +func (x *ExchangeKeys) ProtoReflect() protoreflect.Message { + mi := &file_btrpc_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExchangeKeys.ProtoReflect.Descriptor instead. +func (*ExchangeKeys) Descriptor() ([]byte, []int) { + return file_btrpc_proto_rawDescGZIP(), []int{16} +} + +func (x *ExchangeKeys) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *ExchangeKeys) GetSecret() string { + if x != nil { + return x.Secret + } + return "" +} + +func (x *ExchangeKeys) GetClientId() string { + if x != nil { + return x.ClientId + } + return "" +} + +func (x *ExchangeKeys) GetPemKey() string { + if x != nil { + return x.PemKey + } + return "" +} + +func (x *ExchangeKeys) GetSubAccount() string { + if x != nil { + return x.SubAccount + } + return "" +} + +func (x *ExchangeKeys) GetOneTimePassword() string { + if x != nil { + return x.OneTimePassword + } + return "" +} + +type ExchangeCredentials struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Exchange string `protobuf:"bytes,1,opt,name=exchange,proto3" json:"exchange,omitempty"` + Keys *ExchangeKeys `protobuf:"bytes,2,opt,name=keys,proto3" json:"keys,omitempty"` +} + +func (x *ExchangeCredentials) Reset() { + *x = ExchangeCredentials{} + if protoimpl.UnsafeEnabled { + mi := &file_btrpc_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExchangeCredentials) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExchangeCredentials) ProtoMessage() {} + +func (x *ExchangeCredentials) ProtoReflect() protoreflect.Message { + mi := &file_btrpc_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExchangeCredentials.ProtoReflect.Descriptor instead. +func (*ExchangeCredentials) Descriptor() ([]byte, []int) { + return file_btrpc_proto_rawDescGZIP(), []int{17} +} + +func (x *ExchangeCredentials) GetExchange() string { + if x != nil { + return x.Exchange + } + return "" +} + +func (x *ExchangeCredentials) GetKeys() *ExchangeKeys { + if x != nil { + return x.Keys + } + return nil +} + type LiveData struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ApiKeyOverride string `protobuf:"bytes,1,opt,name=api_key_override,json=apiKeyOverride,proto3" json:"api_key_override,omitempty"` - ApiSecretOverride string `protobuf:"bytes,2,opt,name=api_secret_override,json=apiSecretOverride,proto3" json:"api_secret_override,omitempty"` - ApiClientIdOverride string `protobuf:"bytes,3,opt,name=api_client_id_override,json=apiClientIdOverride,proto3" json:"api_client_id_override,omitempty"` - Api_2FaOverride string `protobuf:"bytes,4,opt,name=api_2fa_override,json=api2faOverride,proto3" json:"api_2fa_override,omitempty"` - ApiSubAccountOverride string `protobuf:"bytes,5,opt,name=api_sub_account_override,json=apiSubAccountOverride,proto3" json:"api_sub_account_override,omitempty"` - UseRealOrders bool `protobuf:"varint,6,opt,name=use_real_orders,json=useRealOrders,proto3" json:"use_real_orders,omitempty"` + NewEventTimeout int64 `protobuf:"varint,1,opt,name=new_event_timeout,json=newEventTimeout,proto3" json:"new_event_timeout,omitempty"` + DataCheckTimer int64 `protobuf:"varint,2,opt,name=data_check_timer,json=dataCheckTimer,proto3" json:"data_check_timer,omitempty"` + RealOrders bool `protobuf:"varint,3,opt,name=real_orders,json=realOrders,proto3" json:"real_orders,omitempty"` + ClosePositionsOnStop bool `protobuf:"varint,4,opt,name=close_positions_on_stop,json=closePositionsOnStop,proto3" json:"close_positions_on_stop,omitempty"` + DataRequestRetryTolerance int64 `protobuf:"varint,5,opt,name=data_request_retry_tolerance,json=dataRequestRetryTolerance,proto3" json:"data_request_retry_tolerance,omitempty"` + DataRequestRetryWaitTime int64 `protobuf:"varint,6,opt,name=data_request_retry_wait_time,json=dataRequestRetryWaitTime,proto3" json:"data_request_retry_wait_time,omitempty"` + Credentials []*ExchangeCredentials `protobuf:"bytes,7,rep,name=credentials,proto3" json:"credentials,omitempty"` } func (x *LiveData) Reset() { *x = LiveData{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[16] + mi := &file_btrpc_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1228,7 +1371,7 @@ func (x *LiveData) String() string { func (*LiveData) ProtoMessage() {} func (x *LiveData) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[16] + mi := &file_btrpc_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1241,51 +1384,58 @@ func (x *LiveData) ProtoReflect() protoreflect.Message { // Deprecated: Use LiveData.ProtoReflect.Descriptor instead. func (*LiveData) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{16} + return file_btrpc_proto_rawDescGZIP(), []int{18} } -func (x *LiveData) GetApiKeyOverride() string { +func (x *LiveData) GetNewEventTimeout() int64 { if x != nil { - return x.ApiKeyOverride + return x.NewEventTimeout } - return "" + return 0 } -func (x *LiveData) GetApiSecretOverride() string { +func (x *LiveData) GetDataCheckTimer() int64 { if x != nil { - return x.ApiSecretOverride + return x.DataCheckTimer } - return "" + return 0 } -func (x *LiveData) GetApiClientIdOverride() string { +func (x *LiveData) GetRealOrders() bool { if x != nil { - return x.ApiClientIdOverride - } - return "" -} - -func (x *LiveData) GetApi_2FaOverride() string { - if x != nil { - return x.Api_2FaOverride - } - return "" -} - -func (x *LiveData) GetApiSubAccountOverride() string { - if x != nil { - return x.ApiSubAccountOverride - } - return "" -} - -func (x *LiveData) GetUseRealOrders() bool { - if x != nil { - return x.UseRealOrders + return x.RealOrders } return false } +func (x *LiveData) GetClosePositionsOnStop() bool { + if x != nil { + return x.ClosePositionsOnStop + } + return false +} + +func (x *LiveData) GetDataRequestRetryTolerance() int64 { + if x != nil { + return x.DataRequestRetryTolerance + } + return 0 +} + +func (x *LiveData) GetDataRequestRetryWaitTime() int64 { + if x != nil { + return x.DataRequestRetryWaitTime + } + return 0 +} + +func (x *LiveData) GetCredentials() []*ExchangeCredentials { + if x != nil { + return x.Credentials + } + return nil +} + type DataSettings struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1302,7 +1452,7 @@ type DataSettings struct { func (x *DataSettings) Reset() { *x = DataSettings{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[17] + mi := &file_btrpc_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1315,7 +1465,7 @@ func (x *DataSettings) String() string { func (*DataSettings) ProtoMessage() {} func (x *DataSettings) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[17] + mi := &file_btrpc_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1328,7 +1478,7 @@ func (x *DataSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use DataSettings.ProtoReflect.Descriptor instead. func (*DataSettings) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{17} + return file_btrpc_proto_rawDescGZIP(), []int{19} } func (x *DataSettings) GetInterval() uint64 { @@ -1387,7 +1537,7 @@ type Leverage struct { func (x *Leverage) Reset() { *x = Leverage{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[18] + mi := &file_btrpc_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1400,7 +1550,7 @@ func (x *Leverage) String() string { func (*Leverage) ProtoMessage() {} func (x *Leverage) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[18] + mi := &file_btrpc_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1413,7 +1563,7 @@ func (x *Leverage) ProtoReflect() protoreflect.Message { // Deprecated: Use Leverage.ProtoReflect.Descriptor instead. func (*Leverage) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{18} + return file_btrpc_proto_rawDescGZIP(), []int{20} } func (x *Leverage) GetCanUseLeverage() bool { @@ -1457,7 +1607,7 @@ type PortfolioSettings struct { func (x *PortfolioSettings) Reset() { *x = PortfolioSettings{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[19] + mi := &file_btrpc_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1470,7 +1620,7 @@ func (x *PortfolioSettings) String() string { func (*PortfolioSettings) ProtoMessage() {} func (x *PortfolioSettings) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[19] + mi := &file_btrpc_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1483,7 +1633,7 @@ func (x *PortfolioSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use PortfolioSettings.ProtoReflect.Descriptor instead. func (*PortfolioSettings) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{19} + return file_btrpc_proto_rawDescGZIP(), []int{21} } func (x *PortfolioSettings) GetLeverage() *Leverage { @@ -1518,7 +1668,7 @@ type StatisticSettings struct { func (x *StatisticSettings) Reset() { *x = StatisticSettings{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[20] + mi := &file_btrpc_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1531,7 +1681,7 @@ func (x *StatisticSettings) String() string { func (*StatisticSettings) ProtoMessage() {} func (x *StatisticSettings) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[20] + mi := &file_btrpc_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1544,7 +1694,7 @@ func (x *StatisticSettings) ProtoReflect() protoreflect.Message { // Deprecated: Use StatisticSettings.ProtoReflect.Descriptor instead. func (*StatisticSettings) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{20} + return file_btrpc_proto_rawDescGZIP(), []int{22} } func (x *StatisticSettings) GetRiskFreeRate() string { @@ -1572,7 +1722,7 @@ type Config struct { func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[21] + mi := &file_btrpc_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1585,7 +1735,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[21] + mi := &file_btrpc_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1598,7 +1748,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{21} + return file_btrpc_proto_rawDescGZIP(), []int{23} } func (x *Config) GetNickname() string { @@ -1657,7 +1807,7 @@ func (x *Config) GetStatisticSettings() *StatisticSettings { return nil } -type RunSummary struct { +type TaskSummary struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -1672,23 +1822,23 @@ type RunSummary struct { RealOrders bool `protobuf:"varint,8,opt,name=real_orders,json=realOrders,proto3" json:"real_orders,omitempty"` } -func (x *RunSummary) Reset() { - *x = RunSummary{} +func (x *TaskSummary) Reset() { + *x = TaskSummary{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[22] + mi := &file_btrpc_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } -func (x *RunSummary) String() string { +func (x *TaskSummary) String() string { return protoimpl.X.MessageStringOf(x) } -func (*RunSummary) ProtoMessage() {} +func (*TaskSummary) ProtoMessage() {} -func (x *RunSummary) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[22] +func (x *TaskSummary) ProtoReflect() protoreflect.Message { + mi := &file_btrpc_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1699,61 +1849,61 @@ func (x *RunSummary) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use RunSummary.ProtoReflect.Descriptor instead. -func (*RunSummary) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{22} +// Deprecated: Use TaskSummary.ProtoReflect.Descriptor instead. +func (*TaskSummary) Descriptor() ([]byte, []int) { + return file_btrpc_proto_rawDescGZIP(), []int{24} } -func (x *RunSummary) GetId() string { +func (x *TaskSummary) GetId() string { if x != nil { return x.Id } return "" } -func (x *RunSummary) GetStrategyName() string { +func (x *TaskSummary) GetStrategyName() string { if x != nil { return x.StrategyName } return "" } -func (x *RunSummary) GetDateLoaded() string { +func (x *TaskSummary) GetDateLoaded() string { if x != nil { return x.DateLoaded } return "" } -func (x *RunSummary) GetDateStarted() string { +func (x *TaskSummary) GetDateStarted() string { if x != nil { return x.DateStarted } return "" } -func (x *RunSummary) GetDateEnded() string { +func (x *TaskSummary) GetDateEnded() string { if x != nil { return x.DateEnded } return "" } -func (x *RunSummary) GetClosed() bool { +func (x *TaskSummary) GetClosed() bool { if x != nil { return x.Closed } return false } -func (x *RunSummary) GetLiveTesting() bool { +func (x *TaskSummary) GetLiveTesting() bool { if x != nil { return x.LiveTesting } return false } -func (x *RunSummary) GetRealOrders() bool { +func (x *TaskSummary) GetRealOrders() bool { if x != nil { return x.RealOrders } @@ -1774,7 +1924,7 @@ type ExecuteStrategyFromFileRequest struct { func (x *ExecuteStrategyFromFileRequest) Reset() { *x = ExecuteStrategyFromFileRequest{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[23] + mi := &file_btrpc_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1787,7 +1937,7 @@ func (x *ExecuteStrategyFromFileRequest) String() string { func (*ExecuteStrategyFromFileRequest) ProtoMessage() {} func (x *ExecuteStrategyFromFileRequest) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[23] + mi := &file_btrpc_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1800,7 +1950,7 @@ func (x *ExecuteStrategyFromFileRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteStrategyFromFileRequest.ProtoReflect.Descriptor instead. func (*ExecuteStrategyFromFileRequest) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{23} + return file_btrpc_proto_rawDescGZIP(), []int{25} } func (x *ExecuteStrategyFromFileRequest) GetStrategyFilePath() string { @@ -1829,13 +1979,13 @@ type ExecuteStrategyResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Run *RunSummary `protobuf:"bytes,1,opt,name=run,proto3" json:"run,omitempty"` + Task *TaskSummary `protobuf:"bytes,1,opt,name=task,proto3" json:"task,omitempty"` } func (x *ExecuteStrategyResponse) Reset() { *x = ExecuteStrategyResponse{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[24] + mi := &file_btrpc_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1848,7 +1998,7 @@ func (x *ExecuteStrategyResponse) String() string { func (*ExecuteStrategyResponse) ProtoMessage() {} func (x *ExecuteStrategyResponse) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[24] + mi := &file_btrpc_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1861,12 +2011,12 @@ func (x *ExecuteStrategyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteStrategyResponse.ProtoReflect.Descriptor instead. func (*ExecuteStrategyResponse) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{24} + return file_btrpc_proto_rawDescGZIP(), []int{26} } -func (x *ExecuteStrategyResponse) GetRun() *RunSummary { +func (x *ExecuteStrategyResponse) GetTask() *TaskSummary { if x != nil { - return x.Run + return x.Task } return nil } @@ -1884,7 +2034,7 @@ type ExecuteStrategyFromConfigRequest struct { func (x *ExecuteStrategyFromConfigRequest) Reset() { *x = ExecuteStrategyFromConfigRequest{} if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[25] + mi := &file_btrpc_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1897,7 +2047,7 @@ func (x *ExecuteStrategyFromConfigRequest) String() string { func (*ExecuteStrategyFromConfigRequest) ProtoMessage() {} func (x *ExecuteStrategyFromConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[25] + mi := &file_btrpc_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1910,7 +2060,7 @@ func (x *ExecuteStrategyFromConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteStrategyFromConfigRequest.ProtoReflect.Descriptor instead. func (*ExecuteStrategyFromConfigRequest) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{25} + return file_btrpc_proto_rawDescGZIP(), []int{27} } func (x *ExecuteStrategyFromConfigRequest) GetDoNotRunImmediately() bool { @@ -1934,101 +2084,14 @@ func (x *ExecuteStrategyFromConfigRequest) GetConfig() *Config { return nil } -type ListAllRunsRequest struct { +type ListAllTasksRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } -func (x *ListAllRunsRequest) Reset() { - *x = ListAllRunsRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[26] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListAllRunsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListAllRunsRequest) ProtoMessage() {} - -func (x *ListAllRunsRequest) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[26] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListAllRunsRequest.ProtoReflect.Descriptor instead. -func (*ListAllRunsRequest) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{26} -} - -type ListAllRunsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Runs []*RunSummary `protobuf:"bytes,1,rep,name=runs,proto3" json:"runs,omitempty"` -} - -func (x *ListAllRunsResponse) Reset() { - *x = ListAllRunsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_btrpc_proto_msgTypes[27] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ListAllRunsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ListAllRunsResponse) ProtoMessage() {} - -func (x *ListAllRunsResponse) ProtoReflect() protoreflect.Message { - mi := &file_btrpc_proto_msgTypes[27] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ListAllRunsResponse.ProtoReflect.Descriptor instead. -func (*ListAllRunsResponse) Descriptor() ([]byte, []int) { - return file_btrpc_proto_rawDescGZIP(), []int{27} -} - -func (x *ListAllRunsResponse) GetRuns() []*RunSummary { - if x != nil { - return x.Runs - } - return nil -} - -type StopRunRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *StopRunRequest) Reset() { - *x = StopRunRequest{} +func (x *ListAllTasksRequest) Reset() { + *x = ListAllTasksRequest{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2036,13 +2099,13 @@ func (x *StopRunRequest) Reset() { } } -func (x *StopRunRequest) String() string { +func (x *ListAllTasksRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopRunRequest) ProtoMessage() {} +func (*ListAllTasksRequest) ProtoMessage() {} -func (x *StopRunRequest) ProtoReflect() protoreflect.Message { +func (x *ListAllTasksRequest) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2054,28 +2117,21 @@ func (x *StopRunRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopRunRequest.ProtoReflect.Descriptor instead. -func (*StopRunRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ListAllTasksRequest.ProtoReflect.Descriptor instead. +func (*ListAllTasksRequest) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{28} } -func (x *StopRunRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type StopRunResponse struct { +type ListAllTasksResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - StoppedRun *RunSummary `protobuf:"bytes,1,opt,name=stopped_run,json=stoppedRun,proto3" json:"stopped_run,omitempty"` + Tasks []*TaskSummary `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"` } -func (x *StopRunResponse) Reset() { - *x = StopRunResponse{} +func (x *ListAllTasksResponse) Reset() { + *x = ListAllTasksResponse{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2083,13 +2139,13 @@ func (x *StopRunResponse) Reset() { } } -func (x *StopRunResponse) String() string { +func (x *ListAllTasksResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopRunResponse) ProtoMessage() {} +func (*ListAllTasksResponse) ProtoMessage() {} -func (x *StopRunResponse) ProtoReflect() protoreflect.Message { +func (x *ListAllTasksResponse) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2101,19 +2157,19 @@ func (x *StopRunResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopRunResponse.ProtoReflect.Descriptor instead. -func (*StopRunResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ListAllTasksResponse.ProtoReflect.Descriptor instead. +func (*ListAllTasksResponse) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{29} } -func (x *StopRunResponse) GetStoppedRun() *RunSummary { +func (x *ListAllTasksResponse) GetTasks() []*TaskSummary { if x != nil { - return x.StoppedRun + return x.Tasks } return nil } -type StartRunRequest struct { +type StopTaskRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -2121,8 +2177,8 @@ type StartRunRequest struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (x *StartRunRequest) Reset() { - *x = StartRunRequest{} +func (x *StopTaskRequest) Reset() { + *x = StopTaskRequest{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2130,13 +2186,13 @@ func (x *StartRunRequest) Reset() { } } -func (x *StartRunRequest) String() string { +func (x *StopTaskRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StartRunRequest) ProtoMessage() {} +func (*StopTaskRequest) ProtoMessage() {} -func (x *StartRunRequest) ProtoReflect() protoreflect.Message { +func (x *StopTaskRequest) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2148,28 +2204,28 @@ func (x *StartRunRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StartRunRequest.ProtoReflect.Descriptor instead. -func (*StartRunRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StopTaskRequest.ProtoReflect.Descriptor instead. +func (*StopTaskRequest) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{30} } -func (x *StartRunRequest) GetId() string { +func (x *StopTaskRequest) GetId() string { if x != nil { return x.Id } return "" } -type StartRunResponse struct { +type StopTaskResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Started bool `protobuf:"varint,1,opt,name=started,proto3" json:"started,omitempty"` + StoppedTask *TaskSummary `protobuf:"bytes,1,opt,name=stopped_task,json=stoppedTask,proto3" json:"stopped_task,omitempty"` } -func (x *StartRunResponse) Reset() { - *x = StartRunResponse{} +func (x *StopTaskResponse) Reset() { + *x = StopTaskResponse{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2177,13 +2233,13 @@ func (x *StartRunResponse) Reset() { } } -func (x *StartRunResponse) String() string { +func (x *StopTaskResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StartRunResponse) ProtoMessage() {} +func (*StopTaskResponse) ProtoMessage() {} -func (x *StartRunResponse) ProtoReflect() protoreflect.Message { +func (x *StopTaskResponse) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2195,26 +2251,28 @@ func (x *StartRunResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StartRunResponse.ProtoReflect.Descriptor instead. -func (*StartRunResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use StopTaskResponse.ProtoReflect.Descriptor instead. +func (*StopTaskResponse) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{31} } -func (x *StartRunResponse) GetStarted() bool { +func (x *StopTaskResponse) GetStoppedTask() *TaskSummary { if x != nil { - return x.Started + return x.StoppedTask } - return false + return nil } -type StartAllRunsRequest struct { +type StartTaskRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (x *StartAllRunsRequest) Reset() { - *x = StartAllRunsRequest{} +func (x *StartTaskRequest) Reset() { + *x = StartTaskRequest{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2222,13 +2280,13 @@ func (x *StartAllRunsRequest) Reset() { } } -func (x *StartAllRunsRequest) String() string { +func (x *StartTaskRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StartAllRunsRequest) ProtoMessage() {} +func (*StartTaskRequest) ProtoMessage() {} -func (x *StartAllRunsRequest) ProtoReflect() protoreflect.Message { +func (x *StartTaskRequest) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2240,21 +2298,28 @@ func (x *StartAllRunsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StartAllRunsRequest.ProtoReflect.Descriptor instead. -func (*StartAllRunsRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StartTaskRequest.ProtoReflect.Descriptor instead. +func (*StartTaskRequest) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{32} } -type StartAllRunsResponse struct { +func (x *StartTaskRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type StartTaskResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RunsStarted []string `protobuf:"bytes,1,rep,name=runs_started,json=runsStarted,proto3" json:"runs_started,omitempty"` + Started bool `protobuf:"varint,1,opt,name=started,proto3" json:"started,omitempty"` } -func (x *StartAllRunsResponse) Reset() { - *x = StartAllRunsResponse{} +func (x *StartTaskResponse) Reset() { + *x = StartTaskResponse{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2262,13 +2327,13 @@ func (x *StartAllRunsResponse) Reset() { } } -func (x *StartAllRunsResponse) String() string { +func (x *StartTaskResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StartAllRunsResponse) ProtoMessage() {} +func (*StartTaskResponse) ProtoMessage() {} -func (x *StartAllRunsResponse) ProtoReflect() protoreflect.Message { +func (x *StartTaskResponse) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2280,26 +2345,26 @@ func (x *StartAllRunsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StartAllRunsResponse.ProtoReflect.Descriptor instead. -func (*StartAllRunsResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use StartTaskResponse.ProtoReflect.Descriptor instead. +func (*StartTaskResponse) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{33} } -func (x *StartAllRunsResponse) GetRunsStarted() []string { +func (x *StartTaskResponse) GetStarted() bool { if x != nil { - return x.RunsStarted + return x.Started } - return nil + return false } -type StopAllRunsRequest struct { +type StartAllTasksRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } -func (x *StopAllRunsRequest) Reset() { - *x = StopAllRunsRequest{} +func (x *StartAllTasksRequest) Reset() { + *x = StartAllTasksRequest{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2307,13 +2372,13 @@ func (x *StopAllRunsRequest) Reset() { } } -func (x *StopAllRunsRequest) String() string { +func (x *StartAllTasksRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopAllRunsRequest) ProtoMessage() {} +func (*StartAllTasksRequest) ProtoMessage() {} -func (x *StopAllRunsRequest) ProtoReflect() protoreflect.Message { +func (x *StartAllTasksRequest) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2325,21 +2390,21 @@ func (x *StopAllRunsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopAllRunsRequest.ProtoReflect.Descriptor instead. -func (*StopAllRunsRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StartAllTasksRequest.ProtoReflect.Descriptor instead. +func (*StartAllTasksRequest) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{34} } -type StopAllRunsResponse struct { +type StartAllTasksResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RunsStopped []*RunSummary `protobuf:"bytes,1,rep,name=runs_stopped,json=runsStopped,proto3" json:"runs_stopped,omitempty"` + TasksStarted []string `protobuf:"bytes,1,rep,name=tasks_started,json=tasksStarted,proto3" json:"tasks_started,omitempty"` } -func (x *StopAllRunsResponse) Reset() { - *x = StopAllRunsResponse{} +func (x *StartAllTasksResponse) Reset() { + *x = StartAllTasksResponse{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2347,13 +2412,13 @@ func (x *StopAllRunsResponse) Reset() { } } -func (x *StopAllRunsResponse) String() string { +func (x *StartAllTasksResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*StopAllRunsResponse) ProtoMessage() {} +func (*StartAllTasksResponse) ProtoMessage() {} -func (x *StopAllRunsResponse) ProtoReflect() protoreflect.Message { +func (x *StartAllTasksResponse) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2365,28 +2430,26 @@ func (x *StopAllRunsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use StopAllRunsResponse.ProtoReflect.Descriptor instead. -func (*StopAllRunsResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use StartAllTasksResponse.ProtoReflect.Descriptor instead. +func (*StartAllTasksResponse) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{35} } -func (x *StopAllRunsResponse) GetRunsStopped() []*RunSummary { +func (x *StartAllTasksResponse) GetTasksStarted() []string { if x != nil { - return x.RunsStopped + return x.TasksStarted } return nil } -type ClearRunRequest struct { +type StopAllTasksRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (x *ClearRunRequest) Reset() { - *x = ClearRunRequest{} +func (x *StopAllTasksRequest) Reset() { + *x = StopAllTasksRequest{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2394,13 +2457,13 @@ func (x *ClearRunRequest) Reset() { } } -func (x *ClearRunRequest) String() string { +func (x *StopAllTasksRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ClearRunRequest) ProtoMessage() {} +func (*StopAllTasksRequest) ProtoMessage() {} -func (x *ClearRunRequest) ProtoReflect() protoreflect.Message { +func (x *StopAllTasksRequest) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2412,28 +2475,21 @@ func (x *ClearRunRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ClearRunRequest.ProtoReflect.Descriptor instead. -func (*ClearRunRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use StopAllTasksRequest.ProtoReflect.Descriptor instead. +func (*StopAllTasksRequest) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{36} } -func (x *ClearRunRequest) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -type ClearRunResponse struct { +type StopAllTasksResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ClearedRun *RunSummary `protobuf:"bytes,1,opt,name=cleared_run,json=clearedRun,proto3" json:"cleared_run,omitempty"` + TasksStopped []*TaskSummary `protobuf:"bytes,1,rep,name=tasks_stopped,json=tasksStopped,proto3" json:"tasks_stopped,omitempty"` } -func (x *ClearRunResponse) Reset() { - *x = ClearRunResponse{} +func (x *StopAllTasksResponse) Reset() { + *x = StopAllTasksResponse{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2441,13 +2497,13 @@ func (x *ClearRunResponse) Reset() { } } -func (x *ClearRunResponse) String() string { +func (x *StopAllTasksResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ClearRunResponse) ProtoMessage() {} +func (*StopAllTasksResponse) ProtoMessage() {} -func (x *ClearRunResponse) ProtoReflect() protoreflect.Message { +func (x *StopAllTasksResponse) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2459,26 +2515,28 @@ func (x *ClearRunResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ClearRunResponse.ProtoReflect.Descriptor instead. -func (*ClearRunResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use StopAllTasksResponse.ProtoReflect.Descriptor instead. +func (*StopAllTasksResponse) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{37} } -func (x *ClearRunResponse) GetClearedRun() *RunSummary { +func (x *StopAllTasksResponse) GetTasksStopped() []*TaskSummary { if x != nil { - return x.ClearedRun + return x.TasksStopped } return nil } -type ClearAllRunsRequest struct { +type ClearTaskRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` } -func (x *ClearAllRunsRequest) Reset() { - *x = ClearAllRunsRequest{} +func (x *ClearTaskRequest) Reset() { + *x = ClearTaskRequest{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2486,13 +2544,13 @@ func (x *ClearAllRunsRequest) Reset() { } } -func (x *ClearAllRunsRequest) String() string { +func (x *ClearTaskRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ClearAllRunsRequest) ProtoMessage() {} +func (*ClearTaskRequest) ProtoMessage() {} -func (x *ClearAllRunsRequest) ProtoReflect() protoreflect.Message { +func (x *ClearTaskRequest) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2504,22 +2562,28 @@ func (x *ClearAllRunsRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ClearAllRunsRequest.ProtoReflect.Descriptor instead. -func (*ClearAllRunsRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use ClearTaskRequest.ProtoReflect.Descriptor instead. +func (*ClearTaskRequest) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{38} } -type ClearAllRunsResponse struct { +func (x *ClearTaskRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type ClearTaskResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ClearedRuns []*RunSummary `protobuf:"bytes,1,rep,name=cleared_runs,json=clearedRuns,proto3" json:"cleared_runs,omitempty"` - RemainingRuns []*RunSummary `protobuf:"bytes,2,rep,name=remaining_runs,json=remainingRuns,proto3" json:"remaining_runs,omitempty"` + ClearedTask *TaskSummary `protobuf:"bytes,1,opt,name=cleared_task,json=clearedTask,proto3" json:"cleared_task,omitempty"` } -func (x *ClearAllRunsResponse) Reset() { - *x = ClearAllRunsResponse{} +func (x *ClearTaskResponse) Reset() { + *x = ClearTaskResponse{} if protoimpl.UnsafeEnabled { mi := &file_btrpc_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2527,13 +2591,13 @@ func (x *ClearAllRunsResponse) Reset() { } } -func (x *ClearAllRunsResponse) String() string { +func (x *ClearTaskResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*ClearAllRunsResponse) ProtoMessage() {} +func (*ClearTaskResponse) ProtoMessage() {} -func (x *ClearAllRunsResponse) ProtoReflect() protoreflect.Message { +func (x *ClearTaskResponse) ProtoReflect() protoreflect.Message { mi := &file_btrpc_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -2545,21 +2609,107 @@ func (x *ClearAllRunsResponse) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use ClearAllRunsResponse.ProtoReflect.Descriptor instead. -func (*ClearAllRunsResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use ClearTaskResponse.ProtoReflect.Descriptor instead. +func (*ClearTaskResponse) Descriptor() ([]byte, []int) { return file_btrpc_proto_rawDescGZIP(), []int{39} } -func (x *ClearAllRunsResponse) GetClearedRuns() []*RunSummary { +func (x *ClearTaskResponse) GetClearedTask() *TaskSummary { if x != nil { - return x.ClearedRuns + return x.ClearedTask } return nil } -func (x *ClearAllRunsResponse) GetRemainingRuns() []*RunSummary { +type ClearAllTasksRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ClearAllTasksRequest) Reset() { + *x = ClearAllTasksRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_btrpc_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClearAllTasksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearAllTasksRequest) ProtoMessage() {} + +func (x *ClearAllTasksRequest) ProtoReflect() protoreflect.Message { + mi := &file_btrpc_proto_msgTypes[40] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearAllTasksRequest.ProtoReflect.Descriptor instead. +func (*ClearAllTasksRequest) Descriptor() ([]byte, []int) { + return file_btrpc_proto_rawDescGZIP(), []int{40} +} + +type ClearAllTasksResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ClearedTasks []*TaskSummary `protobuf:"bytes,1,rep,name=cleared_tasks,json=clearedTasks,proto3" json:"cleared_tasks,omitempty"` + RemainingTasks []*TaskSummary `protobuf:"bytes,2,rep,name=remaining_tasks,json=remainingTasks,proto3" json:"remaining_tasks,omitempty"` +} + +func (x *ClearAllTasksResponse) Reset() { + *x = ClearAllTasksResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_btrpc_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClearAllTasksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClearAllTasksResponse) ProtoMessage() {} + +func (x *ClearAllTasksResponse) ProtoReflect() protoreflect.Message { + mi := &file_btrpc_proto_msgTypes[41] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ClearAllTasksResponse.ProtoReflect.Descriptor instead. +func (*ClearAllTasksResponse) Descriptor() ([]byte, []int) { + return file_btrpc_proto_rawDescGZIP(), []int{41} +} + +func (x *ClearAllTasksResponse) GetClearedTasks() []*TaskSummary { if x != nil { - return x.RemainingRuns + return x.ClearedTasks + } + return nil +} + +func (x *ClearAllTasksResponse) GetRemainingTasks() []*TaskSummary { + if x != nil { + return x.RemainingTasks } return nil } @@ -2760,249 +2910,276 @@ var file_btrpc_proto_rawDesc = []byte{ 0x52, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x45, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x22, 0x1d, 0x0a, 0x07, 0x43, 0x53, 0x56, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, - 0x68, 0x22, 0xa4, 0x02, 0x0a, 0x08, 0x4c, 0x69, 0x76, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x28, - 0x0a, 0x10, 0x61, 0x70, 0x69, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, - 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x70, 0x69, 0x4b, 0x65, 0x79, - 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x61, 0x70, 0x69, 0x5f, - 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x61, 0x70, 0x69, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x16, 0x61, 0x70, 0x69, 0x5f, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, - 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x61, 0x70, 0x69, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x49, 0x64, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x28, 0x0a, - 0x10, 0x61, 0x70, 0x69, 0x5f, 0x32, 0x66, 0x61, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x70, 0x69, 0x32, 0x66, 0x61, 0x4f, - 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x61, 0x70, 0x69, 0x5f, 0x73, - 0x75, 0x62, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, - 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x61, 0x70, 0x69, 0x53, 0x75, - 0x62, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, - 0x12, 0x26, 0x0a, 0x0f, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x75, 0x73, 0x65, 0x52, 0x65, - 0x61, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0x84, 0x02, 0x0a, 0x0c, 0x44, 0x61, 0x74, - 0x61, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x74, 0x79, 0x70, - 0x65, 0x12, 0x29, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x07, 0x61, 0x70, 0x69, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x0d, - 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, - 0x62, 0x61, 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x08, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x43, 0x53, 0x56, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x63, 0x73, 0x76, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x2c, 0x0a, 0x09, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x76, - 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6c, 0x69, 0x76, 0x65, 0x44, 0x61, 0x74, 0x61, 0x22, - 0xfd, 0x01, 0x0a, 0x08, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x10, - 0x63, 0x61, 0x6e, 0x5f, 0x75, 0x73, 0x65, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x63, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x4c, 0x65, - 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x4a, 0x0a, 0x22, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, - 0x6d, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x6c, 0x65, - 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x1e, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x73, 0x57, 0x69, 0x74, 0x68, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x74, - 0x69, 0x6f, 0x12, 0x32, 0x0a, 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6c, 0x65, - 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x13, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, - 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x20, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, - 0x6d, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x6c, 0x65, 0x76, - 0x65, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x1d, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, - 0x72, 0x61, 0x6c, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, - 0xa2, 0x01, 0x0a, 0x11, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, - 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x08, 0x62, 0x75, 0x79, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x72, - 0x63, 0x68, 0x61, 0x73, 0x65, 0x53, 0x69, 0x64, 0x65, 0x52, 0x07, 0x62, 0x75, 0x79, 0x53, 0x69, - 0x64, 0x65, 0x12, 0x30, 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, - 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x53, 0x69, 0x64, 0x65, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x6c, - 0x53, 0x69, 0x64, 0x65, 0x22, 0x39, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, - 0x63, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x69, 0x73, - 0x6b, 0x5f, 0x66, 0x72, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x72, 0x69, 0x73, 0x6b, 0x46, 0x72, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, - 0xd3, 0x03, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, - 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, - 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, 0x61, 0x6c, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x6f, 0x61, 0x6c, 0x12, 0x44, 0x0a, 0x11, 0x73, 0x74, - 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, - 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x10, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, - 0x12, 0x41, 0x0a, 0x10, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x52, 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x44, 0x0a, 0x11, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, - 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, - 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, - 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, - 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x38, 0x0a, 0x0d, 0x64, 0x61, 0x74, - 0x61, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x12, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, - 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x18, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, - 0x6f, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x11, 0x70, 0x6f, 0x72, 0x74, 0x66, - 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x12, - 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x52, 0x11, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x80, 0x02, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x53, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x65, 0x67, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, - 0x65, 0x5f, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x64, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x61, - 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x6c, - 0x6f, 0x73, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x69, 0x76, 0x65, - 0x54, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x6c, 0x5f, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, - 0x61, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0xa5, 0x01, 0x0a, 0x1e, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, - 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, - 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x46, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x6f, 0x5f, - 0x6e, 0x6f, 0x74, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, - 0x65, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x6f, 0x4e, 0x6f, 0x74, - 0x52, 0x75, 0x6e, 0x49, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x12, 0x20, - 0x0a, 0x0c, 0x64, 0x6f, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x22, 0x3e, 0x0a, 0x17, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x03, 0x72, - 0x75, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x03, 0x72, 0x75, 0x6e, - 0x22, 0xa0, 0x01, 0x0a, 0x20, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, - 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x6f, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, - 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x52, 0x75, 0x6e, 0x49, - 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x0c, 0x64, 0x6f, - 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0a, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x25, 0x0a, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x62, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x22, 0x14, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3c, 0x0a, 0x13, 0x4c, 0x69, 0x73, - 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x25, 0x0a, 0x04, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, - 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x52, 0x04, 0x72, 0x75, 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x70, 0x52, - 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x45, 0x0a, 0x0f, 0x53, 0x74, 0x6f, - 0x70, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x0b, - 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0a, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x52, 0x75, 0x6e, - 0x22, 0x21, 0x0a, 0x0f, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x22, 0x2c, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x75, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, - 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, - 0x64, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x39, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x75, 0x6e, 0x73, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x65, 0x64, 0x22, 0x14, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x75, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4b, 0x0a, 0x13, 0x53, 0x74, 0x6f, - 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x34, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x73, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, - 0x75, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0b, 0x72, 0x75, 0x6e, 0x73, 0x53, - 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x22, 0x21, 0x0a, 0x0f, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, - 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x46, 0x0a, 0x10, 0x43, 0x6c, 0x65, - 0x61, 0x72, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, - 0x0b, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0a, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x52, 0x75, - 0x6e, 0x22, 0x15, 0x0a, 0x13, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x86, 0x01, 0x0a, 0x14, 0x43, 0x6c, 0x65, - 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x34, 0x0a, 0x0c, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x72, 0x75, 0x6e, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x52, 0x75, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0b, 0x63, 0x6c, 0x65, 0x61, - 0x72, 0x65, 0x64, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x38, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x61, 0x69, - 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x75, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6e, - 0x73, 0x32, 0xa2, 0x07, 0x0a, 0x11, 0x42, 0x61, 0x63, 0x6b, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x85, 0x01, 0x0a, 0x17, 0x45, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x46, - 0x69, 0x6c, 0x65, 0x12, 0x25, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x46, - 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x62, 0x74, 0x72, - 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, - 0x67, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x1d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x66, 0x72, 0x6f, 0x6d, 0x66, 0x69, 0x6c, 0x65, 0x12, - 0x8b, 0x01, 0x0a, 0x19, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x27, 0x2e, - 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, + 0x68, 0x22, 0xbb, 0x01, 0x0a, 0x0c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4b, 0x65, + 0x79, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x65, 0x6d, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x65, 0x6d, 0x4b, + 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x41, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x6e, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, + 0x6f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, + 0x5a, 0x0a, 0x13, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x12, 0x27, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x4b, 0x65, 0x79, 0x73, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0xf7, 0x02, 0x0a, 0x08, + 0x4c, 0x69, 0x76, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x6e, 0x65, 0x77, 0x5f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x6e, 0x65, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, + 0x64, 0x61, 0x74, 0x61, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x12, 0x1f, + 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x35, 0x0a, 0x17, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x6f, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x14, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x4f, 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x3f, 0x0a, 0x1c, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x74, 0x6f, 0x6c, + 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x19, 0x64, 0x61, + 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x74, 0x72, 0x79, 0x54, 0x6f, + 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x1c, 0x64, 0x61, 0x74, 0x61, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x77, 0x61, + 0x69, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x64, + 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x74, 0x72, 0x79, 0x57, + 0x61, 0x69, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x84, 0x02, 0x0a, 0x0c, 0x44, 0x61, 0x74, 0x61, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x61, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x74, 0x79, 0x70, 0x65, 0x12, 0x29, + 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x70, 0x69, 0x44, 0x61, 0x74, 0x61, + 0x52, 0x07, 0x61, 0x70, 0x69, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x0d, 0x64, 0x61, 0x74, + 0x61, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, + 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x08, 0x63, 0x73, 0x76, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x53, + 0x56, 0x44, 0x61, 0x74, 0x61, 0x52, 0x07, 0x63, 0x73, 0x76, 0x44, 0x61, 0x74, 0x61, 0x12, 0x2c, + 0x0a, 0x09, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x76, 0x65, 0x44, 0x61, + 0x74, 0x61, 0x52, 0x08, 0x6c, 0x69, 0x76, 0x65, 0x44, 0x61, 0x74, 0x61, 0x22, 0xfd, 0x01, 0x0a, + 0x08, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x61, 0x6e, + 0x5f, 0x75, 0x73, 0x65, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0e, 0x63, 0x61, 0x6e, 0x55, 0x73, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x72, + 0x61, 0x67, 0x65, 0x12, 0x4a, 0x0a, 0x22, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6f, + 0x72, 0x64, 0x65, 0x72, 0x73, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x72, + 0x61, 0x67, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x1e, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x57, 0x69, + 0x74, 0x68, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, + 0x32, 0x0a, 0x15, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x72, + 0x61, 0x67, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, + 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, + 0x61, 0x74, 0x65, 0x12, 0x47, 0x0a, 0x20, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x63, + 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, + 0x67, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1d, 0x6d, + 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x43, 0x6f, 0x6c, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, + 0x4c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0xa2, 0x01, 0x0a, + 0x11, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x65, 0x76, + 0x65, 0x72, 0x61, 0x67, 0x65, 0x52, 0x08, 0x6c, 0x65, 0x76, 0x65, 0x72, 0x61, 0x67, 0x65, 0x12, + 0x2e, 0x0a, 0x08, 0x62, 0x75, 0x79, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, + 0x73, 0x65, 0x53, 0x69, 0x64, 0x65, 0x52, 0x07, 0x62, 0x75, 0x79, 0x53, 0x69, 0x64, 0x65, 0x12, + 0x30, 0x0a, 0x09, 0x73, 0x65, 0x6c, 0x6c, 0x5f, 0x73, 0x69, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x75, 0x72, 0x63, 0x68, + 0x61, 0x73, 0x65, 0x53, 0x69, 0x64, 0x65, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x6c, 0x53, 0x69, 0x64, + 0x65, 0x22, 0x39, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x72, 0x69, 0x73, 0x6b, 0x5f, 0x66, + 0x72, 0x65, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x72, 0x69, 0x73, 0x6b, 0x46, 0x72, 0x65, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0xd3, 0x03, 0x0a, + 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x6f, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x67, 0x6f, 0x61, 0x6c, 0x12, 0x44, 0x0a, 0x11, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x10, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x41, 0x0a, + 0x10, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x46, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, + 0x0f, 0x66, 0x75, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x12, 0x44, 0x0a, 0x11, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, 0x74, 0x74, + 0x69, 0x6e, 0x67, 0x73, 0x52, 0x10, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x38, 0x0a, 0x0d, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, + 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x12, 0x47, 0x0a, 0x12, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x5f, 0x73, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x53, 0x65, + 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x11, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, + 0x6f, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x12, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, + 0x11, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x22, 0x81, 0x02, 0x0a, 0x0b, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x65, 0x67, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x65, 0x5f, + 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x61, + 0x74, 0x65, 0x4c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x61, 0x74, 0x65, + 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x64, + 0x61, 0x74, 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, 0x6c, 0x6f, 0x73, + 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x65, 0x73, 0x74, 0x69, + 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x69, 0x76, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65, 0x61, 0x6c, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x22, 0xa5, 0x01, 0x0a, 0x1e, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x46, 0x69, + 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, + 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x6f, 0x5f, 0x6e, 0x6f, + 0x74, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x6c, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x52, 0x75, + 0x6e, 0x49, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x0c, + 0x64, 0x6f, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x22, 0x41, + 0x0a, 0x17, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x74, 0x61, 0x73, + 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x54, 0x61, 0x73, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x73, + 0x6b, 0x22, 0xa0, 0x01, 0x0a, 0x20, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1d, - 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x66, 0x72, 0x6f, 0x6d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x5d, 0x0a, - 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x12, 0x19, 0x2e, 0x62, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, - 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x72, 0x75, 0x6e, 0x73, 0x12, 0x51, 0x0a, 0x08, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x75, 0x6e, 0x12, 0x16, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, - 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x75, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x0e, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x72, 0x75, 0x6e, 0x12, - 0x61, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x12, - 0x1a, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, - 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x16, 0x64, 0x6f, 0x5f, 0x6e, 0x6f, 0x74, + 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x69, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x6c, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x52, 0x75, 0x6e, + 0x49, 0x6d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x0c, 0x64, + 0x6f, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x64, 0x6f, 0x4e, 0x6f, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x25, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, + 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x22, 0x15, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x54, + 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x40, 0x0a, 0x14, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x05, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x22, 0x21, 0x0a, + 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x49, 0x0a, 0x10, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0c, 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x5f, + 0x74, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0b, + 0x73, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x22, 0x22, 0x0a, 0x10, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x2d, 0x0a, 0x11, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x22, 0x16, + 0x0a, 0x14, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x3c, 0x0a, 0x15, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, + 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x65, 0x64, 0x22, 0x15, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x54, + 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x4f, 0x0a, 0x14, 0x53, + 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x0d, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x5f, 0x73, 0x74, 0x6f, + 0x70, 0x70, 0x65, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0c, + 0x74, 0x61, 0x73, 0x6b, 0x73, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x22, 0x22, 0x0a, 0x10, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x4a, 0x0a, 0x11, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0c, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, + 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, + 0x0b, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x22, 0x16, 0x0a, 0x14, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x22, 0x8d, 0x01, 0x0a, 0x15, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x41, 0x6c, + 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, + 0x0a, 0x0d, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, + 0x73, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0c, 0x63, 0x6c, 0x65, 0x61, 0x72, + 0x65, 0x64, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x3b, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x61, 0x69, + 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x54, + 0x61, 0x73, 0x6b, 0x73, 0x32, 0xb9, 0x07, 0x0a, 0x11, 0x42, 0x61, 0x63, 0x6b, 0x74, 0x65, 0x73, + 0x74, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x85, 0x01, 0x0a, 0x17, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, + 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x25, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, + 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, + 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, + 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x66, 0x72, 0x6f, 0x6d, 0x66, 0x69, + 0x6c, 0x65, 0x12, 0x8b, 0x01, 0x0a, 0x19, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, + 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x27, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, + 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x46, 0x72, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x62, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x1f, 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x66, 0x72, 0x6f, 0x6d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x61, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, + 0x12, 0x1a, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, + 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, + 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x74, 0x61, + 0x73, 0x6b, 0x73, 0x12, 0x55, 0x0a, 0x09, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, + 0x12, 0x17, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x61, + 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x62, 0x74, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x22, 0x0d, 0x2f, 0x76, 0x31, + 0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x60, 0x0a, 0x0d, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x1b, 0x2e, 0x62, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x22, 0x0c, + 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x61, 0x6c, 0x6c, 0x12, 0x51, 0x0a, 0x08, + 0x53, 0x74, 0x6f, 0x70, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x16, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x17, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x54, 0x61, 0x73, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x0e, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x74, 0x61, 0x73, 0x6b, 0x12, + 0x61, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, + 0x1a, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x54, + 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x62, 0x74, + 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, - 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x61, 0x6c, 0x6c, 0x72, 0x75, - 0x6e, 0x73, 0x12, 0x4d, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x75, 0x6e, 0x12, 0x15, 0x2e, - 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, - 0x70, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x13, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x0d, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x72, 0x75, - 0x6e, 0x12, 0x5d, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, - 0x12, 0x19, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, - 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x62, 0x74, - 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, - 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x61, 0x6c, 0x6c, 0x72, 0x75, 0x6e, 0x73, - 0x12, 0x51, 0x0a, 0x08, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x75, 0x6e, 0x12, 0x16, 0x2e, 0x62, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x65, - 0x61, 0x72, 0x52, 0x75, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x14, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x0e, 0x2a, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x65, 0x61, 0x72, - 0x72, 0x75, 0x6e, 0x12, 0x61, 0x0a, 0x0c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x52, - 0x75, 0x6e, 0x73, 0x12, 0x1a, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x65, 0x61, - 0x72, 0x41, 0x6c, 0x6c, 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1b, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x6c, - 0x52, 0x75, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x61, - 0x6c, 0x6c, 0x72, 0x75, 0x6e, 0x73, 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x68, 0x72, 0x61, 0x73, 0x68, 0x65, 0x72, 0x2d, 0x63, 0x6f, - 0x72, 0x70, 0x2f, 0x67, 0x6f, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x74, 0x72, 0x61, 0x64, 0x65, - 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x62, 0x74, 0x72, - 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x61, 0x6c, 0x6c, 0x74, 0x61, 0x73, + 0x6b, 0x73, 0x12, 0x55, 0x0a, 0x09, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x12, + 0x17, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x54, 0x61, 0x73, + 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, + 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x2a, 0x0d, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x6c, 0x65, 0x61, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x12, 0x65, 0x0a, 0x0d, 0x43, 0x6c, 0x65, + 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x12, 0x1b, 0x2e, 0x62, 0x74, 0x72, + 0x70, 0x63, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x62, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x43, 0x6c, 0x65, 0x61, 0x72, 0x41, 0x6c, 0x6c, 0x54, 0x61, 0x73, 0x6b, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x2a, 0x11, 0x2f, + 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x42, 0x3a, 0x5a, 0x38, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, + 0x68, 0x72, 0x61, 0x73, 0x68, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x67, 0x6f, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x6f, 0x74, 0x72, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x62, 0x61, 0x63, 0x6b, + 0x74, 0x65, 0x73, 0x74, 0x65, 0x72, 0x2f, 0x62, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3017,7 +3194,7 @@ func file_btrpc_proto_rawDescGZIP() []byte { return file_btrpc_proto_rawDescData } -var file_btrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 40) +var file_btrpc_proto_msgTypes = make([]protoimpl.MessageInfo, 42) var file_btrpc_proto_goTypes = []interface{}{ (*StrategySettings)(nil), // 0: btrpc.StrategySettings (*CustomSettings)(nil), // 1: btrpc.CustomSettings @@ -3035,93 +3212,97 @@ var file_btrpc_proto_goTypes = []interface{}{ (*DatabaseConfig)(nil), // 13: btrpc.DatabaseConfig (*DatabaseData)(nil), // 14: btrpc.DatabaseData (*CSVData)(nil), // 15: btrpc.CSVData - (*LiveData)(nil), // 16: btrpc.LiveData - (*DataSettings)(nil), // 17: btrpc.DataSettings - (*Leverage)(nil), // 18: btrpc.Leverage - (*PortfolioSettings)(nil), // 19: btrpc.PortfolioSettings - (*StatisticSettings)(nil), // 20: btrpc.StatisticSettings - (*Config)(nil), // 21: btrpc.Config - (*RunSummary)(nil), // 22: btrpc.RunSummary - (*ExecuteStrategyFromFileRequest)(nil), // 23: btrpc.ExecuteStrategyFromFileRequest - (*ExecuteStrategyResponse)(nil), // 24: btrpc.ExecuteStrategyResponse - (*ExecuteStrategyFromConfigRequest)(nil), // 25: btrpc.ExecuteStrategyFromConfigRequest - (*ListAllRunsRequest)(nil), // 26: btrpc.ListAllRunsRequest - (*ListAllRunsResponse)(nil), // 27: btrpc.ListAllRunsResponse - (*StopRunRequest)(nil), // 28: btrpc.StopRunRequest - (*StopRunResponse)(nil), // 29: btrpc.StopRunResponse - (*StartRunRequest)(nil), // 30: btrpc.StartRunRequest - (*StartRunResponse)(nil), // 31: btrpc.StartRunResponse - (*StartAllRunsRequest)(nil), // 32: btrpc.StartAllRunsRequest - (*StartAllRunsResponse)(nil), // 33: btrpc.StartAllRunsResponse - (*StopAllRunsRequest)(nil), // 34: btrpc.StopAllRunsRequest - (*StopAllRunsResponse)(nil), // 35: btrpc.StopAllRunsResponse - (*ClearRunRequest)(nil), // 36: btrpc.ClearRunRequest - (*ClearRunResponse)(nil), // 37: btrpc.ClearRunResponse - (*ClearAllRunsRequest)(nil), // 38: btrpc.ClearAllRunsRequest - (*ClearAllRunsResponse)(nil), // 39: btrpc.ClearAllRunsResponse - (*timestamppb.Timestamp)(nil), // 40: google.protobuf.Timestamp + (*ExchangeKeys)(nil), // 16: btrpc.ExchangeKeys + (*ExchangeCredentials)(nil), // 17: btrpc.ExchangeCredentials + (*LiveData)(nil), // 18: btrpc.LiveData + (*DataSettings)(nil), // 19: btrpc.DataSettings + (*Leverage)(nil), // 20: btrpc.Leverage + (*PortfolioSettings)(nil), // 21: btrpc.PortfolioSettings + (*StatisticSettings)(nil), // 22: btrpc.StatisticSettings + (*Config)(nil), // 23: btrpc.Config + (*TaskSummary)(nil), // 24: btrpc.TaskSummary + (*ExecuteStrategyFromFileRequest)(nil), // 25: btrpc.ExecuteStrategyFromFileRequest + (*ExecuteStrategyResponse)(nil), // 26: btrpc.ExecuteStrategyResponse + (*ExecuteStrategyFromConfigRequest)(nil), // 27: btrpc.ExecuteStrategyFromConfigRequest + (*ListAllTasksRequest)(nil), // 28: btrpc.ListAllTasksRequest + (*ListAllTasksResponse)(nil), // 29: btrpc.ListAllTasksResponse + (*StopTaskRequest)(nil), // 30: btrpc.StopTaskRequest + (*StopTaskResponse)(nil), // 31: btrpc.StopTaskResponse + (*StartTaskRequest)(nil), // 32: btrpc.StartTaskRequest + (*StartTaskResponse)(nil), // 33: btrpc.StartTaskResponse + (*StartAllTasksRequest)(nil), // 34: btrpc.StartAllTasksRequest + (*StartAllTasksResponse)(nil), // 35: btrpc.StartAllTasksResponse + (*StopAllTasksRequest)(nil), // 36: btrpc.StopAllTasksRequest + (*StopAllTasksResponse)(nil), // 37: btrpc.StopAllTasksResponse + (*ClearTaskRequest)(nil), // 38: btrpc.ClearTaskRequest + (*ClearTaskResponse)(nil), // 39: btrpc.ClearTaskResponse + (*ClearAllTasksRequest)(nil), // 40: btrpc.ClearAllTasksRequest + (*ClearAllTasksResponse)(nil), // 41: btrpc.ClearAllTasksResponse + (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp } var file_btrpc_proto_depIdxs = []int32{ 1, // 0: btrpc.StrategySettings.custom_settings:type_name -> btrpc.CustomSettings 2, // 1: btrpc.FundingSettings.exchange_level_funding:type_name -> btrpc.ExchangeLevelFunding - 18, // 2: btrpc.FuturesDetails.leverage:type_name -> btrpc.Leverage + 20, // 2: btrpc.FuturesDetails.leverage:type_name -> btrpc.Leverage 4, // 3: btrpc.CurrencySettings.buy_side:type_name -> btrpc.PurchaseSide 4, // 4: btrpc.CurrencySettings.sell_side:type_name -> btrpc.PurchaseSide 5, // 5: btrpc.CurrencySettings.spot_details:type_name -> btrpc.SpotDetails 6, // 6: btrpc.CurrencySettings.futures_details:type_name -> btrpc.FuturesDetails - 40, // 7: btrpc.ApiData.start_date:type_name -> google.protobuf.Timestamp - 40, // 8: btrpc.ApiData.end_date:type_name -> google.protobuf.Timestamp - 40, // 9: btrpc.DbData.start_date:type_name -> google.protobuf.Timestamp - 40, // 10: btrpc.DbData.end_date:type_name -> google.protobuf.Timestamp + 42, // 7: btrpc.ApiData.start_date:type_name -> google.protobuf.Timestamp + 42, // 8: btrpc.ApiData.end_date:type_name -> google.protobuf.Timestamp + 42, // 9: btrpc.DbData.start_date:type_name -> google.protobuf.Timestamp + 42, // 10: btrpc.DbData.end_date:type_name -> google.protobuf.Timestamp 9, // 11: btrpc.DbData.config:type_name -> btrpc.DbConfig 12, // 12: btrpc.DatabaseConfig.config:type_name -> btrpc.DatabaseConnectionDetails - 40, // 13: btrpc.DatabaseData.start_date:type_name -> google.protobuf.Timestamp - 40, // 14: btrpc.DatabaseData.end_date:type_name -> google.protobuf.Timestamp + 42, // 13: btrpc.DatabaseData.start_date:type_name -> google.protobuf.Timestamp + 42, // 14: btrpc.DatabaseData.end_date:type_name -> google.protobuf.Timestamp 13, // 15: btrpc.DatabaseData.config:type_name -> btrpc.DatabaseConfig - 8, // 16: btrpc.DataSettings.api_data:type_name -> btrpc.ApiData - 14, // 17: btrpc.DataSettings.database_data:type_name -> btrpc.DatabaseData - 15, // 18: btrpc.DataSettings.csv_data:type_name -> btrpc.CSVData - 16, // 19: btrpc.DataSettings.live_data:type_name -> btrpc.LiveData - 18, // 20: btrpc.PortfolioSettings.leverage:type_name -> btrpc.Leverage - 4, // 21: btrpc.PortfolioSettings.buy_side:type_name -> btrpc.PurchaseSide - 4, // 22: btrpc.PortfolioSettings.sell_side:type_name -> btrpc.PurchaseSide - 0, // 23: btrpc.Config.strategy_settings:type_name -> btrpc.StrategySettings - 3, // 24: btrpc.Config.funding_settings:type_name -> btrpc.FundingSettings - 7, // 25: btrpc.Config.currency_settings:type_name -> btrpc.CurrencySettings - 17, // 26: btrpc.Config.data_settings:type_name -> btrpc.DataSettings - 19, // 27: btrpc.Config.portfolio_settings:type_name -> btrpc.PortfolioSettings - 20, // 28: btrpc.Config.statistic_settings:type_name -> btrpc.StatisticSettings - 22, // 29: btrpc.ExecuteStrategyResponse.run:type_name -> btrpc.RunSummary - 21, // 30: btrpc.ExecuteStrategyFromConfigRequest.config:type_name -> btrpc.Config - 22, // 31: btrpc.ListAllRunsResponse.runs:type_name -> btrpc.RunSummary - 22, // 32: btrpc.StopRunResponse.stopped_run:type_name -> btrpc.RunSummary - 22, // 33: btrpc.StopAllRunsResponse.runs_stopped:type_name -> btrpc.RunSummary - 22, // 34: btrpc.ClearRunResponse.cleared_run:type_name -> btrpc.RunSummary - 22, // 35: btrpc.ClearAllRunsResponse.cleared_runs:type_name -> btrpc.RunSummary - 22, // 36: btrpc.ClearAllRunsResponse.remaining_runs:type_name -> btrpc.RunSummary - 23, // 37: btrpc.BacktesterService.ExecuteStrategyFromFile:input_type -> btrpc.ExecuteStrategyFromFileRequest - 25, // 38: btrpc.BacktesterService.ExecuteStrategyFromConfig:input_type -> btrpc.ExecuteStrategyFromConfigRequest - 26, // 39: btrpc.BacktesterService.ListAllRuns:input_type -> btrpc.ListAllRunsRequest - 30, // 40: btrpc.BacktesterService.StartRun:input_type -> btrpc.StartRunRequest - 32, // 41: btrpc.BacktesterService.StartAllRuns:input_type -> btrpc.StartAllRunsRequest - 28, // 42: btrpc.BacktesterService.StopRun:input_type -> btrpc.StopRunRequest - 34, // 43: btrpc.BacktesterService.StopAllRuns:input_type -> btrpc.StopAllRunsRequest - 36, // 44: btrpc.BacktesterService.ClearRun:input_type -> btrpc.ClearRunRequest - 38, // 45: btrpc.BacktesterService.ClearAllRuns:input_type -> btrpc.ClearAllRunsRequest - 24, // 46: btrpc.BacktesterService.ExecuteStrategyFromFile:output_type -> btrpc.ExecuteStrategyResponse - 24, // 47: btrpc.BacktesterService.ExecuteStrategyFromConfig:output_type -> btrpc.ExecuteStrategyResponse - 27, // 48: btrpc.BacktesterService.ListAllRuns:output_type -> btrpc.ListAllRunsResponse - 31, // 49: btrpc.BacktesterService.StartRun:output_type -> btrpc.StartRunResponse - 33, // 50: btrpc.BacktesterService.StartAllRuns:output_type -> btrpc.StartAllRunsResponse - 29, // 51: btrpc.BacktesterService.StopRun:output_type -> btrpc.StopRunResponse - 35, // 52: btrpc.BacktesterService.StopAllRuns:output_type -> btrpc.StopAllRunsResponse - 37, // 53: btrpc.BacktesterService.ClearRun:output_type -> btrpc.ClearRunResponse - 39, // 54: btrpc.BacktesterService.ClearAllRuns:output_type -> btrpc.ClearAllRunsResponse - 46, // [46:55] is the sub-list for method output_type - 37, // [37:46] is the sub-list for method input_type - 37, // [37:37] is the sub-list for extension type_name - 37, // [37:37] is the sub-list for extension extendee - 0, // [0:37] is the sub-list for field type_name + 16, // 16: btrpc.ExchangeCredentials.keys:type_name -> btrpc.ExchangeKeys + 17, // 17: btrpc.LiveData.credentials:type_name -> btrpc.ExchangeCredentials + 8, // 18: btrpc.DataSettings.api_data:type_name -> btrpc.ApiData + 14, // 19: btrpc.DataSettings.database_data:type_name -> btrpc.DatabaseData + 15, // 20: btrpc.DataSettings.csv_data:type_name -> btrpc.CSVData + 18, // 21: btrpc.DataSettings.live_data:type_name -> btrpc.LiveData + 20, // 22: btrpc.PortfolioSettings.leverage:type_name -> btrpc.Leverage + 4, // 23: btrpc.PortfolioSettings.buy_side:type_name -> btrpc.PurchaseSide + 4, // 24: btrpc.PortfolioSettings.sell_side:type_name -> btrpc.PurchaseSide + 0, // 25: btrpc.Config.strategy_settings:type_name -> btrpc.StrategySettings + 3, // 26: btrpc.Config.funding_settings:type_name -> btrpc.FundingSettings + 7, // 27: btrpc.Config.currency_settings:type_name -> btrpc.CurrencySettings + 19, // 28: btrpc.Config.data_settings:type_name -> btrpc.DataSettings + 21, // 29: btrpc.Config.portfolio_settings:type_name -> btrpc.PortfolioSettings + 22, // 30: btrpc.Config.statistic_settings:type_name -> btrpc.StatisticSettings + 24, // 31: btrpc.ExecuteStrategyResponse.task:type_name -> btrpc.TaskSummary + 23, // 32: btrpc.ExecuteStrategyFromConfigRequest.config:type_name -> btrpc.Config + 24, // 33: btrpc.ListAllTasksResponse.tasks:type_name -> btrpc.TaskSummary + 24, // 34: btrpc.StopTaskResponse.stopped_task:type_name -> btrpc.TaskSummary + 24, // 35: btrpc.StopAllTasksResponse.tasks_stopped:type_name -> btrpc.TaskSummary + 24, // 36: btrpc.ClearTaskResponse.cleared_task:type_name -> btrpc.TaskSummary + 24, // 37: btrpc.ClearAllTasksResponse.cleared_tasks:type_name -> btrpc.TaskSummary + 24, // 38: btrpc.ClearAllTasksResponse.remaining_tasks:type_name -> btrpc.TaskSummary + 25, // 39: btrpc.BacktesterService.ExecuteStrategyFromFile:input_type -> btrpc.ExecuteStrategyFromFileRequest + 27, // 40: btrpc.BacktesterService.ExecuteStrategyFromConfig:input_type -> btrpc.ExecuteStrategyFromConfigRequest + 28, // 41: btrpc.BacktesterService.ListAllTasks:input_type -> btrpc.ListAllTasksRequest + 32, // 42: btrpc.BacktesterService.StartTask:input_type -> btrpc.StartTaskRequest + 34, // 43: btrpc.BacktesterService.StartAllTasks:input_type -> btrpc.StartAllTasksRequest + 30, // 44: btrpc.BacktesterService.StopTask:input_type -> btrpc.StopTaskRequest + 36, // 45: btrpc.BacktesterService.StopAllTasks:input_type -> btrpc.StopAllTasksRequest + 38, // 46: btrpc.BacktesterService.ClearTask:input_type -> btrpc.ClearTaskRequest + 40, // 47: btrpc.BacktesterService.ClearAllTasks:input_type -> btrpc.ClearAllTasksRequest + 26, // 48: btrpc.BacktesterService.ExecuteStrategyFromFile:output_type -> btrpc.ExecuteStrategyResponse + 26, // 49: btrpc.BacktesterService.ExecuteStrategyFromConfig:output_type -> btrpc.ExecuteStrategyResponse + 29, // 50: btrpc.BacktesterService.ListAllTasks:output_type -> btrpc.ListAllTasksResponse + 33, // 51: btrpc.BacktesterService.StartTask:output_type -> btrpc.StartTaskResponse + 35, // 52: btrpc.BacktesterService.StartAllTasks:output_type -> btrpc.StartAllTasksResponse + 31, // 53: btrpc.BacktesterService.StopTask:output_type -> btrpc.StopTaskResponse + 37, // 54: btrpc.BacktesterService.StopAllTasks:output_type -> btrpc.StopAllTasksResponse + 39, // 55: btrpc.BacktesterService.ClearTask:output_type -> btrpc.ClearTaskResponse + 41, // 56: btrpc.BacktesterService.ClearAllTasks:output_type -> btrpc.ClearAllTasksResponse + 48, // [48:57] is the sub-list for method output_type + 39, // [39:48] is the sub-list for method input_type + 39, // [39:39] is the sub-list for extension type_name + 39, // [39:39] is the sub-list for extension extendee + 0, // [0:39] is the sub-list for field type_name } func init() { file_btrpc_proto_init() } @@ -3323,7 +3504,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LiveData); i { + switch v := v.(*ExchangeKeys); i { case 0: return &v.state case 1: @@ -3335,7 +3516,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DataSettings); i { + switch v := v.(*ExchangeCredentials); i { case 0: return &v.state case 1: @@ -3347,7 +3528,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Leverage); i { + switch v := v.(*LiveData); i { case 0: return &v.state case 1: @@ -3359,7 +3540,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PortfolioSettings); i { + switch v := v.(*DataSettings); i { case 0: return &v.state case 1: @@ -3371,7 +3552,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StatisticSettings); i { + switch v := v.(*Leverage); i { case 0: return &v.state case 1: @@ -3383,7 +3564,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Config); i { + switch v := v.(*PortfolioSettings); i { case 0: return &v.state case 1: @@ -3395,7 +3576,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RunSummary); i { + switch v := v.(*StatisticSettings); i { case 0: return &v.state case 1: @@ -3407,7 +3588,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecuteStrategyFromFileRequest); i { + switch v := v.(*Config); i { case 0: return &v.state case 1: @@ -3419,7 +3600,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecuteStrategyResponse); i { + switch v := v.(*TaskSummary); i { case 0: return &v.state case 1: @@ -3431,7 +3612,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecuteStrategyFromConfigRequest); i { + switch v := v.(*ExecuteStrategyFromFileRequest); i { case 0: return &v.state case 1: @@ -3443,7 +3624,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAllRunsRequest); i { + switch v := v.(*ExecuteStrategyResponse); i { case 0: return &v.state case 1: @@ -3455,7 +3636,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListAllRunsResponse); i { + switch v := v.(*ExecuteStrategyFromConfigRequest); i { case 0: return &v.state case 1: @@ -3467,7 +3648,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopRunRequest); i { + switch v := v.(*ListAllTasksRequest); i { case 0: return &v.state case 1: @@ -3479,7 +3660,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopRunResponse); i { + switch v := v.(*ListAllTasksResponse); i { case 0: return &v.state case 1: @@ -3491,7 +3672,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartRunRequest); i { + switch v := v.(*StopTaskRequest); i { case 0: return &v.state case 1: @@ -3503,7 +3684,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartRunResponse); i { + switch v := v.(*StopTaskResponse); i { case 0: return &v.state case 1: @@ -3515,7 +3696,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartAllRunsRequest); i { + switch v := v.(*StartTaskRequest); i { case 0: return &v.state case 1: @@ -3527,7 +3708,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StartAllRunsResponse); i { + switch v := v.(*StartTaskResponse); i { case 0: return &v.state case 1: @@ -3539,7 +3720,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopAllRunsRequest); i { + switch v := v.(*StartAllTasksRequest); i { case 0: return &v.state case 1: @@ -3551,7 +3732,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StopAllRunsResponse); i { + switch v := v.(*StartAllTasksResponse); i { case 0: return &v.state case 1: @@ -3563,7 +3744,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClearRunRequest); i { + switch v := v.(*StopAllTasksRequest); i { case 0: return &v.state case 1: @@ -3575,7 +3756,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClearRunResponse); i { + switch v := v.(*StopAllTasksResponse); i { case 0: return &v.state case 1: @@ -3587,7 +3768,7 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClearAllRunsRequest); i { + switch v := v.(*ClearTaskRequest); i { case 0: return &v.state case 1: @@ -3599,7 +3780,31 @@ func file_btrpc_proto_init() { } } file_btrpc_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClearAllRunsResponse); i { + switch v := v.(*ClearTaskResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_btrpc_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClearAllTasksRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_btrpc_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClearAllTasksResponse); i { case 0: return &v.state case 1: @@ -3617,7 +3822,7 @@ func file_btrpc_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_btrpc_proto_rawDesc, NumEnums: 0, - NumMessages: 40, + NumMessages: 42, NumExtensions: 0, NumServices: 1, }, diff --git a/backtester/btrpc/btrpc.pb.gw.go b/backtester/btrpc/btrpc.pb.gw.go index af889b5b..e649400e 100644 --- a/backtester/btrpc/btrpc.pb.gw.go +++ b/backtester/btrpc/btrpc.pb.gw.go @@ -103,182 +103,182 @@ func local_request_BacktesterService_ExecuteStrategyFromConfig_0(ctx context.Con } -func request_BacktesterService_ListAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ListAllRunsRequest +func request_BacktesterService_ListAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListAllTasksRequest var metadata runtime.ServerMetadata - msg, err := client.ListAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ListAllTasks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_BacktesterService_ListAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ListAllRunsRequest +func local_request_BacktesterService_ListAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListAllTasksRequest var metadata runtime.ServerMetadata - msg, err := server.ListAllRuns(ctx, &protoReq) + msg, err := server.ListAllTasks(ctx, &protoReq) return msg, metadata, err } var ( - filter_BacktesterService_StartRun_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + filter_BacktesterService_StartTask_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) -func request_BacktesterService_StartRun_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StartRunRequest +func request_BacktesterService_StartTask_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StartTaskRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StartRun_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StartTask_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.StartRun(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.StartTask(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_BacktesterService_StartRun_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StartRunRequest +func local_request_BacktesterService_StartTask_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StartTaskRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StartRun_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StartTask_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.StartRun(ctx, &protoReq) + msg, err := server.StartTask(ctx, &protoReq) return msg, metadata, err } -func request_BacktesterService_StartAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StartAllRunsRequest +func request_BacktesterService_StartAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StartAllTasksRequest var metadata runtime.ServerMetadata - msg, err := client.StartAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.StartAllTasks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_BacktesterService_StartAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StartAllRunsRequest +func local_request_BacktesterService_StartAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StartAllTasksRequest var metadata runtime.ServerMetadata - msg, err := server.StartAllRuns(ctx, &protoReq) + msg, err := server.StartAllTasks(ctx, &protoReq) return msg, metadata, err } var ( - filter_BacktesterService_StopRun_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + filter_BacktesterService_StopTask_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) -func request_BacktesterService_StopRun_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StopRunRequest +func request_BacktesterService_StopTask_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StopTaskRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StopRun_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StopTask_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.StopRun(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.StopTask(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_BacktesterService_StopRun_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StopRunRequest +func local_request_BacktesterService_StopTask_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StopTaskRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StopRun_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_StopTask_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.StopRun(ctx, &protoReq) + msg, err := server.StopTask(ctx, &protoReq) return msg, metadata, err } -func request_BacktesterService_StopAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StopAllRunsRequest +func request_BacktesterService_StopAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StopAllTasksRequest var metadata runtime.ServerMetadata - msg, err := client.StopAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.StopAllTasks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_BacktesterService_StopAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq StopAllRunsRequest +func local_request_BacktesterService_StopAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StopAllTasksRequest var metadata runtime.ServerMetadata - msg, err := server.StopAllRuns(ctx, &protoReq) + msg, err := server.StopAllTasks(ctx, &protoReq) return msg, metadata, err } var ( - filter_BacktesterService_ClearRun_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + filter_BacktesterService_ClearTask_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) -func request_BacktesterService_ClearRun_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ClearRunRequest +func request_BacktesterService_ClearTask_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ClearTaskRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ClearRun_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ClearTask_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.ClearRun(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ClearTask(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_BacktesterService_ClearRun_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ClearRunRequest +func local_request_BacktesterService_ClearTask_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ClearTaskRequest var metadata runtime.ServerMetadata if err := req.ParseForm(); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ClearRun_0); err != nil { + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_BacktesterService_ClearTask_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.ClearRun(ctx, &protoReq) + msg, err := server.ClearTask(ctx, &protoReq) return msg, metadata, err } -func request_BacktesterService_ClearAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ClearAllRunsRequest +func request_BacktesterService_ClearAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, client BacktesterServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ClearAllTasksRequest var metadata runtime.ServerMetadata - msg, err := client.ClearAllRuns(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.ClearAllTasks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_BacktesterService_ClearAllRuns_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq ClearAllRunsRequest +func local_request_BacktesterService_ClearAllTasks_0(ctx context.Context, marshaler runtime.Marshaler, server BacktesterServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ClearAllTasksRequest var metadata runtime.ServerMetadata - msg, err := server.ClearAllRuns(ctx, &protoReq) + msg, err := server.ClearAllTasks(ctx, &protoReq) return msg, metadata, err } @@ -339,7 +339,7 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se }) - mux.Handle("GET", pattern_BacktesterService_ListAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_BacktesterService_ListAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -347,12 +347,12 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ListAllRuns", runtime.WithHTTPPathPattern("/v1/listallruns")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ListAllTasks", runtime.WithHTTPPathPattern("/v1/listalltasks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_BacktesterService_ListAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_BacktesterService_ListAllTasks_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -360,11 +360,11 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se return } - forward_BacktesterService_ListAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_ListAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StartRun_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StartTask_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -372,12 +372,12 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StartRun", runtime.WithHTTPPathPattern("/v1/startrun")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StartTask", runtime.WithHTTPPathPattern("/v1/starttask")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_BacktesterService_StartRun_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_BacktesterService_StartTask_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -385,11 +385,11 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se return } - forward_BacktesterService_StartRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StartTask_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StartAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StartAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -397,12 +397,12 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StartAllRuns", runtime.WithHTTPPathPattern("/v1/startallruns")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StartAllTasks", runtime.WithHTTPPathPattern("/v1/startall")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_BacktesterService_StartAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_BacktesterService_StartAllTasks_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -410,11 +410,11 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se return } - forward_BacktesterService_StartAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StartAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StopRun_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StopTask_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -422,12 +422,12 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StopRun", runtime.WithHTTPPathPattern("/v1/stoprun")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StopTask", runtime.WithHTTPPathPattern("/v1/stoptask")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_BacktesterService_StopRun_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_BacktesterService_StopTask_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -435,11 +435,11 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se return } - forward_BacktesterService_StopRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StopTask_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StopAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StopAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -447,12 +447,12 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StopAllRuns", runtime.WithHTTPPathPattern("/v1/stopallruns")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/StopAllTasks", runtime.WithHTTPPathPattern("/v1/stopalltasks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_BacktesterService_StopAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_BacktesterService_StopAllTasks_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -460,11 +460,11 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se return } - forward_BacktesterService_StopAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StopAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("DELETE", pattern_BacktesterService_ClearRun_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("DELETE", pattern_BacktesterService_ClearTask_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -472,12 +472,12 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ClearRun", runtime.WithHTTPPathPattern("/v1/clearrun")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ClearTask", runtime.WithHTTPPathPattern("/v1/cleartask")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_BacktesterService_ClearRun_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_BacktesterService_ClearTask_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -485,11 +485,11 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se return } - forward_BacktesterService_ClearRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_ClearTask_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("DELETE", pattern_BacktesterService_ClearAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("DELETE", pattern_BacktesterService_ClearAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -497,12 +497,12 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ClearAllRuns", runtime.WithHTTPPathPattern("/v1/clearallruns")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/btrpc.BacktesterService/ClearAllTasks", runtime.WithHTTPPathPattern("/v1/clearalltasks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_BacktesterService_ClearAllRuns_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_BacktesterService_ClearAllTasks_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -510,7 +510,7 @@ func RegisterBacktesterServiceHandlerServer(ctx context.Context, mux *runtime.Se return } - forward_BacktesterService_ClearAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_ClearAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -599,157 +599,157 @@ func RegisterBacktesterServiceHandlerClient(ctx context.Context, mux *runtime.Se }) - mux.Handle("GET", pattern_BacktesterService_ListAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("GET", pattern_BacktesterService_ListAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ListAllRuns", runtime.WithHTTPPathPattern("/v1/listallruns")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ListAllTasks", runtime.WithHTTPPathPattern("/v1/listalltasks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_BacktesterService_ListAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_BacktesterService_ListAllTasks_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_BacktesterService_ListAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_ListAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StartRun_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StartTask_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StartRun", runtime.WithHTTPPathPattern("/v1/startrun")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StartTask", runtime.WithHTTPPathPattern("/v1/starttask")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_BacktesterService_StartRun_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_BacktesterService_StartTask_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_BacktesterService_StartRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StartTask_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StartAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StartAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StartAllRuns", runtime.WithHTTPPathPattern("/v1/startallruns")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StartAllTasks", runtime.WithHTTPPathPattern("/v1/startall")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_BacktesterService_StartAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_BacktesterService_StartAllTasks_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_BacktesterService_StartAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StartAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StopRun_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StopTask_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StopRun", runtime.WithHTTPPathPattern("/v1/stoprun")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StopTask", runtime.WithHTTPPathPattern("/v1/stoptask")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_BacktesterService_StopRun_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_BacktesterService_StopTask_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_BacktesterService_StopRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StopTask_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("POST", pattern_BacktesterService_StopAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_BacktesterService_StopAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StopAllRuns", runtime.WithHTTPPathPattern("/v1/stopallruns")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/StopAllTasks", runtime.WithHTTPPathPattern("/v1/stopalltasks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_BacktesterService_StopAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_BacktesterService_StopAllTasks_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_BacktesterService_StopAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_StopAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("DELETE", pattern_BacktesterService_ClearRun_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("DELETE", pattern_BacktesterService_ClearTask_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ClearRun", runtime.WithHTTPPathPattern("/v1/clearrun")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ClearTask", runtime.WithHTTPPathPattern("/v1/cleartask")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_BacktesterService_ClearRun_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_BacktesterService_ClearTask_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_BacktesterService_ClearRun_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_ClearTask_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) - mux.Handle("DELETE", pattern_BacktesterService_ClearAllRuns_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("DELETE", pattern_BacktesterService_ClearAllTasks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ClearAllRuns", runtime.WithHTTPPathPattern("/v1/clearallruns")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/btrpc.BacktesterService/ClearAllTasks", runtime.WithHTTPPathPattern("/v1/clearalltasks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_BacktesterService_ClearAllRuns_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_BacktesterService_ClearAllTasks_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_BacktesterService_ClearAllRuns_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_BacktesterService_ClearAllTasks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -761,19 +761,19 @@ var ( pattern_BacktesterService_ExecuteStrategyFromConfig_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "executestrategyfromconfig"}, "")) - pattern_BacktesterService_ListAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "listallruns"}, "")) + pattern_BacktesterService_ListAllTasks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "listalltasks"}, "")) - pattern_BacktesterService_StartRun_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "startrun"}, "")) + pattern_BacktesterService_StartTask_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "starttask"}, "")) - pattern_BacktesterService_StartAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "startallruns"}, "")) + pattern_BacktesterService_StartAllTasks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "startall"}, "")) - pattern_BacktesterService_StopRun_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "stoprun"}, "")) + pattern_BacktesterService_StopTask_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "stoptask"}, "")) - pattern_BacktesterService_StopAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "stopallruns"}, "")) + pattern_BacktesterService_StopAllTasks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "stopalltasks"}, "")) - pattern_BacktesterService_ClearRun_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "clearrun"}, "")) + pattern_BacktesterService_ClearTask_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "cleartask"}, "")) - pattern_BacktesterService_ClearAllRuns_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "clearallruns"}, "")) + pattern_BacktesterService_ClearAllTasks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "clearalltasks"}, "")) ) var ( @@ -781,17 +781,17 @@ var ( forward_BacktesterService_ExecuteStrategyFromConfig_0 = runtime.ForwardResponseMessage - forward_BacktesterService_ListAllRuns_0 = runtime.ForwardResponseMessage + forward_BacktesterService_ListAllTasks_0 = runtime.ForwardResponseMessage - forward_BacktesterService_StartRun_0 = runtime.ForwardResponseMessage + forward_BacktesterService_StartTask_0 = runtime.ForwardResponseMessage - forward_BacktesterService_StartAllRuns_0 = runtime.ForwardResponseMessage + forward_BacktesterService_StartAllTasks_0 = runtime.ForwardResponseMessage - forward_BacktesterService_StopRun_0 = runtime.ForwardResponseMessage + forward_BacktesterService_StopTask_0 = runtime.ForwardResponseMessage - forward_BacktesterService_StopAllRuns_0 = runtime.ForwardResponseMessage + forward_BacktesterService_StopAllTasks_0 = runtime.ForwardResponseMessage - forward_BacktesterService_ClearRun_0 = runtime.ForwardResponseMessage + forward_BacktesterService_ClearTask_0 = runtime.ForwardResponseMessage - forward_BacktesterService_ClearAllRuns_0 = runtime.ForwardResponseMessage + forward_BacktesterService_ClearAllTasks_0 = runtime.ForwardResponseMessage ) diff --git a/backtester/btrpc/btrpc.swagger.json b/backtester/btrpc/btrpc.swagger.json index 1b5e6e9e..9ed108a5 100644 --- a/backtester/btrpc/btrpc.swagger.json +++ b/backtester/btrpc/btrpc.swagger.json @@ -16,14 +16,14 @@ "application/json" ], "paths": { - "/v1/clearallruns": { + "/v1/clearalltasks": { "delete": { - "operationId": "BacktesterService_ClearAllRuns", + "operationId": "BacktesterService_ClearAllTasks", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/btrpcClearAllRunsResponse" + "$ref": "#/definitions/btrpcClearAllTasksResponse" } }, "default": { @@ -38,14 +38,14 @@ ] } }, - "/v1/clearrun": { + "/v1/cleartask": { "delete": { - "operationId": "BacktesterService_ClearRun", + "operationId": "BacktesterService_ClearTask", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/btrpcClearRunResponse" + "$ref": "#/definitions/btrpcClearTaskResponse" } }, "default": { @@ -255,41 +255,45 @@ "type": "string" }, { - "name": "config.dataSettings.liveData.apiKeyOverride", + "name": "config.dataSettings.liveData.newEventTimeout", "in": "query", "required": false, - "type": "string" + "type": "string", + "format": "int64" }, { - "name": "config.dataSettings.liveData.apiSecretOverride", + "name": "config.dataSettings.liveData.dataCheckTimer", "in": "query", "required": false, - "type": "string" + "type": "string", + "format": "int64" }, { - "name": "config.dataSettings.liveData.apiClientIdOverride", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "config.dataSettings.liveData.api2faOverride", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "config.dataSettings.liveData.apiSubAccountOverride", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "config.dataSettings.liveData.useRealOrders", + "name": "config.dataSettings.liveData.realOrders", "in": "query", "required": false, "type": "boolean" }, + { + "name": "config.dataSettings.liveData.closePositionsOnStop", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "config.dataSettings.liveData.dataRequestRetryTolerance", + "in": "query", + "required": false, + "type": "string", + "format": "int64" + }, + { + "name": "config.dataSettings.liveData.dataRequestRetryWaitTime", + "in": "query", + "required": false, + "type": "string", + "format": "int64" + }, { "name": "config.portfolioSettings.leverage.canUseLeverage", "in": "query", @@ -404,14 +408,14 @@ ] } }, - "/v1/listallruns": { + "/v1/listalltasks": { "get": { - "operationId": "BacktesterService_ListAllRuns", + "operationId": "BacktesterService_ListAllTasks", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/btrpcListAllRunsResponse" + "$ref": "#/definitions/btrpcListAllTasksResponse" } }, "default": { @@ -426,14 +430,14 @@ ] } }, - "/v1/startallruns": { + "/v1/startall": { "post": { - "operationId": "BacktesterService_StartAllRuns", + "operationId": "BacktesterService_StartAllTasks", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/btrpcStartAllRunsResponse" + "$ref": "#/definitions/btrpcStartAllTasksResponse" } }, "default": { @@ -448,14 +452,14 @@ ] } }, - "/v1/startrun": { + "/v1/starttask": { "post": { - "operationId": "BacktesterService_StartRun", + "operationId": "BacktesterService_StartTask", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/btrpcStartRunResponse" + "$ref": "#/definitions/btrpcStartTaskResponse" } }, "default": { @@ -478,14 +482,14 @@ ] } }, - "/v1/stopallruns": { + "/v1/stopalltasks": { "post": { - "operationId": "BacktesterService_StopAllRuns", + "operationId": "BacktesterService_StopAllTasks", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/btrpcStopAllRunsResponse" + "$ref": "#/definitions/btrpcStopAllTasksResponse" } }, "default": { @@ -500,14 +504,14 @@ ] } }, - "/v1/stoprun": { + "/v1/stoptask": { "post": { - "operationId": "BacktesterService_StopRun", + "operationId": "BacktesterService_StopTask", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/btrpcStopRunResponse" + "$ref": "#/definitions/btrpcStopTaskResponse" } }, "default": { @@ -556,28 +560,28 @@ } } }, - "btrpcClearAllRunsResponse": { + "btrpcClearAllTasksResponse": { "type": "object", "properties": { - "clearedRuns": { + "clearedTasks": { "type": "array", "items": { - "$ref": "#/definitions/btrpcRunSummary" + "$ref": "#/definitions/btrpcTaskSummary" } }, - "remainingRuns": { + "remainingTasks": { "type": "array", "items": { - "$ref": "#/definitions/btrpcRunSummary" + "$ref": "#/definitions/btrpcTaskSummary" } } } }, - "btrpcClearRunResponse": { + "btrpcClearTaskResponse": { "type": "object", "properties": { - "clearedRun": { - "$ref": "#/definitions/btrpcRunSummary" + "clearedTask": { + "$ref": "#/definitions/btrpcTaskSummary" } } }, @@ -764,6 +768,40 @@ } } }, + "btrpcExchangeCredentials": { + "type": "object", + "properties": { + "exchange": { + "type": "string" + }, + "keys": { + "$ref": "#/definitions/btrpcExchangeKeys" + } + } + }, + "btrpcExchangeKeys": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "clientId": { + "type": "string" + }, + "pemKey": { + "type": "string" + }, + "subAccount": { + "type": "string" + }, + "oneTimePassword": { + "type": "string" + } + } + }, "btrpcExchangeLevelFunding": { "type": "object", "properties": { @@ -787,8 +825,8 @@ "btrpcExecuteStrategyResponse": { "type": "object", "properties": { - "run": { - "$ref": "#/definitions/btrpcRunSummary" + "task": { + "$ref": "#/definitions/btrpcTaskSummary" } } }, @@ -831,13 +869,13 @@ } } }, - "btrpcListAllRunsResponse": { + "btrpcListAllTasksResponse": { "type": "object", "properties": { - "runs": { + "tasks": { "type": "array", "items": { - "$ref": "#/definitions/btrpcRunSummary" + "$ref": "#/definitions/btrpcTaskSummary" } } } @@ -845,23 +883,33 @@ "btrpcLiveData": { "type": "object", "properties": { - "apiKeyOverride": { - "type": "string" + "newEventTimeout": { + "type": "string", + "format": "int64" }, - "apiSecretOverride": { - "type": "string" + "dataCheckTimer": { + "type": "string", + "format": "int64" }, - "apiClientIdOverride": { - "type": "string" - }, - "api2faOverride": { - "type": "string" - }, - "apiSubAccountOverride": { - "type": "string" - }, - "useRealOrders": { + "realOrders": { "type": "boolean" + }, + "closePositionsOnStop": { + "type": "boolean" + }, + "dataRequestRetryTolerance": { + "type": "string", + "format": "int64" + }, + "dataRequestRetryWaitTime": { + "type": "string", + "format": "int64" + }, + "credentials": { + "type": "array", + "items": { + "$ref": "#/definitions/btrpcExchangeCredentials" + } } } }, @@ -893,7 +941,85 @@ } } }, - "btrpcRunSummary": { + "btrpcSpotDetails": { + "type": "object", + "properties": { + "initialBaseFunds": { + "type": "string" + }, + "initialQuoteFunds": { + "type": "string" + } + } + }, + "btrpcStartAllTasksResponse": { + "type": "object", + "properties": { + "tasksStarted": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "btrpcStartTaskResponse": { + "type": "object", + "properties": { + "started": { + "type": "boolean" + } + } + }, + "btrpcStatisticSettings": { + "type": "object", + "properties": { + "riskFreeRate": { + "type": "string" + } + } + }, + "btrpcStopAllTasksResponse": { + "type": "object", + "properties": { + "tasksStopped": { + "type": "array", + "items": { + "$ref": "#/definitions/btrpcTaskSummary" + } + } + } + }, + "btrpcStopTaskResponse": { + "type": "object", + "properties": { + "stoppedTask": { + "$ref": "#/definitions/btrpcTaskSummary" + } + } + }, + "btrpcStrategySettings": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "useSimultaneousSignalProcessing": { + "type": "boolean" + }, + "disableUsdTracking": { + "type": "boolean" + }, + "customSettings": { + "type": "array", + "items": { + "$ref": "#/definitions/btrpcCustomSettings" + } + } + }, + "title": "struct definitions" + }, + "btrpcTaskSummary": { "type": "object", "properties": { "id": { @@ -922,84 +1048,6 @@ } } }, - "btrpcSpotDetails": { - "type": "object", - "properties": { - "initialBaseFunds": { - "type": "string" - }, - "initialQuoteFunds": { - "type": "string" - } - } - }, - "btrpcStartAllRunsResponse": { - "type": "object", - "properties": { - "runsStarted": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "btrpcStartRunResponse": { - "type": "object", - "properties": { - "started": { - "type": "boolean" - } - } - }, - "btrpcStatisticSettings": { - "type": "object", - "properties": { - "riskFreeRate": { - "type": "string" - } - } - }, - "btrpcStopAllRunsResponse": { - "type": "object", - "properties": { - "runsStopped": { - "type": "array", - "items": { - "$ref": "#/definitions/btrpcRunSummary" - } - } - } - }, - "btrpcStopRunResponse": { - "type": "object", - "properties": { - "stoppedRun": { - "$ref": "#/definitions/btrpcRunSummary" - } - } - }, - "btrpcStrategySettings": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "useSimultaneousSignalProcessing": { - "type": "boolean" - }, - "disableUsdTracking": { - "type": "boolean" - }, - "customSettings": { - "type": "array", - "items": { - "$ref": "#/definitions/btrpcCustomSettings" - } - } - }, - "title": "struct definitions" - }, "protobufAny": { "type": "object", "properties": { diff --git a/backtester/btrpc/btrpc_grpc.pb.go b/backtester/btrpc/btrpc_grpc.pb.go index 8fc4b7c8..c6ddb532 100644 --- a/backtester/btrpc/btrpc_grpc.pb.go +++ b/backtester/btrpc/btrpc_grpc.pb.go @@ -24,13 +24,13 @@ const _ = grpc.SupportPackageIsVersion7 type BacktesterServiceClient interface { ExecuteStrategyFromFile(ctx context.Context, in *ExecuteStrategyFromFileRequest, opts ...grpc.CallOption) (*ExecuteStrategyResponse, error) ExecuteStrategyFromConfig(ctx context.Context, in *ExecuteStrategyFromConfigRequest, opts ...grpc.CallOption) (*ExecuteStrategyResponse, error) - ListAllRuns(ctx context.Context, in *ListAllRunsRequest, opts ...grpc.CallOption) (*ListAllRunsResponse, error) - StartRun(ctx context.Context, in *StartRunRequest, opts ...grpc.CallOption) (*StartRunResponse, error) - StartAllRuns(ctx context.Context, in *StartAllRunsRequest, opts ...grpc.CallOption) (*StartAllRunsResponse, error) - StopRun(ctx context.Context, in *StopRunRequest, opts ...grpc.CallOption) (*StopRunResponse, error) - StopAllRuns(ctx context.Context, in *StopAllRunsRequest, opts ...grpc.CallOption) (*StopAllRunsResponse, error) - ClearRun(ctx context.Context, in *ClearRunRequest, opts ...grpc.CallOption) (*ClearRunResponse, error) - ClearAllRuns(ctx context.Context, in *ClearAllRunsRequest, opts ...grpc.CallOption) (*ClearAllRunsResponse, error) + ListAllTasks(ctx context.Context, in *ListAllTasksRequest, opts ...grpc.CallOption) (*ListAllTasksResponse, error) + StartTask(ctx context.Context, in *StartTaskRequest, opts ...grpc.CallOption) (*StartTaskResponse, error) + StartAllTasks(ctx context.Context, in *StartAllTasksRequest, opts ...grpc.CallOption) (*StartAllTasksResponse, error) + StopTask(ctx context.Context, in *StopTaskRequest, opts ...grpc.CallOption) (*StopTaskResponse, error) + StopAllTasks(ctx context.Context, in *StopAllTasksRequest, opts ...grpc.CallOption) (*StopAllTasksResponse, error) + ClearTask(ctx context.Context, in *ClearTaskRequest, opts ...grpc.CallOption) (*ClearTaskResponse, error) + ClearAllTasks(ctx context.Context, in *ClearAllTasksRequest, opts ...grpc.CallOption) (*ClearAllTasksResponse, error) } type backtesterServiceClient struct { @@ -59,63 +59,63 @@ func (c *backtesterServiceClient) ExecuteStrategyFromConfig(ctx context.Context, return out, nil } -func (c *backtesterServiceClient) ListAllRuns(ctx context.Context, in *ListAllRunsRequest, opts ...grpc.CallOption) (*ListAllRunsResponse, error) { - out := new(ListAllRunsResponse) - err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ListAllRuns", in, out, opts...) +func (c *backtesterServiceClient) ListAllTasks(ctx context.Context, in *ListAllTasksRequest, opts ...grpc.CallOption) (*ListAllTasksResponse, error) { + out := new(ListAllTasksResponse) + err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ListAllTasks", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *backtesterServiceClient) StartRun(ctx context.Context, in *StartRunRequest, opts ...grpc.CallOption) (*StartRunResponse, error) { - out := new(StartRunResponse) - err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StartRun", in, out, opts...) +func (c *backtesterServiceClient) StartTask(ctx context.Context, in *StartTaskRequest, opts ...grpc.CallOption) (*StartTaskResponse, error) { + out := new(StartTaskResponse) + err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StartTask", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *backtesterServiceClient) StartAllRuns(ctx context.Context, in *StartAllRunsRequest, opts ...grpc.CallOption) (*StartAllRunsResponse, error) { - out := new(StartAllRunsResponse) - err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StartAllRuns", in, out, opts...) +func (c *backtesterServiceClient) StartAllTasks(ctx context.Context, in *StartAllTasksRequest, opts ...grpc.CallOption) (*StartAllTasksResponse, error) { + out := new(StartAllTasksResponse) + err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StartAllTasks", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *backtesterServiceClient) StopRun(ctx context.Context, in *StopRunRequest, opts ...grpc.CallOption) (*StopRunResponse, error) { - out := new(StopRunResponse) - err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StopRun", in, out, opts...) +func (c *backtesterServiceClient) StopTask(ctx context.Context, in *StopTaskRequest, opts ...grpc.CallOption) (*StopTaskResponse, error) { + out := new(StopTaskResponse) + err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StopTask", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *backtesterServiceClient) StopAllRuns(ctx context.Context, in *StopAllRunsRequest, opts ...grpc.CallOption) (*StopAllRunsResponse, error) { - out := new(StopAllRunsResponse) - err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StopAllRuns", in, out, opts...) +func (c *backtesterServiceClient) StopAllTasks(ctx context.Context, in *StopAllTasksRequest, opts ...grpc.CallOption) (*StopAllTasksResponse, error) { + out := new(StopAllTasksResponse) + err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/StopAllTasks", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *backtesterServiceClient) ClearRun(ctx context.Context, in *ClearRunRequest, opts ...grpc.CallOption) (*ClearRunResponse, error) { - out := new(ClearRunResponse) - err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ClearRun", in, out, opts...) +func (c *backtesterServiceClient) ClearTask(ctx context.Context, in *ClearTaskRequest, opts ...grpc.CallOption) (*ClearTaskResponse, error) { + out := new(ClearTaskResponse) + err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ClearTask", in, out, opts...) if err != nil { return nil, err } return out, nil } -func (c *backtesterServiceClient) ClearAllRuns(ctx context.Context, in *ClearAllRunsRequest, opts ...grpc.CallOption) (*ClearAllRunsResponse, error) { - out := new(ClearAllRunsResponse) - err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ClearAllRuns", in, out, opts...) +func (c *backtesterServiceClient) ClearAllTasks(ctx context.Context, in *ClearAllTasksRequest, opts ...grpc.CallOption) (*ClearAllTasksResponse, error) { + out := new(ClearAllTasksResponse) + err := c.cc.Invoke(ctx, "/btrpc.BacktesterService/ClearAllTasks", in, out, opts...) if err != nil { return nil, err } @@ -128,13 +128,13 @@ func (c *backtesterServiceClient) ClearAllRuns(ctx context.Context, in *ClearAll type BacktesterServiceServer interface { ExecuteStrategyFromFile(context.Context, *ExecuteStrategyFromFileRequest) (*ExecuteStrategyResponse, error) ExecuteStrategyFromConfig(context.Context, *ExecuteStrategyFromConfigRequest) (*ExecuteStrategyResponse, error) - ListAllRuns(context.Context, *ListAllRunsRequest) (*ListAllRunsResponse, error) - StartRun(context.Context, *StartRunRequest) (*StartRunResponse, error) - StartAllRuns(context.Context, *StartAllRunsRequest) (*StartAllRunsResponse, error) - StopRun(context.Context, *StopRunRequest) (*StopRunResponse, error) - StopAllRuns(context.Context, *StopAllRunsRequest) (*StopAllRunsResponse, error) - ClearRun(context.Context, *ClearRunRequest) (*ClearRunResponse, error) - ClearAllRuns(context.Context, *ClearAllRunsRequest) (*ClearAllRunsResponse, error) + ListAllTasks(context.Context, *ListAllTasksRequest) (*ListAllTasksResponse, error) + StartTask(context.Context, *StartTaskRequest) (*StartTaskResponse, error) + StartAllTasks(context.Context, *StartAllTasksRequest) (*StartAllTasksResponse, error) + StopTask(context.Context, *StopTaskRequest) (*StopTaskResponse, error) + StopAllTasks(context.Context, *StopAllTasksRequest) (*StopAllTasksResponse, error) + ClearTask(context.Context, *ClearTaskRequest) (*ClearTaskResponse, error) + ClearAllTasks(context.Context, *ClearAllTasksRequest) (*ClearAllTasksResponse, error) mustEmbedUnimplementedBacktesterServiceServer() } @@ -148,26 +148,26 @@ func (UnimplementedBacktesterServiceServer) ExecuteStrategyFromFile(context.Cont func (UnimplementedBacktesterServiceServer) ExecuteStrategyFromConfig(context.Context, *ExecuteStrategyFromConfigRequest) (*ExecuteStrategyResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ExecuteStrategyFromConfig not implemented") } -func (UnimplementedBacktesterServiceServer) ListAllRuns(context.Context, *ListAllRunsRequest) (*ListAllRunsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListAllRuns not implemented") +func (UnimplementedBacktesterServiceServer) ListAllTasks(context.Context, *ListAllTasksRequest) (*ListAllTasksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListAllTasks not implemented") } -func (UnimplementedBacktesterServiceServer) StartRun(context.Context, *StartRunRequest) (*StartRunResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method StartRun not implemented") +func (UnimplementedBacktesterServiceServer) StartTask(context.Context, *StartTaskRequest) (*StartTaskResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StartTask not implemented") } -func (UnimplementedBacktesterServiceServer) StartAllRuns(context.Context, *StartAllRunsRequest) (*StartAllRunsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method StartAllRuns not implemented") +func (UnimplementedBacktesterServiceServer) StartAllTasks(context.Context, *StartAllTasksRequest) (*StartAllTasksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StartAllTasks not implemented") } -func (UnimplementedBacktesterServiceServer) StopRun(context.Context, *StopRunRequest) (*StopRunResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method StopRun not implemented") +func (UnimplementedBacktesterServiceServer) StopTask(context.Context, *StopTaskRequest) (*StopTaskResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopTask not implemented") } -func (UnimplementedBacktesterServiceServer) StopAllRuns(context.Context, *StopAllRunsRequest) (*StopAllRunsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method StopAllRuns not implemented") +func (UnimplementedBacktesterServiceServer) StopAllTasks(context.Context, *StopAllTasksRequest) (*StopAllTasksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method StopAllTasks not implemented") } -func (UnimplementedBacktesterServiceServer) ClearRun(context.Context, *ClearRunRequest) (*ClearRunResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ClearRun not implemented") +func (UnimplementedBacktesterServiceServer) ClearTask(context.Context, *ClearTaskRequest) (*ClearTaskResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClearTask not implemented") } -func (UnimplementedBacktesterServiceServer) ClearAllRuns(context.Context, *ClearAllRunsRequest) (*ClearAllRunsResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ClearAllRuns not implemented") +func (UnimplementedBacktesterServiceServer) ClearAllTasks(context.Context, *ClearAllTasksRequest) (*ClearAllTasksResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ClearAllTasks not implemented") } func (UnimplementedBacktesterServiceServer) mustEmbedUnimplementedBacktesterServiceServer() {} @@ -218,128 +218,128 @@ func _BacktesterService_ExecuteStrategyFromConfig_Handler(srv interface{}, ctx c return interceptor(ctx, in, info, handler) } -func _BacktesterService_ListAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListAllRunsRequest) +func _BacktesterService_ListAllTasks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAllTasksRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BacktesterServiceServer).ListAllRuns(ctx, in) + return srv.(BacktesterServiceServer).ListAllTasks(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/btrpc.BacktesterService/ListAllRuns", + FullMethod: "/btrpc.BacktesterService/ListAllTasks", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BacktesterServiceServer).ListAllRuns(ctx, req.(*ListAllRunsRequest)) + return srv.(BacktesterServiceServer).ListAllTasks(ctx, req.(*ListAllTasksRequest)) } return interceptor(ctx, in, info, handler) } -func _BacktesterService_StartRun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StartRunRequest) +func _BacktesterService_StartTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StartTaskRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BacktesterServiceServer).StartRun(ctx, in) + return srv.(BacktesterServiceServer).StartTask(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/btrpc.BacktesterService/StartRun", + FullMethod: "/btrpc.BacktesterService/StartTask", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BacktesterServiceServer).StartRun(ctx, req.(*StartRunRequest)) + return srv.(BacktesterServiceServer).StartTask(ctx, req.(*StartTaskRequest)) } return interceptor(ctx, in, info, handler) } -func _BacktesterService_StartAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StartAllRunsRequest) +func _BacktesterService_StartAllTasks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StartAllTasksRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BacktesterServiceServer).StartAllRuns(ctx, in) + return srv.(BacktesterServiceServer).StartAllTasks(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/btrpc.BacktesterService/StartAllRuns", + FullMethod: "/btrpc.BacktesterService/StartAllTasks", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BacktesterServiceServer).StartAllRuns(ctx, req.(*StartAllRunsRequest)) + return srv.(BacktesterServiceServer).StartAllTasks(ctx, req.(*StartAllTasksRequest)) } return interceptor(ctx, in, info, handler) } -func _BacktesterService_StopRun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StopRunRequest) +func _BacktesterService_StopTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StopTaskRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BacktesterServiceServer).StopRun(ctx, in) + return srv.(BacktesterServiceServer).StopTask(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/btrpc.BacktesterService/StopRun", + FullMethod: "/btrpc.BacktesterService/StopTask", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BacktesterServiceServer).StopRun(ctx, req.(*StopRunRequest)) + return srv.(BacktesterServiceServer).StopTask(ctx, req.(*StopTaskRequest)) } return interceptor(ctx, in, info, handler) } -func _BacktesterService_StopAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StopAllRunsRequest) +func _BacktesterService_StopAllTasks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StopAllTasksRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BacktesterServiceServer).StopAllRuns(ctx, in) + return srv.(BacktesterServiceServer).StopAllTasks(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/btrpc.BacktesterService/StopAllRuns", + FullMethod: "/btrpc.BacktesterService/StopAllTasks", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BacktesterServiceServer).StopAllRuns(ctx, req.(*StopAllRunsRequest)) + return srv.(BacktesterServiceServer).StopAllTasks(ctx, req.(*StopAllTasksRequest)) } return interceptor(ctx, in, info, handler) } -func _BacktesterService_ClearRun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ClearRunRequest) +func _BacktesterService_ClearTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClearTaskRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BacktesterServiceServer).ClearRun(ctx, in) + return srv.(BacktesterServiceServer).ClearTask(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/btrpc.BacktesterService/ClearRun", + FullMethod: "/btrpc.BacktesterService/ClearTask", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BacktesterServiceServer).ClearRun(ctx, req.(*ClearRunRequest)) + return srv.(BacktesterServiceServer).ClearTask(ctx, req.(*ClearTaskRequest)) } return interceptor(ctx, in, info, handler) } -func _BacktesterService_ClearAllRuns_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ClearAllRunsRequest) +func _BacktesterService_ClearAllTasks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ClearAllTasksRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BacktesterServiceServer).ClearAllRuns(ctx, in) + return srv.(BacktesterServiceServer).ClearAllTasks(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/btrpc.BacktesterService/ClearAllRuns", + FullMethod: "/btrpc.BacktesterService/ClearAllTasks", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BacktesterServiceServer).ClearAllRuns(ctx, req.(*ClearAllRunsRequest)) + return srv.(BacktesterServiceServer).ClearAllTasks(ctx, req.(*ClearAllTasksRequest)) } return interceptor(ctx, in, info, handler) } @@ -360,32 +360,32 @@ var BacktesterService_ServiceDesc = grpc.ServiceDesc{ Handler: _BacktesterService_ExecuteStrategyFromConfig_Handler, }, { - MethodName: "ListAllRuns", - Handler: _BacktesterService_ListAllRuns_Handler, + MethodName: "ListAllTasks", + Handler: _BacktesterService_ListAllTasks_Handler, }, { - MethodName: "StartRun", - Handler: _BacktesterService_StartRun_Handler, + MethodName: "StartTask", + Handler: _BacktesterService_StartTask_Handler, }, { - MethodName: "StartAllRuns", - Handler: _BacktesterService_StartAllRuns_Handler, + MethodName: "StartAllTasks", + Handler: _BacktesterService_StartAllTasks_Handler, }, { - MethodName: "StopRun", - Handler: _BacktesterService_StopRun_Handler, + MethodName: "StopTask", + Handler: _BacktesterService_StopTask_Handler, }, { - MethodName: "StopAllRuns", - Handler: _BacktesterService_StopAllRuns_Handler, + MethodName: "StopAllTasks", + Handler: _BacktesterService_StopAllTasks_Handler, }, { - MethodName: "ClearRun", - Handler: _BacktesterService_ClearRun_Handler, + MethodName: "ClearTask", + Handler: _BacktesterService_ClearTask_Handler, }, { - MethodName: "ClearAllRuns", - Handler: _BacktesterService_ClearAllRuns_Handler, + MethodName: "ClearAllTasks", + Handler: _BacktesterService_ClearAllTasks_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/backtester/common/common.go b/backtester/common/common.go index 3d50c922..84270ef2 100644 --- a/backtester/common/common.go +++ b/backtester/common/common.go @@ -86,6 +86,10 @@ func RegisterBacktesterSubLoggers() error { if err != nil { return err } + LiveStrategy, err = log.NewSubLogger("LiveStrategy") + if err != nil { + return err + } Setup, err = log.NewSubLogger("Setup") if err != nil { return err @@ -110,10 +114,6 @@ func RegisterBacktesterSubLoggers() error { if err != nil { return err } - Backtester, err = log.NewSubLogger("Sizing") - if err != nil { - return err - } Holdings, err = log.NewSubLogger("Holdings") if err != nil { return err @@ -122,6 +122,10 @@ func RegisterBacktesterSubLoggers() error { if err != nil { return err } + FundManager, err = log.NewSubLogger("FundManager") + if err != nil { + return err + } // Set to existing registered sub-loggers Config = log.ConfigMgr diff --git a/backtester/common/common_test.go b/backtester/common/common_test.go index 9390a971..ecf2b369 100644 --- a/backtester/common/common_test.go +++ b/backtester/common/common_test.go @@ -6,6 +6,7 @@ import ( "testing" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" + "github.com/thrasher-corp/gocryptotrader/log" ) func TestCanTransact(t *testing.T) { @@ -244,3 +245,16 @@ func TestGenerateFileName(t *testing.T) { t.Errorf("received '%v' expected '%v'", name, "hell0_.moto") } } + +func TestRegisterBacktesterSubLoggers(t *testing.T) { + t.Parallel() + err := RegisterBacktesterSubLoggers() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + err = RegisterBacktesterSubLoggers() + if !errors.Is(err, log.ErrSubLoggerAlreadyRegistered) { + t.Errorf("received '%v' expected '%v'", err, log.ErrSubLoggerAlreadyRegistered) + } +} diff --git a/backtester/common/common_types.go b/backtester/common/common_types.go index 6719911e..e7c25ca6 100644 --- a/backtester/common/common_types.go +++ b/backtester/common/common_types.go @@ -26,9 +26,6 @@ const ( ) var ( - // ErrNilArguments is a common error response to highlight that nils were passed in - // when they should not have been - ErrNilArguments = errors.New("received nil argument(s)") // ErrNilEvent is a common error for whenever a nil event occurs when it shouldn't have ErrNilEvent = errors.New("nil event received") // ErrInvalidDataType occurs when an invalid data type is defined in the config @@ -39,8 +36,8 @@ var ( errCannotGenerateFileName = errors.New("cannot generate filename") ) -// EventHandler interface implements required GetTime() & Pair() return -type EventHandler interface { +// Event interface implements required GetTime() & Pair() return +type Event interface { GetBase() *event.Base GetOffset() int64 SetOffset(int64) @@ -61,6 +58,7 @@ type EventHandler interface { // custom subloggers for backtester use var ( Backtester *log.SubLogger + LiveStrategy *log.SubLogger Setup *log.SubLogger Strategy *log.SubLogger Config *log.SubLogger @@ -73,18 +71,9 @@ var ( FundingStatistics *log.SubLogger Holdings *log.SubLogger Data *log.SubLogger + FundManager *log.SubLogger ) -// DataEventHandler interface used for loading and interacting with Data -type DataEventHandler interface { - EventHandler - GetUnderlyingPair() currency.Pair - GetClosePrice() decimal.Decimal - GetHighPrice() decimal.Decimal - GetLowPrice() decimal.Decimal - GetOpenPrice() decimal.Decimal -} - // Directioner dictates the side of an order type Directioner interface { SetDirection(side order.Side) diff --git a/backtester/config/README.md b/backtester/config/README.md index a22821ef..c03e9e30 100644 --- a/backtester/config/README.md +++ b/backtester/config/README.md @@ -19,42 +19,43 @@ You can track ideas, planned features and what's in progress on this Trello boar Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) ## Config package overview +This readme contains details for both the GoCryptoTrader Backtester config structure along with the strategy config structure -## Backtester Config overview +## GoCryptoTrader Backtester Config overview Below are the details for the GoCryptoTrader Backtester _application_ config. Strategy config overview is below this section | Key | Description | Example | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------| -| PrintLogo | Whether to print the GoCryptoTrader Backtester logo on startup. Recommended because it looks good | `true` | -| Verbose | Whether to receive verbose output. If running a GRPC server, it outputs to the server, not to the client | `false` | -| LogSubheaders | Whether log output contains a descriptor of what area the log is coming from, for example `STRATEGY`. Helpful for debugging | `true` | -| SingleRun | Whether or not to run the GoCryptoTrader Backtester to read the `SingleRunStrategyConfig` strategy and exit afterwards. If false, will run a GRPC server | `false` | -| SingleRunStrategyConfig | The path to the strategy to run when `SingleRun` is `true` | `path\to\strategy\example.strat` | -| Report | Contains details on the output report after a successful backtesting run | See Report table below | -| GRPC | Contains GRPC server details | See GRPC table below | -| UseCMDColours | If enabled, will output pretty colours of your choosing when running the application | `true` | -| Colours | Contains details on what the colour definitions are | See Colours table below | +| print-logo | Whether to print the GoCryptoTrader Backtester logo on startup. Recommended because it looks good | `true` | +| verbose | Whether to receive verbose output. If running a GRPC server, it outputs to the server, not to the client | `false` | +| log-subheaders | Whether log output contains a descriptor of what area the log is coming from, for example `STRATEGY`. Helpful for debugging | `true` | +| stop-all-tasks-on-close | When closing the application, the Backtester will attempt to stop all active tasks | `true` | +| plugin-path | When using custom strategy plugins, you can enter the path here to automatically load the plugin | `true` | +| report | Contains details on the output report after a successful backtesting run | See Report table below | +| grpc | Contains GRPC server details | See GRPC table below | +| use-cmd-colours | If enabled, will output pretty colours of your choosing when running the application | `true` | +| cmd-colours | Contains details on what the colour definitions are | See Colours table below | ### Backtester Config Report overview | Key | Description | Example | |----------------|----------------------------------------------------------------------|---------------------------------| -| GenerateReport | Whether or not to output a report after a successful backtesting run | `true` | -| TemplatePath | The path for the template to use when generating a report | `/backtester/report/tpl.gohtml` | -| OutputPath | The path where report output is saved | `/backtester/results` | -| DarkMode | Whether or not the report defaults to using dark mode | `true` | +| output-report | Whether or not to output a report after a successful backtesting run | `true` | +| template-path | The path for the template to use when generating a report | `/backtester/report/tpl.gohtml` | +| output-path | The path where report output is saved | `/backtester/results` | +| dark-mode | Whether or not the report defaults to using dark mode | `true` | ### Backtester Config GRPC overview | Key | Description | Example | |------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------| -| Username | Your username to negotiate a successful connection with the server | `rpcuser` | -| Password | Your password to negotiate a successful connection with the server | `helloImTheDefaultPassword` | -| Enabled | Whether the server is enabled. Setting this to `false` and `SingleRun` to `false` would be inadvisable | `true` | -| ListenAddress | The listen address for the GRPC server | `localhost:42069` | -| GRPCProxyEnabled | If enabled, creates a proxy server to interact with the GRPC server via HTTP commands | `true` | -| GRPCProxyListenAddress | The address for the proxy to listen on | `localhost:9053` | -| TLSDir | The directory for holding your TLS certifications to make connections to the server. Will be generated by default on startup if not present | `/backtester/config/location/` | +| username | Your username to negotiate a successful connection with the server | `rpcuser` | +| password | Your password to negotiate a successful connection with the server | `helloImTheDefaultPassword` | +| enabled | Whether the server is enabled. Setting this to `false` and `SingleRun` to `false` would be inadvisable | `true` | +| listenAddress | The listen address for the GRPC server | `localhost:9054` | +| grpcProxyEnabled | If enabled, creates a proxy server to interact with the GRPC server via HTTP commands | `true` | +| grpcProxyListenAddress | The address for the proxy to listen on | `localhost:9053` | +| tls-dir | The directory for holding your TLS certifications to make connections to the server. Will be generated by default on startup if not present | `/backtester/config/location/` | ### Backtester Config Colours overview @@ -97,123 +98,111 @@ See below for a set of tables and fields, expected values and what they can do #### Config -| Key | Description | -|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs | -| Goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal | -| CurrencySettings | Currency settings is an array of settings for each individual currency you wish to run the strategy against | -| StrategySettings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions | -| FundingSettings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level | -| PortfolioSettings | Contains a list of global rules for the portfolio manager. CurrencySettings contain their own rules on things like how big a position is allowable, the portfolio manager rules are the same, but override any individual currency's settings | -| StatisticSettings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio | - +| Key | Description | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs | +| goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal | +| strategy-settings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions | +| funding-settings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level | +| currency-settings | Currency settings is an array of settings for each individual currency you wish to run the strategy against | +| data-settings | Holds data retrieval settings. Determines how the GoCryptoTraderBacktester will fetch data and in what format | +| portfolio-settings | Contains a list of global rules for the portfolio manager. CurrencySettings contain their own rules on things like how big a position is allowable, the portfolio manager rules are the same, but override any individual currency's settings | +| statistic-settings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio | #### Strategy Settings -| Key | Description | Example | -|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| -| Name | The strategy to use | `rsi` | -| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | -| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | -| DisableUSDTracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` | - +| Key | Description | Example | +|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| name | The strategy to use | `rsi` | +| use-simultaneous-signal-processing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | +| disable-usd-tracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` | +| custom-settings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | #### Funding Config Settings -| Key | Description | Example | -|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| -| UseExchangeLevelFunding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` | -| ExchangeLevelFunding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` | - +| Key | Description | Example | +|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| use-exchange-level-funding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` | +| exchange-level-funding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` | ##### Funding Item Config Settings -| Key | Description | Example | -|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| -| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | -| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | -| Currency | The currency to set funds | `BTC` | -| InitialFunds | The initial funding for the currency | `1337` | -| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | - +| Key | Description | Example | +|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| +| exchange-name | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | +| asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | +| currency | The currency to set funds | `BTC` | +| initial-funds | The initial funding for the currency | `1337` | +| transfer-fee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | #### Currency Settings -| Key | Description | Example | -|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------| -| ExchangeName | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | -| Asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | -| Base | The base of a currency | `BTC` | -| Quote | The quote of a currency | `USDT` | -| InitialFunds | A legacy field, will be temporarily migrated to `InitialQuoteFunds` if present in your strat config | `` | -| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | - | -| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - | -| MinimumSlippagePercent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` | -| MaximumSlippagePercent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` | -| MakerFee | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` | -| TakerFee | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` | -| MaximumHoldingsRatio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` | -| CanUseExchangeLimits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` | -| SkipCandleVolumeFitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` | -| SpotSettings | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below | -| FuturesSettings | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below | +| Key | Description | Example | +|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------| +| exchange-name | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | +| asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | +| base | The base of a currency | `BTC` | +| quote | The quote of a currency | `USDT` | +| spot-details | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below | +| future-detailss | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below | +| buy-side | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount |-- | +| sell-side | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount |-- | +| min-slippage-percent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` | +| max-slippage-percent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` | +| maker-fee-override | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` | +| taker-fee-override | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` | +| maximum-holdings-ratio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` | +| skip-candle-volume-fitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` | +| use-exchange-order-limits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` | +| use-exchange-pnl-calculation | Instead of simulating the exchange's own way of calculating PNL, use a default method which calculates the value of an asset | `false` | ##### SpotSettings -| Key | Description | Example | -|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| -| InitialBaseFunds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` | -| InitialQuoteFunds | The funds that the GoCryptoTraderBacktester has for the quote currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `10000` | +| Key | Description | Example | +|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| initial-base-funds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` | +| initial-quote-funds | The funds that the GoCryptoTraderBacktester has for the quote currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `10000` | ##### FuturesSettings | Key | Description | Example | |----------|------------------------------------------------------------------------------------------|---------| -| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` | +| leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` | -#### PortfolioSettings - -| Key | Description | -|----------|------------------------------------------------------------------------------------------------------------------------| -| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | -| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | -| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - -#### StatisticsSettings - -| Key | Description | Example | -|--------------|-------------------------------------------------------------------------|---------| -| RiskFreeRate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` | +### DataSettings +| Key | Description | Example | +|---------------------------|--------------------------------------------------------------------------------------------------------|---------------| +| interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | +| data-type | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` | +| verbose-exchange-requests | When retrieving candle data from an exchange, print verbose request/response details | `false` | +| api-data | Holds API data settings. See table `APIData` | | +| database-data | Holds database data settings. See table `DatabaseData` | | +| live-data | Holds API data settings. See table `LiveData` | | +| csv-data | Holds CSV data settings. See table `CSVData` | | #### APIData -| Key | Description | Example | -|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | -| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | -| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | +| Key | Description | Example | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| +| start-date | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | +| end-date | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | +| inclusive-end-date | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | #### CSVData -| Key | Description | Example | -|----------|--------------------------------------------------------------------------------------------------------|--------------------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| FullPath | The file to load | `/data/exchangelist.csv` | +| Key | Description | Example | +|-----------|------------------|--------------------------| +| full-path | The file to load | `/data/exchangelist.csv` | #### DatabaseData -| Key | Description | Example | -|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | -| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | -| Config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` | -| Path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` | -| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | +| Key | Description | Example | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| +| start-date | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | +| end-date | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | +| config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` | +| path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` | +| inclusive-end-date | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | ##### database @@ -237,32 +226,65 @@ See below for a set of tables and fields, expected values and what they can do #### LiveData -| Key | Description | Example | -|-----------------------|--------------------------------------------------------------------------------------------------------|---------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| APIKeyOverride | Will set the GoCryptoTrader exchange to use the following API Key | `1234` | -| APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` | -| APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` | -| API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` | -| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` | -| RealOrders | Whether to place real orders. You really should never consider using this. Ever ever | `true` | +| Key | Description | Example | +|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|---------------| +| new-event-timeout | The time allowed to wait for new data before exiting the strategy. Ensures new data is always coming in | `60000000000` | +| data-check-timer | The interval in which to check exchange API's for new data | `1000000000` | +| real-orders | Whether to place real orders with real money. Its likely you should never want to set this to true | `false` | +| close-positions-on-stop | As live trading doesn't stop until you tell it to, you can trigger a close of your position(s) when you stop the strategy | `true` | +| data-request-retry-tolerance | Rather than immediately closing a strategy on failure to retreive candle data, having a retry tolerance allows multiple attempts to return data | `3` | +| data-request-retry-wait-time | How long to wait in between request retries | `500000000` | +| exchange-credentials | A list of exchange credentials. See table named `ExchangeCredentials` | | + +##### ExchangeCredentials Settings + +| Key | Description | Example | +|-------------|-----------------------------------------------------------|-----------| +| exchange | The exchange to apply credentials to | `binance` | +| credentials | The API credentials to use. See table named `Credentials` | | + +##### Credentials Settings + +| Key | Description | Example | +|-----------------|---------------------------------------------------------------------------------------------|--------------| +| Key | Will set the GoCryptoTrader exchange to use the following API Key | `1234` | +| Secret | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` | +| ClientID | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` | +| PEMKey | Private key for certain API requests. If you don't know it, you probably don't need it | `hello-moto` | +| SubAccount | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` | +| OneTimePassword | Will set the GoCryptoTrader exchange to use the following 2FA seed | `subzero` | + +#### PortfolioSettings + +| Key | Description | +|-----------|------------------------------------------------------------------------------------------------------------------------| +| leverage | This struct defines the leverage rules that this specific currency setting must abide by | +| buy-side | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | +| sell-side | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | ##### Leverage Settings -| Key | Description | Example | -|--------------------------------|------------------------------------------------------------------------------------------|---------| -| CanUseLeverage | Allows the use of leverage | `false` | -| MaximumOrdersWithLeverageRatio | If the ratio of leveraged orders for a currency exceeds this, the order cannot be placed | `0.5` | -| MaximumLeverageRate | Orders cannot be placed with leverage over this amount | `100` | +| Key | Description | Example | +|------------------------------------|-------------------------------|---------| +| can-use-leverage | Allows the use of leverage | `false` | +| maximum-orders-with-leverage-ratio | currently unused | `0.5` | +| maximum-leverage-rate | currently unused | `100` | +| maximum-collateral-leverage-rate | currently unused | `100` | ##### Buy/Sell Settings -| Key | Description | Example | -|--------------|------------------------------------------------------------------------------------------------------------------|---------| -| MinimumSize | If the order's quantity is below this, the order cannot be placed | `0.1` | -| MaximumSize | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` | -| MaximumTotal | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` | +| Key | Description | Example | +|---------------|------------------------------------------------------------------------------------------------------------------|---------| +| minimum-size | If the order's quantity is below this, the order cannot be placed | `0.1` | +| maximum-size | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` | +| maximum-total | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` | + + +#### StatisticsSettings + +| Key | Description | Example | +|----------------|-------------------------------------------------------------------------|---------| +| risk-free-rate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` | ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/backtester/config/backtesterconfig.go b/backtester/config/backtesterconfig.go index aee54302..9b13bd1b 100644 --- a/backtester/config/backtesterconfig.go +++ b/backtester/config/backtesterconfig.go @@ -67,5 +67,6 @@ func GenerateDefaultConfig() (*BacktesterConfig, error) { Warn: common.CMDColours.Warn, Error: common.CMDColours.Error, }, + StopAllTasksOnClose: true, }, nil } diff --git a/backtester/config/backtesterconfig_types.go b/backtester/config/backtesterconfig_types.go index aa4f6c9b..4dfec813 100644 --- a/backtester/config/backtesterconfig_types.go +++ b/backtester/config/backtesterconfig_types.go @@ -18,14 +18,15 @@ var ( // BacktesterConfig contains the configuration for the backtester type BacktesterConfig struct { - PluginPath string `json:"plugin-path"` - PrintLogo bool `json:"print-logo"` - Verbose bool `json:"verbose"` - LogSubheaders bool `json:"log-subheaders"` - Report Report `json:"report"` - GRPC GRPC `json:"grpc"` - UseCMDColours bool `json:"use-cmd-colours"` - Colours common.Colours `json:"cmd-colours"` + PrintLogo bool `json:"print-logo"` + LogSubheaders bool `json:"log-subheaders"` + Verbose bool `json:"verbose"` + StopAllTasksOnClose bool `json:"stop-all-tasks-on-close"` + PluginPath string `json:"plugin-path"` + Report Report `json:"report"` + GRPC GRPC `json:"grpc"` + UseCMDColours bool `json:"use-cmd-colours"` + Colours common.Colours `json:"cmd-colours"` } // Report contains the report settings diff --git a/backtester/config/btcktesterconfig_test.go b/backtester/config/batcktesterconfig_test.go similarity index 100% rename from backtester/config/btcktesterconfig_test.go rename to backtester/config/batcktesterconfig_test.go diff --git a/backtester/config/strategyconfig.go b/backtester/config/strategyconfig.go index 5d317832..d75c8cda 100644 --- a/backtester/config/strategyconfig.go +++ b/backtester/config/strategyconfig.go @@ -35,7 +35,7 @@ func ReadStrategyConfigFromFile(path string) (*Config, error) { // Validate checks all config settings func (c *Config) Validate() error { if c == nil { - return fmt.Errorf("%w nil config", common.ErrNilArguments) + return fmt.Errorf("%w config", gctcommon.ErrNilPointer) } err := c.validateDate() if err != nil { @@ -122,7 +122,7 @@ func (c *Config) validateStrategySettings() error { } } } - strats := strategies.GetStrategies() + strats := strategies.GetSupportedStrategies() for i := range strats { if strings.EqualFold(strats[i].Name(), c.StrategySettings.Name) { return nil @@ -158,12 +158,11 @@ func (c *Config) validateCurrencySettings() error { c.CurrencySettings[i].Asset == asset.PerpetualContract { return errPerpetualsUnsupported } - if c.CurrencySettings[i].Asset == asset.Futures && - (c.CurrencySettings[i].Quote.String() == "PERP" || c.CurrencySettings[i].Base.String() == "PI") { - return errPerpetualsUnsupported - } if c.CurrencySettings[i].Asset.IsFutures() { hasFutures = true + if c.CurrencySettings[i].Quote.String() == "PERP" || c.CurrencySettings[i].Base.String() == "PI" { + return errPerpetualsUnsupported + } } if c.CurrencySettings[i].SpotDetails != nil { if c.FundingSettings.UseExchangeLevelFunding { @@ -239,12 +238,16 @@ func (c *Config) PrintSetting() { if c.FundingSettings.UseExchangeLevelFunding && c.StrategySettings.SimultaneousSignalProcessing { log.Info(common.Config, common.CMDColours.H2+"------------------Funding Settings---------------------------"+common.CMDColours.Default) log.Infof(common.Config, "Use Exchange Level Funding: %v", c.FundingSettings.UseExchangeLevelFunding) - for i := range c.FundingSettings.ExchangeLevelFunding { - log.Infof(common.Config, "Initial funds for %v %v %v: %v", - c.FundingSettings.ExchangeLevelFunding[i].ExchangeName, - c.FundingSettings.ExchangeLevelFunding[i].Asset, - c.FundingSettings.ExchangeLevelFunding[i].Currency, - c.FundingSettings.ExchangeLevelFunding[i].InitialFunds.Round(8)) + if c.DataSettings.LiveData != nil && c.DataSettings.LiveData.RealOrders { + log.Infof(common.Config, "Funding levels will be set by the exchange") + } else { + for i := range c.FundingSettings.ExchangeLevelFunding { + log.Infof(common.Config, "Initial funds for %v %v %v: %v", + c.FundingSettings.ExchangeLevelFunding[i].ExchangeName, + c.FundingSettings.ExchangeLevelFunding[i].Asset, + c.FundingSettings.ExchangeLevelFunding[i].Currency, + c.FundingSettings.ExchangeLevelFunding[i].InitialFunds.Round(8)) + } } } @@ -255,7 +258,10 @@ func (c *Config) PrintSetting() { c.CurrencySettings[i].Quote) log.Infof(common.Config, currStr[:61]) log.Infof(common.Config, "Exchange: %v", c.CurrencySettings[i].ExchangeName) - if !c.FundingSettings.UseExchangeLevelFunding && c.CurrencySettings[i].SpotDetails != nil { + switch { + case c.DataSettings.LiveData != nil && c.DataSettings.LiveData.RealOrders: + log.Infof(common.Config, "Funding levels will be set by the exchange") + case !c.FundingSettings.UseExchangeLevelFunding && c.CurrencySettings[i].SpotDetails != nil: if c.CurrencySettings[i].SpotDetails.InitialBaseFunds != nil { log.Infof(common.Config, "Initial base funds: %v %v", c.CurrencySettings[i].SpotDetails.InitialBaseFunds.Round(8), @@ -299,8 +305,12 @@ func (c *Config) PrintSetting() { log.Info(common.Config, common.CMDColours.H2+"------------------Live Settings------------------------------"+common.CMDColours.Default) log.Infof(common.Config, "Data type: %v", c.DataSettings.DataType) log.Infof(common.Config, "Interval: %v", c.DataSettings.Interval) - log.Infof(common.Config, "REAL ORDERS: %v", c.DataSettings.LiveData.RealOrders) - log.Infof(common.Config, "Overriding GCT API settings: %v", c.DataSettings.LiveData.APIClientIDOverride != "") + log.Infof(common.Config, "Using real orders: %v", c.DataSettings.LiveData.RealOrders) + log.Infof(common.Config, "Data check timer: %v", c.DataSettings.LiveData.DataCheckTimer) + log.Infof(common.Config, "New event timeout: %v", c.DataSettings.LiveData.NewEventTimeout) + for i := range c.DataSettings.LiveData.ExchangeCredentials { + log.Infof(common.Config, "%s credentials: %s", c.DataSettings.LiveData.ExchangeCredentials[i].Exchange, c.DataSettings.LiveData.ExchangeCredentials[i].Keys.String()) + } } if c.DataSettings.APIData != nil { log.Info(common.Config, common.CMDColours.H2+"------------------API Settings-------------------------------"+common.CMDColours.Default) diff --git a/backtester/config/strategyconfig_test.go b/backtester/config/strategyconfig_test.go index 1e6feaf8..20272afe 100644 --- a/backtester/config/strategyconfig_test.go +++ b/backtester/config/strategyconfig_test.go @@ -17,12 +17,13 @@ import ( "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 ( - testExchange = "ftx" + mainExchange = "binance" dca = "dollarcostaverage" // change this if you modify a config and want it to save to the example folder saveConfig = false @@ -39,9 +40,17 @@ var ( 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) { @@ -58,8 +67,8 @@ func TestValidateDate(t *testing.T) { t.Parallel() c := Config{} err := c.validateDate() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } c.DataSettings = DataSettings{ DatabaseData: &DatabaseData{}, @@ -76,8 +85,8 @@ func TestValidateDate(t *testing.T) { } c.DataSettings.DatabaseData.EndDate = c.DataSettings.DatabaseData.StartDate.Add(time.Minute) err = c.validateDate() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } c.DataSettings.APIData = &APIData{} err = c.validateDate() @@ -92,8 +101,8 @@ func TestValidateDate(t *testing.T) { } c.DataSettings.APIData.EndDate = c.DataSettings.APIData.StartDate.Add(time.Minute) err = c.validateDate() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -127,9 +136,88 @@ func TestValidateCurrencySettings(t *testing.T) { } c.CurrencySettings[0].ExchangeName = "lol" err = c.validateCurrencySettings() - if err != nil { - t.Error(err) + 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{ @@ -282,10 +370,10 @@ func TestPrintSettings(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds1000000, InitialBaseFunds: initialFunds1000000, @@ -308,27 +396,15 @@ func TestPrintSettings(t *testing.T) { CSVData: &CSVData{ FullPath: "fake", }, - LiveData: &LiveData{ - APIKeyOverride: "", - APISecretOverride: "", - APIClientIDOverride: "", - API2FAOverride: "", - APISubAccountOverride: "", - RealOrders: false, - }, + LiveData: &LiveData{}, DatabaseData: &DatabaseData{ - StartDate: startDate, - EndDate: endDate, - Config: database.Config{}, - InclusiveEndDate: false, + StartDate: startDate, + EndDate: endDate, }, }, PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -348,10 +424,10 @@ func TestValidate(t *testing.T) { StrategySettings: StrategySettings{Name: dca}, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialBaseFunds: initialFunds10, InitialQuoteFunds: initialFunds100000, @@ -371,8 +447,8 @@ func TestValidate(t *testing.T) { c = nil err = c.Validate() - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received %v expected %v", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received %v expected %v", err, gctcommon.ErrNilPointer) } } @@ -383,16 +459,16 @@ func TestReadStrategyConfigFromFile(t *testing.T) { t.Fatalf("Problem creating temp file at %v: %s\n", passFile, err) } _, err = passFile.WriteString("{}") - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = passFile.Close() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } _, err = ReadStrategyConfigFromFile(passFile.Name()) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } _, err = ReadStrategyConfigFromFile("test") @@ -413,10 +489,10 @@ func TestGenerateConfigForDCAAPICandles(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -438,9 +514,6 @@ func TestGenerateConfigForDCAAPICandles(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -455,7 +528,7 @@ func TestGenerateConfigForDCAAPICandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -474,10 +547,10 @@ func TestGenerateConfigForPluginStrategy(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds1000000, }, @@ -516,7 +589,7 @@ func TestGenerateConfigForPluginStrategy(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "custom-plugin-strategy.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "custom-plugin-strategy.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -539,29 +612,29 @@ func TestGenerateConfigForDCAAPICandlesExchangeLevelFunding(t *testing.T) { UseExchangeLevelFunding: true, ExchangeLevelFunding: []ExchangeLevelFunding{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Currency: currency.USDT, + Currency: mainCurrencyPair.Quote, InitialFunds: decimal.NewFromInt(100000), }, }, }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.ETH, - Quote: currency.USDT, + Quote: mainCurrencyPair.Quote, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, @@ -580,9 +653,6 @@ func TestGenerateConfigForDCAAPICandlesExchangeLevelFunding(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -597,7 +667,7 @@ func TestGenerateConfigForDCAAPICandlesExchangeLevelFunding(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles-exchange-level-funding.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles-exchange-level-funding.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -616,10 +686,10 @@ func TestGenerateConfigForDCAAPITrades(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: "ftx", + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -650,9 +720,6 @@ func TestGenerateConfigForDCAAPITrades(t *testing.T) { MaximumSize: decimal.NewFromInt(1), MaximumTotal: decimal.NewFromInt(10000), }, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -667,7 +734,7 @@ func TestGenerateConfigForDCAAPITrades(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-api-trades.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-trades.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -686,10 +753,10 @@ func TestGenerateConfigForDCAAPICandlesMultipleCurrencies(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -699,10 +766,10 @@ func TestGenerateConfigForDCAAPICandlesMultipleCurrencies(t *testing.T) { TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.ETH, - Quote: currency.USDT, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -724,9 +791,6 @@ func TestGenerateConfigForDCAAPICandlesMultipleCurrencies(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -741,7 +805,7 @@ func TestGenerateConfigForDCAAPICandlesMultipleCurrencies(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles-multiple-currencies.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles-multiple-currencies.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -761,10 +825,10 @@ func TestGenerateConfigForDCAAPICandlesSimultaneousProcessing(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds1000000, }, @@ -774,10 +838,10 @@ func TestGenerateConfigForDCAAPICandlesSimultaneousProcessing(t *testing.T) { TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.ETH, - Quote: currency.USDT, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -799,9 +863,6 @@ func TestGenerateConfigForDCAAPICandlesSimultaneousProcessing(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -816,7 +877,7 @@ func TestGenerateConfigForDCAAPICandlesSimultaneousProcessing(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles-simultaneous-processing.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-api-candles-simultaneous-processing.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -836,15 +897,15 @@ func TestGenerateConfigForDCALiveCandles(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, - BuySide: minMax, - SellSide: minMax, + BuySide: strictMinMax, + SellSide: strictMinMax, MakerFee: &makerFee, TakerFee: &takerFee, }, @@ -853,20 +914,25 @@ func TestGenerateConfigForDCALiveCandles(t *testing.T) { Interval: kline.OneMin, DataType: common.CandleStr, LiveData: &LiveData{ - APIKeyOverride: "", - APISecretOverride: "", - APIClientIDOverride: "", - API2FAOverride: "", - APISubAccountOverride: "", - RealOrders: false, + 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: minMax, - SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, + BuySide: strictMinMax, + SellSide: strictMinMax, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -881,7 +947,7 @@ func TestGenerateConfigForDCALiveCandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-candles-live.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-candles-live.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -905,10 +971,10 @@ func TestGenerateConfigForRSIAPICustomSettings(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -930,9 +996,6 @@ func TestGenerateConfigForRSIAPICustomSettings(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -947,7 +1010,7 @@ func TestGenerateConfigForRSIAPICustomSettings(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "rsi-api-candles.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "rsi-api-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -968,10 +1031,10 @@ func TestGenerateConfigForDCACSVCandles(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -991,9 +1054,6 @@ func TestGenerateConfigForDCACSVCandles(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -1008,7 +1068,7 @@ func TestGenerateConfigForDCACSVCandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-csv-candles.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-csv-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -1029,10 +1089,10 @@ func TestGenerateConfigForDCACSVTrades(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -1047,11 +1107,7 @@ func TestGenerateConfigForDCACSVTrades(t *testing.T) { FullPath: fp, }, }, - PortfolioSettings: PortfolioSettings{ - Leverage: Leverage{ - CanUseLeverage: false, - }, - }, + PortfolioSettings: PortfolioSettings{}, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), }, @@ -1065,7 +1121,7 @@ func TestGenerateConfigForDCACSVTrades(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-csv-trades.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-csv-trades.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -1084,10 +1140,10 @@ func TestGenerateConfigForDCADatabaseCandles(t *testing.T) { }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, SpotDetails: &SpotDetails{ InitialQuoteFunds: initialFunds100000, }, @@ -1118,9 +1174,6 @@ func TestGenerateConfigForDCADatabaseCandles(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{ - CanUseLeverage: false, - }, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -1135,7 +1188,7 @@ func TestGenerateConfigForDCADatabaseCandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "dca-database-candles.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "dca-database-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -1163,75 +1216,75 @@ func TestGenerateConfigForTop2Bottom2(t *testing.T) { UseExchangeLevelFunding: true, ExchangeLevelFunding: []ExchangeLevelFunding{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Currency: currency.BTC, + Currency: mainCurrencyPair.Base, InitialFunds: decimal.NewFromFloat(3), }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Currency: currency.USDT, + Currency: mainCurrencyPair.Quote, InitialFunds: decimal.NewFromInt(10000), }, }, }, CurrencySettings: []CurrencySettings{ { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USDT, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.DOGE, - Quote: currency.USDT, + Quote: mainCurrencyPair.Quote, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.ETH, - Quote: currency.BTC, + Quote: mainCurrencyPair.Base, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.LTC, - Quote: currency.BTC, + Quote: mainCurrencyPair.Base, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.XRP, - Quote: currency.USDT, + Quote: mainCurrencyPair.Quote, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, TakerFee: &takerFee, }, { - ExchangeName: testExchange, + ExchangeName: mainExchange, Asset: asset.Spot, Base: currency.BNB, - Quote: currency.BTC, + Quote: mainCurrencyPair.Base, BuySide: minMax, SellSide: minMax, MakerFee: &makerFee, @@ -1249,7 +1302,6 @@ func TestGenerateConfigForTop2Bottom2(t *testing.T) { PortfolioSettings: PortfolioSettings{ BuySide: minMax, SellSide: minMax, - Leverage: Leverage{}, }, StatisticSettings: StatisticSettings{ RiskFreeRate: decimal.NewFromFloat(0.03), @@ -1264,14 +1316,14 @@ func TestGenerateConfigForTop2Bottom2(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "t2b2-api-candles-exchange-funding.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "t2b2-api-candles-exchange-funding.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } } } -func TestGenerateFTXCashAndCarryStrategy(t *testing.T) { +func TestGenerateBinanceCashAndCarryStrategy(t *testing.T) { if !saveConfig { t.Skip() } @@ -1279,36 +1331,40 @@ func TestGenerateFTXCashAndCarryStrategy(t *testing.T) { Nickname: "ExampleCashAndCarry", Goal: "To demonstrate a cash and carry strategy", StrategySettings: StrategySettings{ - Name: "ftx-cash-carry", + Name: "binance-cash-carry", SimultaneousSignalProcessing: true, }, FundingSettings: FundingSettings{ UseExchangeLevelFunding: true, ExchangeLevelFunding: []ExchangeLevelFunding{ { - ExchangeName: "ftx", + ExchangeName: mainExchange, Asset: asset.Spot, - Currency: currency.USD, + Currency: mainCurrencyPair.Quote, InitialFunds: *initialFunds100000, }, }, }, CurrencySettings: []CurrencySettings{ { - ExchangeName: "ftx", - Asset: asset.Futures, - Base: currency.BTC, - Quote: currency.NewCode("20210924"), + ExchangeName: mainExchange, + Asset: asset.USDTMarginedFutures, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, MakerFee: &makerFee, TakerFee: &takerFee, + BuySide: minMax, + SellSide: minMax, }, { - ExchangeName: "ftx", + ExchangeName: mainExchange, Asset: asset.Spot, - Base: currency.BTC, - Quote: currency.USD, + Base: mainCurrencyPair.Base, + Quote: mainCurrencyPair.Quote, MakerFee: &makerFee, TakerFee: &takerFee, + BuySide: minMax, + SellSide: minMax, }, }, DataSettings: DataSettings{ @@ -1320,9 +1376,92 @@ func TestGenerateFTXCashAndCarryStrategy(t *testing.T) { InclusiveEndDate: false, }, }, - PortfolioSettings: PortfolioSettings{ - Leverage: Leverage{ - CanUseLeverage: true, + 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{ @@ -1338,7 +1477,7 @@ func TestGenerateFTXCashAndCarryStrategy(t *testing.T) { if err != nil { t.Fatal(err) } - err = os.WriteFile(filepath.Join(p, "examples", "ftx-cash-carry.strat"), result, file.DefaultPermissionOctal) + err = os.WriteFile(filepath.Join(p, "strategyexamples", "binance-live-cash-and-carry.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } diff --git a/backtester/config/strategyconfig_types.go b/backtester/config/strategyconfig_types.go index 435b30b5..48ffd7f7 100644 --- a/backtester/config/strategyconfig_types.go +++ b/backtester/config/strategyconfig_types.go @@ -7,6 +7,7 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" ) @@ -42,12 +43,13 @@ type Config struct { // DataSettings is a container for each type of data retrieval setting. // Only ONE can be populated per config type DataSettings struct { - Interval kline.Interval `json:"interval"` - DataType string `json:"data-type"` - APIData *APIData `json:"api-data,omitempty"` - DatabaseData *DatabaseData `json:"database-data,omitempty"` - LiveData *LiveData `json:"live-data,omitempty"` - CSVData *CSVData `json:"csv-data,omitempty"` + Interval kline.Interval `json:"interval"` + DataType string `json:"data-type"` + VerboseExchangeRequests bool `json:"verbose-exchange-requests"` + APIData *APIData `json:"api-data,omitempty"` + DatabaseData *DatabaseData `json:"database-data,omitempty"` + LiveData *LiveData `json:"live-data,omitempty"` + CSVData *CSVData `json:"csv-data,omitempty"` } // FundingSettings contains funding details for individual currencies @@ -188,10 +190,17 @@ type DatabaseData struct { // LiveData defines all fields to configure live data type LiveData struct { - APIKeyOverride string `json:"api-key-override"` - APISecretOverride string `json:"api-secret-override"` - APIClientIDOverride string `json:"api-client-id-override"` - API2FAOverride string `json:"api-2fa-override"` - APISubAccountOverride string `json:"api-sub-account-override"` - RealOrders bool `json:"real-orders"` + NewEventTimeout time.Duration `json:"new-event-timeout"` + DataCheckTimer time.Duration `json:"data-check-timer"` + RealOrders bool `json:"real-orders"` + ClosePositionsOnStop bool `json:"close-positions-on-stop"` + DataRequestRetryTolerance int64 `json:"data-request-retry-tolerance"` + DataRequestRetryWaitTime time.Duration `json:"data-request-retry-wait-time"` + ExchangeCredentials []Credentials `json:"exchange-credentials"` +} + +// Credentials holds each exchanges credentials +type Credentials struct { + Exchange string `json:"exchange"` + Keys account.Credentials `json:"credentials"` } diff --git a/backtester/config/strategyconfigbuilder/main.go b/backtester/config/strategyconfigbuilder/main.go index ea7f881d..bef61f2c 100644 --- a/backtester/config/strategyconfigbuilder/main.go +++ b/backtester/config/strategyconfigbuilder/main.go @@ -236,7 +236,7 @@ func parseExchangeSettings(reader *bufio.Reader, cfg *config.Config) error { func parseStrategySettings(cfg *config.Config, reader *bufio.Reader) error { fmt.Println("Firstly, please select which strategy you wish to use") - strats := strategies.GetStrategies() + strats := strategies.GetSupportedStrategies() strategiesToUse := make([]string, len(strats)) for i := range strats { fmt.Printf("%v. %s\n", i+1, strats[i].Name()) @@ -460,19 +460,32 @@ func parseLive(reader *bufio.Reader, cfg *config.Config) { 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) + fmt.Printf("Do you want to set credentials for exchanges? y/n\n") input = quickParse(reader) - if input == y || input == yes { + if input != yes && input != y { + return + } + for { + var creds config.Credentials + fmt.Printf("What is the exchange name? y/n\n") + creds.Exchange = quickParse(reader) fmt.Println("What is the API key?") - cfg.DataSettings.LiveData.APIKeyOverride = quickParse(reader) + creds.Keys.Key = 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) + creds.Keys.Secret = quickParse(reader) + fmt.Println("What is the Client ID? (leave blank if not applicable)") + creds.Keys.ClientID = quickParse(reader) + fmt.Println("What is the 2FA seed? (leave blank if not applicable)") + creds.Keys.OneTimePassword = quickParse(reader) + fmt.Println("What is the subaccount to use? (leave blank if not applicable)") + creds.Keys.SubAccount = quickParse(reader) + fmt.Println("What is the PEM key? (leave blank if not applicable)") + creds.Keys.PEMKey = quickParse(reader) + cfg.DataSettings.LiveData.ExchangeCredentials = append(cfg.DataSettings.LiveData.ExchangeCredentials, creds) + fmt.Printf("Do you want to add another? y/n\n") + if input != yes && input != y { + break + } } } } @@ -556,10 +569,7 @@ func customSettingsLoop(reader *bufio.Reader) map[string]interface{} { } func addCurrencySetting(reader *bufio.Reader, usingExchangeLevelFunding bool) (*config.CurrencySettings, error) { - setting := config.CurrencySettings{ - BuySide: config.MinMax{}, - SellSide: config.MinMax{}, - } + setting := config.CurrencySettings{} fmt.Println("Enter the exchange name. eg Binance") setting.ExchangeName = quickParse(reader) diff --git a/backtester/config/strategyexamples/README.md b/backtester/config/strategyexamples/README.md index 9d868e9f..1dfecbd4 100644 --- a/backtester/config/strategyexamples/README.md +++ b/backtester/config/strategyexamples/README.md @@ -1,16 +1,16 @@ -# GoCryptoTrader Backtester: Examples package +# GoCryptoTrader Backtester: Strategyexamples package [![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml) [![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/config/examples) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/config/strategyexamples) [![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) -This examples package is part of the GoCryptoTrader codebase. +This strategyexamples package is part of the GoCryptoTrader codebase. ## This is still in active development @@ -18,7 +18,7 @@ You can track ideas, planned features and what's in progress on this Trello boar Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) -## Examples package overview +## Strategyexamples package overview ### Current Config Examples @@ -34,7 +34,8 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | dca-database-candles.strat | The same DCA strategy, but uses a database to retrieve candle data | | rsi-api-candles.strat | Runs a strategy using rsi figures to make buy or sell orders based on market figures | | t2b2-api-candles-exchange-funding.strat | Runs a more complex strategy using simultaneous signal processing, exchange level funding and MFI values to make buy or sell signals based on the two strongest and weakest MFI values | -| ftx-cash-carry.strat | Executes a cash and carry trade on FTX, buying BTC-USD while shorting the long dated futures contract BTC-20210924 | +| binance-cash-and-carry.strat | Executes a cash and carry trade on Binance, buying BTC-USD while shorting the long dated futures contract. Is not currently implemented | +| binance-live-cash-and-carry.strat | Executes a cash and carry trade on Binance using realtime 15 second candles, buying BTC-USD while shorting the long dated futures contract. Is not currently implemented | ### Want to make your own configs? Use the provided config builder under `/backtester/config/configbuilder` or modify tests under `/backtester/config/config_test.go` to generates strategy files quickly diff --git a/backtester/config/strategyexamples/ftx-cash-carry.strat b/backtester/config/strategyexamples/binance-cash-and-carry.strat old mode 100644 new mode 100755 similarity index 73% rename from backtester/config/strategyexamples/ftx-cash-carry.strat rename to backtester/config/strategyexamples/binance-cash-and-carry.strat index a15346ec..abfc3ed0 --- a/backtester/config/strategyexamples/ftx-cash-carry.strat +++ b/backtester/config/strategyexamples/binance-cash-and-carry.strat @@ -2,7 +2,7 @@ "nickname": "ExampleCashAndCarry", "goal": "To demonstrate a cash and carry strategy", "strategy-settings": { - "name": "ftx-cash-carry", + "name": "binance-cash-carry", "use-simultaneous-signal-processing": true, "disable-usd-tracking": false }, @@ -10,9 +10,9 @@ "use-exchange-level-funding": true, "exchange-level-funding": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", - "currency": "USD", + "currency": "USDT", "initial-funds": "100000", "transfer-fee": "0" } @@ -20,19 +20,19 @@ }, "currency-settings": [ { - "exchange-name": "ftx", - "asset": "futures", + "exchange-name": "binance", + "asset": "usdtmarginedfutures", "base": "BTC", - "quote": "20210924", + "quote": "USDT", "buy-side": { - "minimum-size": "0", - "maximum-size": "0", - "maximum-total": "0" + "minimum-size": "0.005", + "maximum-size": "2", + "maximum-total": "40000" }, "sell-side": { - "minimum-size": "0", - "maximum-size": "0", - "maximum-total": "0" + "minimum-size": "0.005", + "maximum-size": "2", + "maximum-total": "40000" }, "min-slippage-percent": "0", "max-slippage-percent": "0", @@ -44,19 +44,19 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", - "quote": "USD", + "quote": "USDT", "buy-side": { - "minimum-size": "0", - "maximum-size": "0", - "maximum-total": "0" + "minimum-size": "0.005", + "maximum-size": "2", + "maximum-total": "40000" }, "sell-side": { - "minimum-size": "0", - "maximum-size": "0", - "maximum-total": "0" + "minimum-size": "0.005", + "maximum-size": "2", + "maximum-total": "40000" }, "min-slippage-percent": "0", "max-slippage-percent": "0", @@ -71,6 +71,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-01-14T00:00:00Z", "end-date": "2021-09-24T00:00:00Z", @@ -79,7 +80,7 @@ }, "portfolio-settings": { "leverage": { - "can-use-leverage": true, + "can-use-leverage": false, "maximum-orders-with-leverage-ratio": "0", "maximum-leverage-rate": "0", "maximum-collateral-leverage-rate": "0" diff --git a/backtester/config/strategyexamples/binance-live-cash-and-carry.strat b/backtester/config/strategyexamples/binance-live-cash-and-carry.strat new file mode 100755 index 00000000..d6a16a56 --- /dev/null +++ b/backtester/config/strategyexamples/binance-live-cash-and-carry.strat @@ -0,0 +1,118 @@ +{ + "nickname": "ExampleBinanceLiveCashAndCarry", + "goal": "To demonstrate a cash and carry strategy using a live data source", + "strategy-settings": { + "name": "binance-cash-carry", + "use-simultaneous-signal-processing": true, + "disable-usd-tracking": false + }, + "funding-settings": { + "use-exchange-level-funding": true, + "exchange-level-funding": [ + { + "exchange-name": "binance", + "asset": "spot", + "currency": "USDT", + "initial-funds": "100000", + "transfer-fee": "0" + } + ] + }, + "currency-settings": [ + { + "exchange-name": "binance", + "asset": "usdtmarginedfutures", + "base": "BTC", + "quote": "USDT", + "buy-side": { + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" + }, + "sell-side": { + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" + }, + "min-slippage-percent": "0", + "max-slippage-percent": "0", + "maker-fee-override": "0.0002", + "taker-fee-override": "0.0007", + "maximum-holdings-ratio": "0", + "skip-candle-volume-fitting": true, + "use-exchange-order-limits": false, + "use-exchange-pnl-calculation": false + }, + { + "exchange-name": "binance", + "asset": "spot", + "base": "BTC", + "quote": "USDT", + "buy-side": { + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" + }, + "sell-side": { + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" + }, + "min-slippage-percent": "0", + "max-slippage-percent": "0", + "maker-fee-override": "0.0002", + "taker-fee-override": "0.0007", + "maximum-holdings-ratio": "0", + "skip-candle-volume-fitting": true, + "use-exchange-order-limits": false, + "use-exchange-pnl-calculation": false + } + ], + "data-settings": { + "interval": 15000000000, + "data-type": "candle", + "verbose-exchange-requests": false, + "live-data": { + "new-event-timeout": 60000000000, + "data-check-timer": 1000000000, + "real-orders": false, + "close-positions-on-stop": true, + "data-request-retry-tolerance": 3, + "data-request-retry-wait-time": 500000000, + "exchange-credentials": [ + { + "exchange": "binance", + "credentials": { + "Key": "", + "Secret": "", + "ClientID": "", + "PEMKey": "", + "SubAccount": "", + "OneTimePassword": "" + } + } + ] + } + }, + "portfolio-settings": { + "leverage": { + "can-use-leverage": false, + "maximum-orders-with-leverage-ratio": "0", + "maximum-leverage-rate": "0", + "maximum-collateral-leverage-rate": "0" + }, + "buy-side": { + "minimum-size": "0", + "maximum-size": "0", + "maximum-total": "0" + }, + "sell-side": { + "minimum-size": "0", + "maximum-size": "0", + "maximum-total": "0" + } + }, + "statistic-settings": { + "risk-free-rate": "0.03" + } +} \ No newline at end of file diff --git a/backtester/config/strategyexamples/custom-plugin-strategy.strat b/backtester/config/strategyexamples/custom-plugin-strategy.strat index e8739742..c58188d8 100644 --- a/backtester/config/strategyexamples/custom-plugin-strategy.strat +++ b/backtester/config/strategyexamples/custom-plugin-strategy.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -41,6 +41,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/config/strategyexamples/dca-api-candles-exchange-level-funding.strat b/backtester/config/strategyexamples/dca-api-candles-exchange-level-funding.strat index dca2e098..9be15ae9 100644 --- a/backtester/config/strategyexamples/dca-api-candles-exchange-level-funding.strat +++ b/backtester/config/strategyexamples/dca-api-candles-exchange-level-funding.strat @@ -10,7 +10,7 @@ "use-exchange-level-funding": true, "exchange-level-funding": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "currency": "USDT", "initial-funds": "100000", @@ -20,7 +20,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -44,7 +44,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "ETH", "quote": "USDT", @@ -71,6 +71,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/config/strategyexamples/dca-api-candles-multiple-currencies.strat b/backtester/config/strategyexamples/dca-api-candles-multiple-currencies.strat index 08de70e9..2c10b8bf 100644 --- a/backtester/config/strategyexamples/dca-api-candles-multiple-currencies.strat +++ b/backtester/config/strategyexamples/dca-api-candles-multiple-currencies.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -38,7 +38,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "ETH", "quote": "USDT", @@ -68,6 +68,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/config/strategyexamples/dca-api-candles-simultaneous-processing.strat b/backtester/config/strategyexamples/dca-api-candles-simultaneous-processing.strat index 662f4a5e..b3f186bc 100644 --- a/backtester/config/strategyexamples/dca-api-candles-simultaneous-processing.strat +++ b/backtester/config/strategyexamples/dca-api-candles-simultaneous-processing.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -38,7 +38,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "ETH", "quote": "USDT", @@ -68,6 +68,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/config/strategyexamples/dca-api-candles.strat b/backtester/config/strategyexamples/dca-api-candles.strat index 6e93d23c..8e481028 100644 --- a/backtester/config/strategyexamples/dca-api-candles.strat +++ b/backtester/config/strategyexamples/dca-api-candles.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -41,6 +41,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/config/strategyexamples/dca-api-trades.strat b/backtester/config/strategyexamples/dca-api-trades.strat index a253a2a7..da6eca62 100644 --- a/backtester/config/strategyexamples/dca-api-trades.strat +++ b/backtester/config/strategyexamples/dca-api-trades.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -41,6 +41,7 @@ "data-settings": { "interval": 3600000000000, "data-type": "trade", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-08-04T00:00:00+10:00", diff --git a/backtester/config/strategyexamples/dca-candles-live.strat b/backtester/config/strategyexamples/dca-candles-live.strat index 7fe0bede..295caeb9 100644 --- a/backtester/config/strategyexamples/dca-candles-live.strat +++ b/backtester/config/strategyexamples/dca-candles-live.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -19,14 +19,14 @@ "initial-quote-funds": "100000" }, "buy-side": { - "minimum-size": "0.005", - "maximum-size": "2", - "maximum-total": "40000" + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" }, "sell-side": { - "minimum-size": "0.005", - "maximum-size": "2", - "maximum-total": "40000" + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" }, "min-slippage-percent": "0", "max-slippage-percent": "0", @@ -41,13 +41,27 @@ "data-settings": { "interval": 60000000000, "data-type": "candle", + "verbose-exchange-requests": false, "live-data": { - "api-key-override": "", - "api-secret-override": "", - "api-client-id-override": "", - "api-2fa-override": "", - "api-sub-account-override": "", - "real-orders": false + "new-event-timeout": 120000000000, + "data-check-timer": 1000000000, + "real-orders": false, + "close-positions-on-stop": false, + "data-request-retry-tolerance": 3, + "data-request-retry-wait-time": 500000000, + "exchange-credentials": [ + { + "exchange": "binance", + "credentials": { + "Key": "", + "Secret": "", + "ClientID": "", + "PEMKey": "", + "SubAccount": "", + "OneTimePassword": "" + } + } + ] } }, "portfolio-settings": { @@ -58,14 +72,14 @@ "maximum-collateral-leverage-rate": "0" }, "buy-side": { - "minimum-size": "0.005", - "maximum-size": "2", - "maximum-total": "40000" + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" }, "sell-side": { - "minimum-size": "0.005", - "maximum-size": "2", - "maximum-total": "40000" + "minimum-size": "0.001", + "maximum-size": "0.05", + "maximum-total": "100" } }, "statistic-settings": { diff --git a/backtester/config/strategyexamples/dca-csv-candles.strat b/backtester/config/strategyexamples/dca-csv-candles.strat index b95c3639..afdacc0c 100644 --- a/backtester/config/strategyexamples/dca-csv-candles.strat +++ b/backtester/config/strategyexamples/dca-csv-candles.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -41,6 +41,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "csv-data": { "full-path": "..\\testdata\\binance_BTCUSDT_24h_2019_01_01_2020_01_01.csv" } diff --git a/backtester/config/strategyexamples/dca-csv-trades.strat b/backtester/config/strategyexamples/dca-csv-trades.strat index 8bdd43dd..f220ddf6 100644 --- a/backtester/config/strategyexamples/dca-csv-trades.strat +++ b/backtester/config/strategyexamples/dca-csv-trades.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -41,6 +41,7 @@ "data-settings": { "interval": 60000000000, "data-type": "trade", + "verbose-exchange-requests": false, "csv-data": { "full-path": "..\\testdata\\binance_BTCUSDT_24h-trades_2020_11_16.csv" } diff --git a/backtester/config/strategyexamples/dca-database-candles.strat b/backtester/config/strategyexamples/dca-database-candles.strat index 08e2b18e..aff612a7 100644 --- a/backtester/config/strategyexamples/dca-database-candles.strat +++ b/backtester/config/strategyexamples/dca-database-candles.strat @@ -11,7 +11,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -41,6 +41,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "database-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/config/strategyexamples/rsi-api-candles.strat b/backtester/config/strategyexamples/rsi-api-candles.strat index e3e946fd..860a4bdd 100644 --- a/backtester/config/strategyexamples/rsi-api-candles.strat +++ b/backtester/config/strategyexamples/rsi-api-candles.strat @@ -16,7 +16,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -46,6 +46,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-05-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/config/strategyexamples/t2b2-api-candles-exchange-funding.strat b/backtester/config/strategyexamples/t2b2-api-candles-exchange-funding.strat index 4ba22996..ec3bd2cf 100644 --- a/backtester/config/strategyexamples/t2b2-api-candles-exchange-funding.strat +++ b/backtester/config/strategyexamples/t2b2-api-candles-exchange-funding.strat @@ -15,14 +15,14 @@ "use-exchange-level-funding": true, "exchange-level-funding": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "currency": "BTC", "initial-funds": "3", "transfer-fee": "0" }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "currency": "USDT", "initial-funds": "10000", @@ -32,7 +32,7 @@ }, "currency-settings": [ { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BTC", "quote": "USDT", @@ -56,7 +56,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "DOGE", "quote": "USDT", @@ -80,7 +80,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "ETH", "quote": "BTC", @@ -104,7 +104,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "LTC", "quote": "BTC", @@ -128,7 +128,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "XRP", "quote": "USDT", @@ -152,7 +152,7 @@ "use-exchange-pnl-calculation": false }, { - "exchange-name": "ftx", + "exchange-name": "binance", "asset": "spot", "base": "BNB", "quote": "BTC", @@ -179,6 +179,7 @@ "data-settings": { "interval": 86400000000000, "data-type": "candle", + "verbose-exchange-requests": false, "api-data": { "start-date": "2021-08-01T00:00:00+10:00", "end-date": "2021-12-01T00:00:00+11:00", diff --git a/backtester/data/data.go b/backtester/data/data.go index 43ef41ff..bf506ca8 100644 --- a/backtester/data/data.go +++ b/backtester/data/data.go @@ -6,129 +6,338 @@ import ( "strings" "github.com/thrasher-corp/gocryptotrader/backtester/common" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) -// Setup creates a basic map -func (h *HandlerPerCurrency) Setup() { - if h.data == nil { - h.data = make(map[string]map[asset.Item]map[currency.Pair]Handler) +// NewHandlerHolder returns a new HandlerHolder +func NewHandlerHolder() *HandlerHolder { + return &HandlerHolder{ + data: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler), } } -// SetDataForCurrency assigns a data Handler to the data map by exchange, asset and currency -func (h *HandlerPerCurrency) SetDataForCurrency(e string, a asset.Item, p currency.Pair, k Handler) { +// SetDataForCurrency assigns a Data Handler to the Data map by exchange, asset and currency +func (h *HandlerHolder) SetDataForCurrency(e string, a asset.Item, p currency.Pair, k Handler) error { + if h == nil { + return fmt.Errorf("%w handler holder", gctcommon.ErrNilPointer) + } + h.m.Lock() + defer h.m.Unlock() if h.data == nil { - h.Setup() + h.data = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler) } e = strings.ToLower(e) - if h.data[e] == nil { - h.data[e] = make(map[asset.Item]map[currency.Pair]Handler) + m1, ok := h.data[e] + if !ok { + m1 = make(map[asset.Item]map[*currency.Item]map[*currency.Item]Handler) + h.data[e] = m1 } - if h.data[e][a] == nil { - h.data[e][a] = make(map[currency.Pair]Handler) + + m2, ok := m1[a] + if !ok { + m2 = make(map[*currency.Item]map[*currency.Item]Handler) + m1[a] = m2 } - h.data[e][a][p] = k + + m3, ok := m2[p.Base.Item] + if !ok { + m3 = make(map[*currency.Item]Handler) + m2[p.Base.Item] = m3 + } + + m3[p.Quote.Item] = k + return nil } -// GetAllData returns all set data in the data map -func (h *HandlerPerCurrency) GetAllData() map[string]map[asset.Item]map[currency.Pair]Handler { - return h.data +// GetAllData returns all set Data in the Data map +func (h *HandlerHolder) GetAllData() ([]Handler, error) { + if h == nil { + return nil, fmt.Errorf("%w handler holder", gctcommon.ErrNilPointer) + } + h.m.Lock() + defer h.m.Unlock() + var resp []Handler + for _, exchMap := range h.data { + for _, assetMap := range exchMap { + for _, baseMap := range assetMap { + for _, handler := range baseMap { + resp = append(resp, handler) + } + } + } + } + return resp, nil } // GetDataForCurrency returns the Handler for a specific exchange, asset, currency -func (h *HandlerPerCurrency) GetDataForCurrency(ev common.EventHandler) (Handler, error) { +func (h *HandlerHolder) GetDataForCurrency(ev common.Event) (Handler, error) { + if h == nil { + return nil, fmt.Errorf("%w handler holder", gctcommon.ErrNilPointer) + } if ev == nil { return nil, common.ErrNilEvent } - handler, ok := h.data[ev.GetExchange()][ev.GetAssetType()][ev.Pair()] + h.m.Lock() + defer h.m.Unlock() + exch := ev.GetExchange() + a := ev.GetAssetType() + p := ev.Pair() + handler, ok := h.data[exch][a][p.Base.Item][p.Quote.Item] if !ok { - return nil, fmt.Errorf("%s %s %s %w", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), ErrHandlerNotFound) + return nil, fmt.Errorf("%s %s %s %w", exch, a, p, ErrHandlerNotFound) } return handler, nil } // Reset returns the struct to defaults -func (h *HandlerPerCurrency) Reset() { - h.data = nil +func (h *HandlerHolder) Reset() error { + if h == nil { + return gctcommon.ErrNilPointer + } + h.m.Lock() + defer h.m.Unlock() + h.data = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler) + return nil } -// Reset loaded data to blank state -func (b *Base) Reset() { +// GetDetails returns data about the Base Holder +func (b *Base) GetDetails() (string, asset.Item, currency.Pair, error) { + if b == nil { + return "", asset.Empty, currency.EMPTYPAIR, fmt.Errorf("%w base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + return b.latest.GetExchange(), b.latest.GetAssetType(), b.latest.Pair(), nil +} + +// Reset loaded Data to blank state +func (b *Base) Reset() error { + if b == nil { + return gctcommon.ErrNilPointer + } + b.m.Lock() + defer b.m.Unlock() + b.stream = nil b.latest = nil b.offset = 0 - b.stream = nil + b.isLiveData = false + return nil } -// GetStream will return entire data list -func (b *Base) GetStream() []common.DataEventHandler { - return b.stream +// GetStream will return entire Data list +func (b *Base) GetStream() (Events, error) { + if b == nil { + return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + stream := make([]Event, len(b.stream)) + copy(stream, b.stream) + + return stream, nil } -// Offset returns the current iteration of candle data the backtester is assessing -func (b *Base) Offset() int { - return b.offset +// Offset returns the current iteration of candle Data the backtester is assessing +func (b *Base) Offset() (int64, error) { + if b == nil { + return 0, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + return b.offset, nil } -// SetStream sets the data stream for candle analysis -func (b *Base) SetStream(s []common.DataEventHandler) { +// SetStream sets the Data stream for candle analysis +func (b *Base) SetStream(s []Event) error { + if b == nil { + return fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + + sort.Slice(s, func(i, j int) bool { + return s[i].GetTime().Before(s[j].GetTime()) + }) + for x := range s { + if s[x] == nil { + return fmt.Errorf("%w Event", gctcommon.ErrNilPointer) + } + if s[x].GetExchange() == "" || !s[x].GetAssetType().IsValid() || s[x].Pair().IsEmpty() || s[x].GetTime().IsZero() { + return ErrInvalidEventSupplied + } + if len(b.stream) > 0 { + if s[x].GetExchange() != b.stream[0].GetExchange() || + s[x].GetAssetType() != b.stream[0].GetAssetType() || + !s[x].Pair().Equal(b.stream[0].Pair()) { + return fmt.Errorf("%w cannot set base stream from %v %v %v to %v %v %v", errMisMatchedEvent, s[x].GetExchange(), s[x].GetAssetType(), s[x].Pair(), b.stream[0].GetExchange(), b.stream[0].GetAssetType(), b.stream[0].Pair()) + } + } + // due to the Next() function, we cannot take + // stream offsets as is, and we re-set them + s[x].SetOffset(int64(x) + 1) + } + b.stream = s + return nil } // AppendStream appends new datas onto the stream, however, will not // add duplicates. Used for live analysis -func (b *Base) AppendStream(s ...common.DataEventHandler) { - for i := range s { - if s[i] == nil { - continue - } - b.stream = append(b.stream, s[i]) +func (b *Base) AppendStream(s ...Event) error { + if b == nil { + return fmt.Errorf("%w Base", gctcommon.ErrNilPointer) } + if len(s) == 0 { + return errNothingToAdd + } + b.m.Lock() + defer b.m.Unlock() +candles: + for x := range s { + if s[x] == nil { + return fmt.Errorf("%w Event", gctcommon.ErrNilPointer) + } + if s[x].GetExchange() == "" || !s[x].GetAssetType().IsValid() || s[x].Pair().IsEmpty() || s[x].GetTime().IsZero() { + return ErrInvalidEventSupplied + } + if len(b.stream) > 0 { + if s[x].GetExchange() != b.stream[0].GetExchange() || + s[x].GetAssetType() != b.stream[0].GetAssetType() || + !s[x].Pair().Equal(b.stream[0].Pair()) { + return fmt.Errorf("%w %v %v %v received %v %v %v", errMisMatchedEvent, b.stream[0].GetExchange(), b.stream[0].GetAssetType(), b.stream[0].Pair(), s[x].GetExchange(), s[x].GetAssetType(), s[x].Pair()) + } + // todo change b.stream to map + for y := len(b.stream) - 1; y >= 0; y-- { + if s[x].GetTime().Equal(b.stream[y].GetTime()) { + continue candles + } + } + } + + b.stream = append(b.stream, s[x]) + } + + sort.Slice(b.stream, func(i, j int) bool { + return b.stream[i].GetTime().Before(b.stream[j].GetTime()) + }) + for i := range b.stream { + b.stream[i].SetOffset(int64(i) + 1) + } + return nil } // Next will return the next event in the list and also shift the offset one -func (b *Base) Next() (dh common.DataEventHandler) { - if len(b.stream) <= b.offset { - return nil +func (b *Base) Next() (Event, error) { + if b == nil { + return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + if int64(len(b.stream)) <= b.offset { + return nil, fmt.Errorf("%w data length %v offset %v", ErrEndOfData, len(b.stream), b.offset) } - ret := b.stream[b.offset] b.offset++ b.latest = ret - return ret + return ret, nil } -// History will return all previous data events that have happened -func (b *Base) History() []common.DataEventHandler { - return b.stream[:b.offset] +// History will return all previous Data events that have happened +func (b *Base) History() (Events, error) { + if b == nil { + return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + + stream := make([]Event, len(b.stream[:b.offset])) + copy(stream, b.stream[:b.offset]) + + return stream, nil } -// Latest will return latest data event -func (b *Base) Latest() common.DataEventHandler { - if b.latest == nil && len(b.stream) >= b.offset+1 { +// Latest will return latest Data event +func (b *Base) Latest() (Event, error) { + if b == nil { + return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + + if b.latest == nil && int64(len(b.stream)) >= b.offset+1 { b.latest = b.stream[b.offset] } - return b.latest + return b.latest, nil } -// List returns all future data events from the current iteration +// List returns all future Data events from the current iteration // ill-advised to use this in strategies because you don't know the future in real life -func (b *Base) List() []common.DataEventHandler { - return b.stream[b.offset:] +func (b *Base) List() (Events, error) { + if b == nil { + return nil, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + + stream := make([]Event, len(b.stream[b.offset:])) + copy(stream, b.stream[b.offset:]) + + return stream, nil } // IsLastEvent determines whether the latest event is the last event -func (b *Base) IsLastEvent() bool { - return b.latest != nil && b.latest.GetOffset() == int64(len(b.stream)) +// for live Data, this will be false, as all appended Data is the latest available Data +// and this signal cannot be completely relied upon +func (b *Base) IsLastEvent() (bool, error) { + if b == nil { + return false, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + + return b.latest != nil && b.latest.GetOffset() == int64(len(b.stream)) && !b.isLiveData, + nil } -// SortStream sorts the stream by timestamp -func (b *Base) SortStream() { - sort.Slice(b.stream, func(i, j int) bool { - b1 := b.stream[i] - b2 := b.stream[j] +// IsLive returns if the Data source is a live one +// less scrutiny on checks is required on live Data sourcing +func (b *Base) IsLive() (bool, error) { + if b == nil { + return false, fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() - return b1.GetTime().Before(b2.GetTime()) - }) + return b.isLiveData, nil +} + +// SetLive sets if the Data source is a live one +// less scrutiny on checks is required on live Data sourcing +func (b *Base) SetLive(isLive bool) error { + if b == nil { + return fmt.Errorf("%w Base", gctcommon.ErrNilPointer) + } + b.m.Lock() + defer b.m.Unlock() + + b.isLiveData = isLive + return nil +} + +// First returns the first element of a slice +func (e Events) First() (Event, error) { + if len(e) == 0 { + return nil, ErrEmptySlice + } + return e[0], nil +} + +// Last returns the last element of a slice +func (e Events) Last() (Event, error) { + if len(e) == 0 { + return nil, ErrEmptySlice + } + return e[len(e)-1], nil } diff --git a/backtester/data/data_test.go b/backtester/data/data_test.go index 0fc5fe45..b0eb7d1c 100644 --- a/backtester/data/data_test.go +++ b/backtester/data/data_test.go @@ -2,152 +2,84 @@ package data import ( "errors" + "strings" "testing" "time" "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" - "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/kline" + gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" ) -const testExchange = "binance" +const exch = "binance" +const a = asset.Spot -type fakeDataHandler struct { - time int +var p = currency.NewPair(currency.BTC, currency.USD) + +type fakeEvent struct { + secretID int64 + *event.Base } -func TestLatest(t *testing.T) { - t.Parallel() - var d Base - d.AppendStream(&fakeDataHandler{time: 1}) - if latest := d.Latest(); latest != d.stream[d.offset] { - t.Error("expected latest to match offset") - } -} - -func TestBaseDataFunctions(t *testing.T) { - t.Parallel() - var d Base - - d.Next() - o := d.Offset() - if o != 0 { - t.Error("expected 0") - } - d.AppendStream(nil) - if d.IsLastEvent() { - t.Error("no") - } - d.AppendStream(nil) - if len(d.stream) != 0 { - t.Error("expected 0") - } - d.AppendStream(&fakeDataHandler{time: 1}) - d.AppendStream(&fakeDataHandler{time: 2}) - d.AppendStream(&fakeDataHandler{time: 3}) - d.AppendStream(&fakeDataHandler{time: 4}) - d.Next() - - d.Next() - if list := d.List(); len(list) != 2 { - t.Errorf("expected 2 received %v", len(list)) - } - d.Next() - d.Next() - if !d.IsLastEvent() { - t.Error("expected last event") - } - o = d.Offset() - if o != 4 { - t.Error("expected 4") - } - if list := d.List(); len(list) != 0 { - t.Error("expected 0") - } - if history := d.History(); len(history) != 4 { - t.Errorf("expected 4 received %v", len(history)) - } - - d.SetStream(nil) - if st := d.GetStream(); st != nil { - t.Error("expected nil") - } - d.Reset() - d.GetStream() - d.SortStream() -} - -func TestSetup(t *testing.T) { - t.Parallel() - d := HandlerPerCurrency{} - d.Setup() - if d.data == nil { - t.Error("expected not nil") - } -} +type fakeHandler struct{} func TestSetDataForCurrency(t *testing.T) { t.Parallel() - d := HandlerPerCurrency{} - exch := testExchange - a := asset.Spot - p := currency.NewPair(currency.BTC, currency.USDT) - d.SetDataForCurrency(exch, a, p, nil) + d := HandlerHolder{} + err := d.SetDataForCurrency(exch, a, p, nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if d.data == nil { t.Error("expected not nil") } - if d.data[exch][a][p] != nil { + if d.data[exch][a][p.Base.Item][p.Quote.Item] != nil { t.Error("expected nil") } } func TestGetAllData(t *testing.T) { t.Parallel() - d := HandlerPerCurrency{} - exch := testExchange - a := asset.Spot - p := currency.NewPair(currency.BTC, currency.USDT) - d.SetDataForCurrency(exch, a, p, nil) - d.SetDataForCurrency(exch, a, currency.NewPair(currency.BTC, currency.DOGE), nil) - result := d.GetAllData() - if len(result) != 1 { - t.Error("expected 1") + d := HandlerHolder{} + err := d.SetDataForCurrency(exch, a, p, nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } - if len(result[exch][a]) != 2 { + err = d.SetDataForCurrency(exch, a, currency.NewPair(currency.BTC, currency.DOGE), nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + result, err := d.GetAllData() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(result) != 2 { t.Error("expected 2") } } func TestGetDataForCurrency(t *testing.T) { t.Parallel() - d := HandlerPerCurrency{} - a := asset.Spot - p := currency.NewPair(currency.BTC, currency.USDT) - d.SetDataForCurrency(testExchange, a, p, nil) - d.SetDataForCurrency(testExchange, a, currency.NewPair(currency.BTC, currency.DOGE), nil) - ev := &order.Order{Base: &event.Base{ - Exchange: testExchange, - AssetType: a, - CurrencyPair: p, - }} - result, err := d.GetDataForCurrency(ev) - if err != nil { - t.Error(err) - } - if result != nil { - t.Error("expected nil") + d := HandlerHolder{} + err := d.SetDataForCurrency(exch, a, p, &fakeHandler{}) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } + err = d.SetDataForCurrency(exch, a, currency.NewPair(currency.BTC, currency.DOGE), nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } _, err = d.GetDataForCurrency(nil) if !errors.Is(err, common.ErrNilEvent) { t.Errorf("received '%v' expected '%v'", err, common.ErrNilEvent) } - _, err = d.GetDataForCurrency(&order.Order{Base: &event.Base{ + _, err = d.GetDataForCurrency(&fakeEvent{Base: &event.Base{ Exchange: "lol", AssetType: asset.USDTMarginedFutures, CurrencyPair: currency.NewPair(currency.EMB, currency.DOGE), @@ -156,99 +88,800 @@ func TestGetDataForCurrency(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, ErrHandlerNotFound) } - _, err = d.GetDataForCurrency(&order.Order{Base: &event.Base{ - Exchange: testExchange, - AssetType: asset.USDTMarginedFutures, - CurrencyPair: currency.NewPair(currency.EMB, currency.DOGE), + _, err = d.GetDataForCurrency(&fakeEvent{Base: &event.Base{ + Exchange: exch, + AssetType: a, + CurrencyPair: p, }}) - if !errors.Is(err, ErrHandlerNotFound) { - t.Errorf("received '%v' expected '%v'", err, ErrHandlerNotFound) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } } func TestReset(t *testing.T) { t.Parallel() - d := HandlerPerCurrency{} - exch := testExchange - a := asset.Spot - p := currency.NewPair(currency.BTC, currency.USDT) - d.SetDataForCurrency(exch, a, p, nil) - d.SetDataForCurrency(exch, a, currency.NewPair(currency.BTC, currency.DOGE), nil) - d.Reset() - if d.data != nil { - t.Error("expected nil") + d := &HandlerHolder{} + err := d.SetDataForCurrency(exch, a, p, nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + err = d.SetDataForCurrency(exch, a, currency.NewPair(currency.BTC, currency.DOGE), nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + err = d.Reset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if d.data == nil { + t.Error("expected a map") + } + d = nil + err = d.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } } -// methods that satisfy the common.DataEventHandler interface -func (f fakeDataHandler) GetOffset() int64 { - return 4 +func TestBaseReset(t *testing.T) { + t.Parallel() + b := &Base{offset: 1} + err := b.Reset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if b.offset != 0 { + t.Errorf("received '%v' expected '%v'", b.offset, 0) + } + b = nil + err = b.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } } -func (f fakeDataHandler) SetOffset(int64) { +func TestGetStream(t *testing.T) { + t.Parallel() + b := &Base{} + resp, err := b.GetStream() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(resp) != 0 { + t.Errorf("received '%v' expected '%v'", len(resp), 0) + } + b.stream = []Event{ + &fakeEvent{ + Base: &event.Base{ + Offset: 2048, + Time: time.Now(), + }, + }, + &fakeEvent{ + Base: &event.Base{ + Offset: 1337, + Time: time.Now().Add(-time.Hour), + }, + }, + } + resp, err = b.GetStream() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(resp) != 2 { + t.Errorf("received '%v' expected '%v'", len(resp), 2) + } + + b = nil + _, err = b.GetStream() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } } -func (f fakeDataHandler) IsEvent() bool { +func TestOffset(t *testing.T) { + t.Parallel() + b := &Base{} + o, err := b.Offset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if o != 0 { + t.Errorf("received '%v' expected '%v'", o, 0) + } + b.offset = 1337 + o, err = b.Offset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if o != 1337 { + t.Errorf("received '%v' expected '%v'", o, 1337) + } + + b = nil + _, err = b.Offset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestSetStream(t *testing.T) { + t.Parallel() + b := &Base{} + err := b.SetStream(nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(b.stream) != 0 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 0) + } + cp := currency.NewPair(currency.BTC, currency.USD) + err = b.SetStream([]Event{ + &fakeEvent{ + Base: &event.Base{ + Offset: 2048, + Time: time.Now(), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + &fakeEvent{ + Base: &event.Base{ + Offset: 1337, + Time: time.Now().Add(-time.Hour), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + if len(b.stream) != 2 { + t.Fatalf("received '%v' expected '%v'", len(b.stream), 2) + } + if b.stream[0].GetOffset() != 1 { + t.Errorf("received '%v' expected '%v'", b.stream[0].GetOffset(), 1) + } + + misMatchEvent := &fakeEvent{ + Base: &event.Base{ + Exchange: "mismatch", + CurrencyPair: currency.NewPair(currency.BTC, currency.DOGE), + AssetType: asset.Futures, + }, + } + err = b.SetStream([]Event{misMatchEvent}) + if !errors.Is(err, ErrInvalidEventSupplied) { + t.Fatalf("received '%v' expected '%v'", err, ErrInvalidEventSupplied) + } + + misMatchEvent.Time = time.Now() + err = b.SetStream([]Event{misMatchEvent}) + if !errors.Is(err, errMisMatchedEvent) { + t.Fatalf("received '%v' expected '%v'", err, errMisMatchedEvent) + } + + err = b.SetStream([]Event{nil}) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Fatalf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + b = nil + err = b.SetStream(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestNext(t *testing.T) { + t.Parallel() + b := &Base{} + cp := currency.NewPair(currency.BTC, currency.USD) + err := b.SetStream([]Event{ + &fakeEvent{ + Base: &event.Base{ + Offset: 2048, + Time: time.Now(), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + &fakeEvent{ + Base: &event.Base{ + Offset: 1337, + Time: time.Now().Add(-time.Hour), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + resp, err := b.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if resp != b.stream[0] { + t.Errorf("received '%v' expected '%v'", resp, b.stream[0]) + } + if b.offset != 1 { + t.Errorf("received '%v' expected '%v'", b.offset, 1) + } + _, err = b.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + resp, err = b.Next() + if !errors.Is(err, ErrEndOfData) { + t.Errorf("received '%v' expected '%v'", err, ErrEndOfData) + } + if resp != nil { + t.Errorf("received '%v' expected '%v'", resp, nil) + } + + b = nil + _, err = b.Next() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestHistory(t *testing.T) { + t.Parallel() + b := &Base{} + cp := currency.NewPair(currency.BTC, currency.USD) + err := b.SetStream([]Event{ + &fakeEvent{ + Base: &event.Base{ + Offset: 2048, + Time: time.Now(), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + &fakeEvent{ + Base: &event.Base{ + Offset: 1337, + Time: time.Now().Add(-time.Hour), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + resp, err := b.History() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(resp) != 0 { + t.Errorf("received '%v' expected '%v'", len(resp), 0) + } + + _, err = b.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + resp, err = b.History() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(resp) != 1 { + t.Errorf("received '%v' expected '%v'", len(resp), 1) + } + + b = nil + _, err = b.History() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestLatest(t *testing.T) { + t.Parallel() + b := &Base{} + cp := currency.NewPair(currency.BTC, currency.USD) + err := b.SetStream([]Event{ + &fakeEvent{ + Base: &event.Base{ + Offset: 2048, + Time: time.Now(), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + &fakeEvent{ + Base: &event.Base{ + Offset: 1337, + Time: time.Now().Add(-time.Hour), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + resp, err := b.Latest() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if resp != b.stream[0] { + t.Errorf("received '%v' expected '%v'", resp, b.stream[0]) + } + _, err = b.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + resp, err = b.Latest() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if resp != b.stream[0] { + t.Errorf("received '%v' expected '%v'", resp, b.stream[0]) + } + + _, err = b.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + resp, err = b.Latest() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if resp != b.stream[1] { + t.Errorf("received '%v' expected '%v'", resp, b.stream[1]) + } + + b = nil + _, err = b.Latest() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestList(t *testing.T) { + t.Parallel() + b := &Base{} + cp := currency.NewPair(currency.BTC, currency.USD) + err := b.SetStream([]Event{ + &fakeEvent{ + Base: &event.Base{ + Offset: 2048, + Time: time.Now(), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + &fakeEvent{ + Base: &event.Base{ + Offset: 1337, + Time: time.Now().Add(-time.Hour), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + list, err := b.List() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(list) != 2 { + t.Errorf("received '%v' expected '%v'", len(list), 2) + } + + b = nil + _, err = b.List() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestIsLastEvent(t *testing.T) { + t.Parallel() + b := &Base{} + cp := currency.NewPair(currency.BTC, currency.USD) + err := b.SetStream([]Event{ + &fakeEvent{ + Base: &event.Base{ + Offset: 2048, + Time: time.Now(), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + &fakeEvent{ + Base: &event.Base{ + Offset: 1337, + Time: time.Now().Add(-time.Hour), + Exchange: "test", + AssetType: asset.Spot, + CurrencyPair: cp, + }, + }, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + b.latest = b.stream[0] + b.offset = b.stream[0].GetOffset() + isLastEvent, err := b.IsLastEvent() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if isLastEvent { + t.Errorf("received '%v' expected '%v'", false, true) + } + + b.isLiveData = true + isLastEvent, err = b.IsLastEvent() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if isLastEvent { + t.Errorf("received '%v' expected '%v'", false, true) + } + + b = nil + _, err = b.IsLastEvent() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestIsLive(t *testing.T) { + t.Parallel() + b := &Base{} + isLive, err := b.IsLive() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if isLive { + t.Error("expected false") + } + b.isLiveData = true + isLive, err = b.IsLive() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if !isLive { + t.Error("expected true") + } + + b = nil + _, err = b.IsLive() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestSetLive(t *testing.T) { + t.Parallel() + b := &Base{} + err := b.SetLive(true) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if !b.isLiveData { + t.Error("expected true") + } + + err = b.SetLive(false) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if b.isLiveData { + t.Error("expected false") + } + + b = nil + err = b.SetLive(false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestAppendStream(t *testing.T) { + t.Parallel() + b := &Base{} + e := &fakeEvent{ + Base: &event.Base{}, + } + err := b.AppendStream(e) + if !errors.Is(err, ErrInvalidEventSupplied) { + t.Errorf("received '%v' expected '%v'", err, ErrInvalidEventSupplied) + } + if len(b.stream) != 0 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 0) + } + tt := time.Now().Add(-time.Hour) + cp := currency.NewPair(currency.BTC, currency.USD) + e.Exchange = "b" + e.AssetType = asset.Spot + e.CurrencyPair = cp + err = b.AppendStream(e) + if !errors.Is(err, ErrInvalidEventSupplied) { + t.Fatalf("received '%v' expected '%v'", err, ErrInvalidEventSupplied) + } + + e.Time = tt + err = b.AppendStream(e, e) + if !errors.Is(err, nil) { + t.Fatalf("received '%v' expected '%v'", err, nil) + } + if len(b.stream) != 1 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 1) + } + + err = b.AppendStream(e) + if !errors.Is(err, nil) { + t.Fatalf("received '%v' expected '%v'", err, nil) + } + if len(b.stream) != 1 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 1) + } + + err = b.AppendStream(&fakeEvent{ + Base: &event.Base{ + Exchange: "b", + AssetType: asset.Spot, + CurrencyPair: cp, + Time: time.Now(), + }, + }) + if !errors.Is(err, nil) { + t.Fatalf("received '%v' expected '%v'", err, nil) + } + if len(b.stream) != 2 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 2) + } + + misMatchEvent := &fakeEvent{ + Base: &event.Base{ + Exchange: "mismatch", + CurrencyPair: currency.NewPair(currency.BTC, currency.DOGE), + AssetType: asset.Futures, + Time: tt, + }, + } + err = b.AppendStream(misMatchEvent) + if !errors.Is(err, errMisMatchedEvent) { + t.Fatalf("received '%v' expected '%v'", err, errMisMatchedEvent) + } + if len(b.stream) != 2 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 2) + } + + err = b.AppendStream(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Fatalf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + if len(b.stream) != 2 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 2) + } + + err = b.AppendStream() + if !errors.Is(err, errNothingToAdd) { + t.Fatalf("received '%v' expected '%v'", err, errNothingToAdd) + } + if len(b.stream) != 2 { + t.Errorf("received '%v' expected '%v'", len(b.stream), 2) + } + + b = nil + err = b.AppendStream() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestFirst(t *testing.T) { + t.Parallel() + var id1 int64 = 1 + var id2 int64 = 2 + var id3 int64 = 3 + e := Events{ + fakeEvent{secretID: id1}, + fakeEvent{secretID: id2}, + fakeEvent{secretID: id3}, + } + + first, err := e.First() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if first.GetOffset() != id1 { + t.Errorf("received '%v' expected '%v'", first.GetOffset(), id1) + } +} + +func TestLast(t *testing.T) { + t.Parallel() + var id1 int64 = 1 + var id2 int64 = 2 + var id3 int64 = 3 + e := Events{ + fakeEvent{secretID: id1}, + fakeEvent{secretID: id2}, + fakeEvent{secretID: id3}, + } + + last, err := e.Last() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if last.GetOffset() != id3 { + t.Errorf("received '%v' expected '%v'", last.GetOffset(), id1) + } +} + +// methods that satisfy the common.Event interface +func (f fakeEvent) GetOffset() int64 { + if f.secretID > 0 { + return f.secretID + } + return f.Offset +} + +func (f fakeEvent) SetOffset(o int64) { + f.Offset = o +} + +func (f fakeEvent) IsEvent() bool { return false } -func (f fakeDataHandler) GetTime() time.Time { - return time.Now().Add(time.Hour * time.Duration(f.time)) +func (f fakeEvent) GetTime() time.Time { + return f.Base.Time } -func (f fakeDataHandler) Pair() currency.Pair { +func (f fakeEvent) Pair() currency.Pair { return currency.NewPair(currency.BTC, currency.USD) } -func (f fakeDataHandler) GetExchange() string { - return "fake" +func (f fakeEvent) GetExchange() string { + return f.Exchange } -func (f fakeDataHandler) GetInterval() kline.Interval { - return kline.Interval(time.Minute) +func (f fakeEvent) GetInterval() gctkline.Interval { + return gctkline.Interval(time.Minute) } -func (f fakeDataHandler) GetAssetType() asset.Item { - return asset.Spot +func (f fakeEvent) GetAssetType() asset.Item { + return f.AssetType } -func (f fakeDataHandler) GetReason() string { - return "fake" +func (f fakeEvent) GetReason() string { + return strings.Join(f.Reasons, ",") } -func (f fakeDataHandler) AppendReason(string) { +func (f fakeEvent) AppendReason(string) { } -func (f fakeDataHandler) GetClosePrice() decimal.Decimal { +func (f fakeEvent) GetClosePrice() decimal.Decimal { return decimal.Zero } -func (f fakeDataHandler) GetHighPrice() decimal.Decimal { +func (f fakeEvent) GetHighPrice() decimal.Decimal { return decimal.Zero } -func (f fakeDataHandler) GetLowPrice() decimal.Decimal { +func (f fakeEvent) GetLowPrice() decimal.Decimal { return decimal.Zero } -func (f fakeDataHandler) GetOpenPrice() decimal.Decimal { +func (f fakeEvent) GetOpenPrice() decimal.Decimal { return decimal.Zero } -func (f fakeDataHandler) GetUnderlyingPair() currency.Pair { +func (f fakeEvent) GetVolume() decimal.Decimal { + return decimal.Zero +} + +func (f fakeEvent) GetUnderlyingPair() currency.Pair { return f.Pair() } -func (f fakeDataHandler) AppendReasonf(s string, i ...interface{}) {} +func (f fakeEvent) AppendReasonf(string, ...interface{}) {} -func (f fakeDataHandler) GetBase() *event.Base { +func (f fakeEvent) GetBase() *event.Base { return &event.Base{} } -func (f fakeDataHandler) GetConcatReasons() string { +func (f fakeEvent) GetConcatReasons() string { return "" } -func (f fakeDataHandler) GetReasons() []string { +func (f fakeEvent) GetReasons() []string { return nil } + +func (f fakeHandler) Load() error { + return nil +} + +func (f fakeHandler) AppendStream(...Event) error { + return nil +} + +func (f fakeHandler) GetBase() Base { + return Base{} +} + +func (f fakeHandler) Next() (Event, error) { + return nil, nil +} + +func (f fakeHandler) GetStream() (Events, error) { + return nil, nil +} + +func (f fakeHandler) History() (Events, error) { + return nil, nil +} + +func (f fakeHandler) Latest() (Event, error) { + return nil, nil +} + +func (f fakeHandler) List() (Events, error) { + return nil, nil +} + +func (f fakeHandler) IsLastEvent() (bool, error) { + return false, nil +} + +func (f fakeHandler) Offset() (int64, error) { + return 0, nil +} + +func (f fakeHandler) StreamOpen() ([]decimal.Decimal, error) { + return nil, nil +} + +func (f fakeHandler) StreamHigh() ([]decimal.Decimal, error) { + return nil, nil +} + +func (f fakeHandler) StreamLow() ([]decimal.Decimal, error) { + return nil, nil +} + +func (f fakeHandler) StreamClose() ([]decimal.Decimal, error) { + return nil, nil +} + +func (f fakeHandler) StreamVol() ([]decimal.Decimal, error) { + return nil, nil +} + +func (f fakeHandler) HasDataAtTime(time.Time) (bool, error) { + return false, nil +} + +func (f fakeHandler) Reset() error { + return nil +} + +func (f fakeHandler) GetDetails() (string, asset.Item, currency.Pair, error) { + return "", asset.Empty, currency.EMPTYPAIR, nil +} diff --git a/backtester/data/data_types.go b/backtester/data/data_types.go index 2edc6813..4a3bac4f 100644 --- a/backtester/data/data_types.go +++ b/backtester/data/data_types.go @@ -2,6 +2,7 @@ package data import ( "errors" + "sync" "time" "github.com/shopspring/decimal" @@ -10,58 +11,87 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) -// ErrHandlerNotFound returned when a handler is not found for specified exchange, asset, pair -var ErrHandlerNotFound = errors.New("handler not found") +var ( + // ErrHandlerNotFound returned when a handler is not found for specified exchange, asset, pair + ErrHandlerNotFound = errors.New("handler not found") + // ErrInvalidEventSupplied returned when a bad event is supplied + ErrInvalidEventSupplied = errors.New("invalid event supplied") + // ErrEmptySlice is returned when the supplied slice is nil or empty + ErrEmptySlice = errors.New("empty slice") + // ErrEndOfData is returned when attempting to load the next offset when there is no more + ErrEndOfData = errors.New("no more data to retrieve") -// HandlerPerCurrency stores an event handler per exchange asset pair -type HandlerPerCurrency struct { - data map[string]map[asset.Item]map[currency.Pair]Handler + errNothingToAdd = errors.New("cannot append empty event to stream") + errMisMatchedEvent = errors.New("cannot add event to stream, does not match") +) + +// HandlerHolder stores an event handler per exchange asset pair +type HandlerHolder struct { + m sync.Mutex + data map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]Handler } -// Holder interface dictates what a data holder is expected to do +// Holder interface dictates what a Data holder is expected to do type Holder interface { - Setup() - SetDataForCurrency(string, asset.Item, currency.Pair, Handler) - GetAllData() map[string]map[asset.Item]map[currency.Pair]Handler - GetDataForCurrency(ev common.EventHandler) (Handler, error) - Reset() + SetDataForCurrency(string, asset.Item, currency.Pair, Handler) error + GetAllData() ([]Handler, error) + GetDataForCurrency(ev common.Event) (Handler, error) + Reset() error } // Base is the base implementation of some interface functions -// where further specific functions are implmented in DataFromKline +// where further specific functions are implemented in DataFromKline type Base struct { - latest common.DataEventHandler - stream []common.DataEventHandler - offset int + m sync.Mutex + latest Event + stream []Event + offset int64 + isLiveData bool } -// Handler interface for Loading and Streaming data +// Handler interface for Loading and Streaming Data type Handler interface { Loader Streamer - Reset() + GetDetails() (string, asset.Item, currency.Pair, error) + Reset() error } -// Loader interface for Loading data into backtest supported format +// Loader interface for Loading Data into backtest supported format type Loader interface { Load() error + AppendStream(s ...Event) error } -// Streamer interface handles loading, parsing, distributing BackTest data +// Streamer interface handles loading, parsing, distributing BackTest Data type Streamer interface { - Next() common.DataEventHandler - GetStream() []common.DataEventHandler - History() []common.DataEventHandler - Latest() common.DataEventHandler - List() []common.DataEventHandler - IsLastEvent() bool - Offset() int + Next() (Event, error) + GetStream() (Events, error) + History() (Events, error) + Latest() (Event, error) + List() (Events, error) + IsLastEvent() (bool, error) + Offset() (int64, error) - StreamOpen() []decimal.Decimal - StreamHigh() []decimal.Decimal - StreamLow() []decimal.Decimal - StreamClose() []decimal.Decimal - StreamVol() []decimal.Decimal + StreamOpen() ([]decimal.Decimal, error) + StreamHigh() ([]decimal.Decimal, error) + StreamLow() ([]decimal.Decimal, error) + StreamClose() ([]decimal.Decimal, error) + StreamVol() ([]decimal.Decimal, error) - HasDataAtTime(time.Time) bool + HasDataAtTime(time.Time) (bool, error) } + +// Event interface used for loading and interacting with Data +type Event interface { + common.Event + GetUnderlyingPair() currency.Pair + GetClosePrice() decimal.Decimal + GetHighPrice() decimal.Decimal + GetLowPrice() decimal.Decimal + GetOpenPrice() decimal.Decimal + GetVolume() decimal.Decimal +} + +// Events allows for some common functions on a slice of events +type Events []Event diff --git a/backtester/data/kline/csv/csv.go b/backtester/data/kline/csv/csv.go index 38043a54..244e808d 100644 --- a/backtester/data/kline/csv/csv.go +++ b/backtester/data/kline/csv/csv.go @@ -11,10 +11,10 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/backtester/common" - gctkline "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" + "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/kline" + gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" "github.com/thrasher-corp/gocryptotrader/log" @@ -23,8 +23,8 @@ import ( var errNoUSDData = errors.New("could not retrieve USD CSV candle data") // LoadData is a basic csv reader which converts the found CSV file into a kline item -func LoadData(dataType int64, filepath, exchangeName string, interval time.Duration, fPair currency.Pair, a asset.Item, isUSDTrackingPair bool) (*gctkline.DataFromKline, error) { - resp := &gctkline.DataFromKline{} +func LoadData(dataType int64, filepath, exchangeName string, interval time.Duration, fPair currency.Pair, a asset.Item, isUSDTrackingPair bool) (*kline.DataFromKline, error) { + resp := kline.NewDataFromKline() csvFile, err := os.Open(filepath) if err != nil { return nil, err @@ -41,11 +41,11 @@ func LoadData(dataType int64, filepath, exchangeName string, interval time.Durat switch dataType { case common.DataCandle: - candles := kline.Item{ + candles := gctkline.Item{ Exchange: exchangeName, Pair: fPair, Asset: a, - Interval: kline.Interval(interval), + Interval: gctkline.Interval(interval), } for { @@ -57,7 +57,7 @@ func LoadData(dataType int64, filepath, exchangeName string, interval time.Durat return nil, fmt.Errorf("could not read csv data for %v %v %v, %v", exchangeName, a, fPair, errCSV) } - candle := kline.Candle{} + candle := gctkline.Candle{} v, errParse := strconv.ParseInt(row[0], 10, 32) if errParse != nil { return nil, errParse @@ -146,7 +146,7 @@ func LoadData(dataType int64, filepath, exchangeName string, interval time.Durat trades = append(trades, t) } - resp.Item, err = trade.ConvertTradesToCandles(kline.Interval(interval), trades...) + resp.Item, err = trade.ConvertTradesToCandles(gctkline.Interval(interval), trades...) if err != nil { return nil, fmt.Errorf("could not read csv trade data for %v %v %v, %v", exchangeName, a, fPair, err) } @@ -159,7 +159,7 @@ func LoadData(dataType int64, filepath, exchangeName string, interval time.Durat resp.Item.Exchange = strings.ToLower(exchangeName) resp.Item.Pair = fPair resp.Item.Asset = a - resp.Item.Interval = kline.Interval(interval) + resp.Item.Interval = gctkline.Interval(interval) return resp, nil } diff --git a/backtester/data/kline/csv/csv_test.go b/backtester/data/kline/csv/csv_test.go index 6ad6e467..4b8f2c7f 100644 --- a/backtester/data/kline/csv/csv_test.go +++ b/backtester/data/kline/csv/csv_test.go @@ -25,8 +25,8 @@ func TestLoadDataCandles(t *testing.T) { p, a, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -42,8 +42,8 @@ func TestLoadDataTrades(t *testing.T) { p, a, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } diff --git a/backtester/data/kline/database/database.go b/backtester/data/kline/database/database.go index 0a87d497..cd5befe0 100644 --- a/backtester/data/kline/database/database.go +++ b/backtester/data/kline/database/database.go @@ -19,7 +19,7 @@ var errNoUSDData = errors.New("could not retrieve USD database candle data") // LoadData retrieves data from an existing database using GoCryptoTrader's database handling implementation func LoadData(startDate, endDate time.Time, interval time.Duration, exchangeName string, dataType int64, fPair currency.Pair, a asset.Item, isUSDTrackingPair bool) (*kline.DataFromKline, error) { - resp := &kline.DataFromKline{} + resp := kline.NewDataFromKline() switch dataType { case common.DataCandle: klineItem, err := getCandleDatabaseData( diff --git a/backtester/data/kline/database/database_test.go b/backtester/data/kline/database/database_test.go index 8d513271..bfae6150 100644 --- a/backtester/data/kline/database/database_test.go +++ b/backtester/data/kline/database/database_test.go @@ -85,8 +85,8 @@ func TestLoadDataCandles(t *testing.T) { database.MigrationDir = filepath.Join("..", "..", "..", "..", "database", "migrations") testhelpers.MigrationDir = filepath.Join("..", "..", "..", "..", "database", "migrations") conn, err := testhelpers.ConnectToDatabase(&dbConfg) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } err = exchangeDB.InsertMany([]exchangeDB.Details{{Name: testExchange}}) @@ -115,13 +115,13 @@ func TestLoadDataCandles(t *testing.T) { }, } _, err = gctkline.StoreInDatabase(data, true) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } _, err = LoadData(dStart, dEnd, gctkline.FifteenMin.Duration(), exch, common.DataCandle, p, a, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if err = conn.SQL.Close(); err != nil { @@ -160,8 +160,8 @@ func TestLoadDataTrades(t *testing.T) { database.MigrationDir = filepath.Join("..", "..", "..", "..", "database", "migrations") testhelpers.MigrationDir = filepath.Join("..", "..", "..", "..", "database", "migrations") conn, err := testhelpers.ConnectToDatabase(&dbConfg) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } err = exchangeDB.InsertMany([]exchangeDB.Details{{Name: testExchange}}) @@ -183,13 +183,13 @@ func TestLoadDataTrades(t *testing.T) { Side: gctorder.Buy.String(), Timestamp: dInsert, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } _, err = LoadData(dStart, dEnd, gctkline.FifteenMin.Duration(), exch, common.DataTrade, p, a, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if err = conn.SQL.Close(); err != nil { diff --git a/backtester/data/kline/kline.go b/backtester/data/kline/kline.go index de63db76..913144ba 100644 --- a/backtester/data/kline/kline.go +++ b/backtester/data/kline/kline.go @@ -1,33 +1,57 @@ package kline import ( + "fmt" "time" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" - "github.com/thrasher-corp/gocryptotrader/log" ) +// NewDataFromKline returns a new struct +func NewDataFromKline() *DataFromKline { + return &DataFromKline{ + Base: &data.Base{}, + } +} + // HasDataAtTime verifies checks the underlying range data // To determine whether there is any candle data present at the time provided -func (d *DataFromKline) HasDataAtTime(t time.Time) bool { - if d.RangeHolder == nil { - return false +func (d *DataFromKline) HasDataAtTime(t time.Time) (bool, error) { + isLive, err := d.Base.IsLive() + if err != nil { + return false, err } - return d.RangeHolder.HasDataAtDate(t) + if isLive { + var s []data.Event + s, err = d.GetStream() + if err != nil { + return false, err + } + for i := range s { + if s[i].GetTime().Equal(t) { + return true, nil + } + } + return false, nil + } + if d.RangeHolder == nil { + return false, fmt.Errorf("%w RangeHolder", gctcommon.ErrNilPointer) + } + return d.RangeHolder.HasDataAtDate(t), nil } // Load sets the candle data to the stream for processing func (d *DataFromKline) Load() error { - d.addedTimes = make(map[int64]bool) if len(d.Item.Candles) == 0 { return errNoCandleData } - klineData := make([]common.DataEventHandler, len(d.Item.Candles)) + klineData := make([]data.Event, len(d.Item.Candles)) for i := range d.Item.Candles { newKline := &kline.Kline{ Base: &event.Base{ @@ -47,135 +71,138 @@ func (d *DataFromKline) Load() error { ValidationIssues: d.Item.Candles[i].ValidationIssues, } klineData[i] = newKline - d.addedTimes[d.Item.Candles[i].Time.UTC().UnixNano()] = true } - d.SetStream(klineData) - d.SortStream() - return nil + return d.SetStream(klineData) } // AppendResults adds a candle item to the data stream and sorts it to ensure it is all in order -func (d *DataFromKline) AppendResults(ki *gctkline.Item) { - if d.addedTimes == nil { - d.addedTimes = make(map[int64]bool) +func (d *DataFromKline) AppendResults(ki *gctkline.Item) error { + if ki == nil { + return fmt.Errorf("%w kline item", gctcommon.ErrNilPointer) + } + err := d.Item.EqualSource(ki) + if err != nil { + return err } - var gctCandles []gctkline.Candle - for i := range ki.Candles { - if _, ok := d.addedTimes[ki.Candles[i].Time.UnixNano()]; !ok { - gctCandles = append(gctCandles, ki.Candles[i]) - d.addedTimes[ki.Candles[i].Time.UnixNano()] = true + stream, err := d.Base.GetStream() + if err != nil { + return err + } +candleLoop: + for x := range ki.Candles { + for y := range stream { + if stream[y].GetTime().Equal(ki.Candles[x].Time) { + continue candleLoop + } } + gctCandles = append(gctCandles, ki.Candles[x]) + } + if len(gctCandles) == 0 { + return nil + } + klineData := make([]data.Event, len(gctCandles)) + for i := range gctCandles { + d.Item.Candles = append(d.Item.Candles, gctCandles[i]) + newKline := &kline.Kline{ + Base: &event.Base{ + Exchange: d.Item.Exchange, + Interval: d.Item.Interval, + CurrencyPair: d.Item.Pair, + AssetType: d.Item.Asset, + UnderlyingPair: d.Item.UnderlyingPair, + Time: gctCandles[i].Time.UTC(), + }, + Open: decimal.NewFromFloat(gctCandles[i].Open), + High: decimal.NewFromFloat(gctCandles[i].High), + Low: decimal.NewFromFloat(gctCandles[i].Low), + Close: decimal.NewFromFloat(gctCandles[i].Close), + Volume: decimal.NewFromFloat(gctCandles[i].Volume), + } + klineData[i] = newKline + } + err = d.AppendStream(klineData...) + if err != nil { + return err } - klineData := make([]common.DataEventHandler, len(gctCandles)) - candleTimes := make([]time.Time, len(gctCandles)) - for i := range gctCandles { - klineData[i] = &kline.Kline{ - Base: &event.Base{ - Offset: int64(i + 1), - Exchange: ki.Exchange, - Time: gctCandles[i].Time, - Interval: ki.Interval, - CurrencyPair: ki.Pair, - AssetType: ki.Asset, - }, - Open: decimal.NewFromFloat(gctCandles[i].Open), - High: decimal.NewFromFloat(gctCandles[i].High), - Low: decimal.NewFromFloat(gctCandles[i].Low), - Close: decimal.NewFromFloat(gctCandles[i].Close), - Volume: decimal.NewFromFloat(gctCandles[i].Volume), - ValidationIssues: gctCandles[i].ValidationIssues, - } - candleTimes[i] = gctCandles[i].Time + d.Item.RemoveDuplicateCandlesByTime() + d.Item.SortCandlesByTimestamp(false) + if d.RangeHolder != nil { + // offline data check when there is a known range + // live data does not need this + d.RangeHolder.SetHasDataFromCandles(d.Item.Candles) } - for i := range d.RangeHolder.Ranges { - for j := range d.RangeHolder.Ranges[i].Intervals { - d.RangeHolder.Ranges[i].Intervals[j].HasData = true - } - } - log.Debugf(common.Data, "Appending %v candle intervals: %v", len(gctCandles), candleTimes) - d.AppendStream(klineData...) - d.SortStream() + return nil } // StreamOpen returns all Open prices from the beginning until the current iteration -func (d *DataFromKline) StreamOpen() []decimal.Decimal { - s := d.GetStream() - o := d.Offset() - - ret := make([]decimal.Decimal, o) - for x := range s[:o] { - if val, ok := s[x].(*kline.Kline); ok { - ret[x] = val.Open - } else { - log.Errorf(common.Data, "Incorrect data loaded into stream") - } +func (d *DataFromKline) StreamOpen() ([]decimal.Decimal, error) { + s, err := d.History() + if err != nil { + return nil, err } - return ret + + ret := make([]decimal.Decimal, len(s)) + for x := range s { + ret[x] = s[x].GetOpenPrice() + } + return ret, nil } // StreamHigh returns all High prices from the beginning until the current iteration -func (d *DataFromKline) StreamHigh() []decimal.Decimal { - s := d.GetStream() - o := d.Offset() - - ret := make([]decimal.Decimal, o) - for x := range s[:o] { - if val, ok := s[x].(*kline.Kline); ok { - ret[x] = val.High - } else { - log.Errorf(common.Data, "Incorrect data loaded into stream") - } +func (d *DataFromKline) StreamHigh() ([]decimal.Decimal, error) { + s, err := d.History() + if err != nil { + return nil, err } - return ret + + ret := make([]decimal.Decimal, len(s)) + for x := range s { + ret[x] = s[x].GetHighPrice() + } + return ret, nil } // StreamLow returns all Low prices from the beginning until the current iteration -func (d *DataFromKline) StreamLow() []decimal.Decimal { - s := d.GetStream() - o := d.Offset() - - ret := make([]decimal.Decimal, o) - for x := range s[:o] { - if val, ok := s[x].(*kline.Kline); ok { - ret[x] = val.Low - } else { - log.Errorf(common.Data, "Incorrect data loaded into stream") - } +func (d *DataFromKline) StreamLow() ([]decimal.Decimal, error) { + s, err := d.History() + if err != nil { + return nil, err } - return ret + + ret := make([]decimal.Decimal, len(s)) + for x := range s { + ret[x] = s[x].GetLowPrice() + } + return ret, nil } // StreamClose returns all Close prices from the beginning until the current iteration -func (d *DataFromKline) StreamClose() []decimal.Decimal { - s := d.GetStream() - o := d.Offset() - - ret := make([]decimal.Decimal, o) - for x := range s[:o] { - if val, ok := s[x].(*kline.Kline); ok { - ret[x] = val.Close - } else { - log.Errorf(common.Data, "Incorrect data loaded into stream") - } +func (d *DataFromKline) StreamClose() ([]decimal.Decimal, error) { + s, err := d.History() + if err != nil { + return nil, err } - return ret + + ret := make([]decimal.Decimal, len(s)) + for x := range s { + ret[x] = s[x].GetClosePrice() + } + return ret, nil } // StreamVol returns all Volume prices from the beginning until the current iteration -func (d *DataFromKline) StreamVol() []decimal.Decimal { - s := d.GetStream() - o := d.Offset() - - ret := make([]decimal.Decimal, o) - for x := range s[:o] { - if val, ok := s[x].(*kline.Kline); ok { - ret[x] = val.Volume - } else { - log.Errorf(common.Data, "Incorrect data loaded into stream") - } +func (d *DataFromKline) StreamVol() ([]decimal.Decimal, error) { + s, err := d.History() + if err != nil { + return nil, err } - return ret + + ret := make([]decimal.Decimal, len(s)) + for x := range s { + ret[x] = s[x].GetVolume() + } + return ret, nil } diff --git a/backtester/data/kline/kline_test.go b/backtester/data/kline/kline_test.go index e6fe0b32..f370d8c4 100644 --- a/backtester/data/kline/kline_test.go +++ b/backtester/data/kline/kline_test.go @@ -6,9 +6,10 @@ import ( "time" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" @@ -24,7 +25,9 @@ func TestLoad(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) tt := time.Now() - d := DataFromKline{} + d := DataFromKline{ + Base: &data.Base{}, + } err := d.Load() if !errors.Is(err, errNoCandleData) { t.Errorf("received: %v, expected: %v", err, errNoCandleData) @@ -46,8 +49,8 @@ func TestLoad(t *testing.T) { }, } err = d.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -59,8 +62,22 @@ func TestHasDataAtTime(t *testing.T) { exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := DataFromKline{} - has := d.HasDataAtTime(time.Now()) + d := DataFromKline{ + Base: &data.Base{}, + } + has, err := d.HasDataAtTime(time.Now()) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) + } + if has { + t.Error("expected false") + } + + d.RangeHolder = &gctkline.IntervalRangeHolder{} + has, err = d.HasDataAtTime(time.Now()) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if has { t.Error("expected false") } @@ -81,22 +98,46 @@ func TestHasDataAtTime(t *testing.T) { }, }, } - if err := d.Load(); err != nil { + if err = d.Load(); err != nil { t.Error(err) } - has = d.HasDataAtTime(dInsert) + has, err = d.HasDataAtTime(dInsert) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if has { t.Error("expected false") } ranger, err := gctkline.CalculateCandleDateRanges(dStart, dEnd, gctkline.OneDay, 100000) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } d.RangeHolder = ranger d.RangeHolder.SetHasDataFromCandles(d.Item.Candles) - has = d.HasDataAtTime(dInsert) + has, err = d.HasDataAtTime(dInsert) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if !has { + t.Error("expected true") + } + err = d.SetLive(true) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + has, err = d.HasDataAtTime(time.Time{}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if has { + t.Error("expected false") + } + has, err = d.HasDataAtTime(dInsert) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if !has { t.Error("expected true") } @@ -104,16 +145,18 @@ func TestHasDataAtTime(t *testing.T) { func TestAppend(t *testing.T) { t.Parallel() - exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) d := DataFromKline{ + Base: &data.Base{}, + Item: gctkline.Item{ + Exchange: testExchange, + Asset: a, + Pair: p, + }, RangeHolder: &gctkline.IntervalRangeHolder{}, } item := gctkline.Item{ - Exchange: exch, - Pair: p, - Asset: a, Interval: gctkline.OneDay, Candles: []gctkline.Candle{ { @@ -126,7 +169,28 @@ func TestAppend(t *testing.T) { }, }, } - d.AppendResults(&item) + err := d.AppendResults(&item) + if !errors.Is(err, gctkline.ErrItemNotEqual) { + t.Errorf("received: %v, expected: %v", err, gctkline.ErrItemNotEqual) + } + + item.Exchange = testExchange + item.Pair = p + item.Asset = a + err = d.AppendResults(&item) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + err = d.AppendResults(&item) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + err = d.AppendResults(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) + } } func TestStreamOpen(t *testing.T) { @@ -134,11 +198,17 @@ func TestStreamOpen(t *testing.T) { exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := DataFromKline{} - if bad := d.StreamOpen(); len(bad) > 0 { + d := DataFromKline{ + Base: &data.Base{}, + } + bad, err := d.StreamOpen() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(bad) > 0 { t.Error("expected no stream") } - d.SetStream([]common.DataEventHandler{ + err = d.SetStream([]data.Event{ &kline.Kline{ Base: &event.Base{ Exchange: exch, @@ -154,8 +224,18 @@ func TestStreamOpen(t *testing.T) { Volume: elite, }, }) - d.Next() - if open := d.StreamOpen(); len(open) == 0 { + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + open, err := d.StreamOpen() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(open) == 0 { t.Error("expected open") } } @@ -165,11 +245,17 @@ func TestStreamVolume(t *testing.T) { exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := DataFromKline{} - if bad := d.StreamVol(); len(bad) > 0 { + d := DataFromKline{ + Base: &data.Base{}, + } + bad, err := d.StreamVol() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(bad) > 0 { t.Error("expected no stream") } - d.SetStream([]common.DataEventHandler{ + err = d.SetStream([]data.Event{ &kline.Kline{ Base: &event.Base{ Exchange: exch, @@ -185,8 +271,18 @@ func TestStreamVolume(t *testing.T) { Volume: elite, }, }) - d.Next() - if open := d.StreamVol(); len(open) == 0 { + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + vol, err := d.StreamVol() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(vol) == 0 { t.Error("expected volume") } } @@ -196,11 +292,18 @@ func TestStreamClose(t *testing.T) { exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := DataFromKline{} - if bad := d.StreamClose(); len(bad) > 0 { + d := DataFromKline{ + Base: &data.Base{}, + } + bad, err := d.StreamClose() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(bad) > 0 { t.Error("expected no stream") } - d.SetStream([]common.DataEventHandler{ + + err = d.SetStream([]data.Event{ &kline.Kline{ Base: &event.Base{ Exchange: exch, @@ -216,8 +319,18 @@ func TestStreamClose(t *testing.T) { Volume: elite, }, }) - d.Next() - if open := d.StreamClose(); len(open) == 0 { + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + cl, err := d.StreamClose() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(cl) == 0 { t.Error("expected close") } } @@ -227,11 +340,18 @@ func TestStreamHigh(t *testing.T) { exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := DataFromKline{} - if bad := d.StreamHigh(); len(bad) > 0 { + d := DataFromKline{ + Base: &data.Base{}, + } + bad, err := d.StreamHigh() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(bad) > 0 { t.Error("expected no stream") } - d.SetStream([]common.DataEventHandler{ + + err = d.SetStream([]data.Event{ &kline.Kline{ Base: &event.Base{ Exchange: exch, @@ -247,8 +367,18 @@ func TestStreamHigh(t *testing.T) { Volume: elite, }, }) - d.Next() - if open := d.StreamHigh(); len(open) == 0 { + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + high, err := d.StreamHigh() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(high) == 0 { t.Error("expected high") } } @@ -259,12 +389,18 @@ func TestStreamLow(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) d := DataFromKline{ + Base: &data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{}, } - if bad := d.StreamLow(); len(bad) > 0 { + bad, err := d.StreamLow() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(bad) > 0 { t.Error("expected no stream") } - d.SetStream([]common.DataEventHandler{ + + err = d.SetStream([]data.Event{ &kline.Kline{ Base: &event.Base{ Exchange: exch, @@ -280,8 +416,19 @@ func TestStreamLow(t *testing.T) { Volume: elite, }, }) - d.Next() - if open := d.StreamLow(); len(open) == 0 { + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + low, err := d.StreamLow() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if len(low) == 0 { t.Error("expected low") } } diff --git a/backtester/data/kline/kline_types.go b/backtester/data/kline/kline_types.go index 0c466ace..93e5e046 100644 --- a/backtester/data/kline/kline_types.go +++ b/backtester/data/kline/kline_types.go @@ -12,8 +12,7 @@ var errNoCandleData = errors.New("no candle data provided") // DataFromKline is a struct which implements the data.Streamer interface // It holds candle data for a specified range with helper functions type DataFromKline struct { - data.Base - addedTimes map[int64]bool + *data.Base Item gctkline.Item RangeHolder *gctkline.IntervalRangeHolder } diff --git a/backtester/data/kline/live/README.md b/backtester/data/kline/live/README.md index 6b436ed0..a7f855c8 100644 --- a/backtester/data/kline/live/README.md +++ b/backtester/data/kline/live/README.md @@ -23,7 +23,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader This package will retrieve data for the backtester via continuous requests to live endpoints ## Important notice -Live trading is not fully implemented and you should never consider setting `RealOrders` to `true` in a config. *Past performance is no guarantee of future results* +Its incredibly risky to enable `real-orders`. *Past performance is no guarantee of future results* ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/backtester/data/kline/live/live.go b/backtester/data/kline/live/live.go index 01a90164..7120311c 100644 --- a/backtester/data/kline/live/live.go +++ b/backtester/data/kline/live/live.go @@ -7,35 +7,54 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/backtester/common" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" ) // LoadData retrieves data from a GoCryptoTrader exchange wrapper which calls the exchange's API for the latest interval -func LoadData(ctx context.Context, exch exchange.IBotExchange, dataType int64, interval time.Duration, fPair currency.Pair, a asset.Item) (*kline.Item, error) { +// note: this is not in a state to utilise with realOrders = true +func LoadData(ctx context.Context, timeToRetrieve time.Time, exch exchange.IBotExchange, dataType int64, interval time.Duration, currencyPair, underlyingPair currency.Pair, a asset.Item, verbose bool) (*kline.Item, error) { + if exch == nil { + return nil, fmt.Errorf("%w IBotExchange", gctcommon.ErrNilPointer) + } var candles kline.Item var err error + if verbose { + ctx = request.WithVerbose(ctx) + } + var startTime, endTime time.Time + exchBase := exch.GetBase() + pFmt, err := exchBase.FormatExchangeCurrency(currencyPair, a) + if err != nil { + return nil, err + } + startTime = timeToRetrieve.Truncate(interval).Add(-interval) + endTime = timeToRetrieve.Truncate(interval).Add(-1) switch dataType { case common.DataCandle: candles, err = exch.GetHistoricCandles(ctx, - fPair, + pFmt, a, - time.Now().Add(-interval*2), // multiplied by 2 to ensure the latest candle is always included - time.Now(), - kline.Interval(interval)) + startTime, + endTime, + kline.Interval(interval), + ) if err != nil { - return nil, fmt.Errorf("could not retrieve live candle data for %v %v %v, %v", exch.GetName(), a, fPair, err) + return nil, fmt.Errorf("could not retrieve live candle data for %v %v %v, %v", exch.GetName(), a, currencyPair, err) } case common.DataTrade: var trades []trade.Data trades, err = exch.GetHistoricTrades(ctx, - fPair, + pFmt, a, - time.Now().Add(-interval*2), // multiplied by 2 to ensure the latest candle is always included - time.Now()) + startTime, + endTime, + ) if err != nil { return nil, err } @@ -47,22 +66,24 @@ func LoadData(ctx context.Context, exch exchange.IBotExchange, dataType int64, i base := exch.GetBase() if len(candles.Candles) <= 1 && base.GetSupportedFeatures().RESTCapabilities.TradeHistory { trades, err = exch.GetHistoricTrades(ctx, - fPair, + pFmt, a, - time.Now().Add(-interval), - time.Now()) + startTime, + endTime, + ) if err != nil { - return nil, fmt.Errorf("could not retrieve live trade data for %v %v %v, %v", exch.GetName(), a, fPair, err) + return nil, fmt.Errorf("could not retrieve live trade data for %v %v %v, %v", exch.GetName(), a, currencyPair, err) } candles, err = trade.ConvertTradesToCandles(kline.Interval(interval), trades...) if err != nil { - return nil, fmt.Errorf("could not convert live trade data to candles for %v %v %v, %v", exch.GetName(), a, fPair, err) + return nil, fmt.Errorf("could not convert live trade data to candles for %v %v %v, %v", exch.GetName(), a, currencyPair, err) } } default: - return nil, fmt.Errorf("could not retrieve live data for %v %v %v, %w", exch.GetName(), a, fPair, common.ErrInvalidDataType) + return nil, fmt.Errorf("could not retrieve live data for %v %v %v, %w: '%v'", exch.GetName(), a, currencyPair, common.ErrInvalidDataType, dataType) } candles.Exchange = strings.ToLower(exch.GetName()) + candles.UnderlyingPair = underlyingPair return &candles, nil } diff --git a/backtester/data/kline/live/live_test.go b/backtester/data/kline/live/live_test.go index c3e7fdb3..fcf76c35 100644 --- a/backtester/data/kline/live/live_test.go +++ b/backtester/data/kline/live/live_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "testing" + "time" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/common/convert" @@ -18,7 +19,7 @@ const testExchange = "binance" func TestLoadCandles(t *testing.T) { t.Parallel() interval := gctkline.OneHour - cp1 := currency.NewPair(currency.BTC, currency.USDT) + cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Spot em := engine.SetupExchangeManager() exch, err := em.NewExchangeByName(testExchange) @@ -30,22 +31,21 @@ func TestLoadCandles(t *testing.T) { exch.SetDefaults() b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ - Available: currency.Pairs{cp1}, - Enabled: currency.Pairs{cp1}, + Available: currency.Pairs{cp}, + Enabled: currency.Pairs{cp}, AssetEnabled: convert.BoolPtr(true), RequestFormat: pFormat, ConfigFormat: pFormat, } var data *gctkline.Item - data, err = LoadData(context.Background(), - exch, common.DataCandle, interval.Duration(), cp1, a) + data, err = LoadData(context.Background(), time.Now(), exch, common.DataCandle, interval.Duration(), cp, currency.EMPTYPAIR, a, true) if err != nil { t.Fatal(err) } if len(data.Candles) == 0 { t.Error("expected candles") } - _, err = LoadData(context.Background(), exch, -1, interval.Duration(), cp1, a) + _, err = LoadData(context.Background(), time.Now(), exch, -1, interval.Duration(), cp, currency.EMPTYPAIR, a, true) if !errors.Is(err, common.ErrInvalidDataType) { t.Errorf("received: %v, expected: %v", err, common.ErrInvalidDataType) } @@ -54,7 +54,7 @@ func TestLoadCandles(t *testing.T) { func TestLoadTrades(t *testing.T) { t.Parallel() interval := gctkline.OneMin - cp1 := currency.NewPair(currency.BTC, currency.USDT) + cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Spot em := engine.SetupExchangeManager() exch, err := em.NewExchangeByName(testExchange) @@ -66,14 +66,14 @@ func TestLoadTrades(t *testing.T) { exch.SetDefaults() b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ - Available: currency.Pairs{cp1}, - Enabled: currency.Pairs{cp1}, + Available: currency.Pairs{cp}, + Enabled: currency.Pairs{cp}, AssetEnabled: convert.BoolPtr(true), RequestFormat: pFormat, ConfigFormat: pFormat, } var data *gctkline.Item - data, err = LoadData(context.Background(), exch, common.DataTrade, interval.Duration(), cp1, a) + data, err = LoadData(context.Background(), time.Now(), exch, common.DataTrade, interval.Duration(), cp, currency.EMPTYPAIR, a, true) if err != nil { t.Fatal(err) } diff --git a/backtester/engine/backtest.go b/backtester/engine/backtest.go index 1b31e628..a40d00f1 100644 --- a/backtester/engine/backtest.go +++ b/backtester/engine/backtest.go @@ -3,20 +3,17 @@ package engine import ( "errors" "fmt" - "sync" "time" "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/data" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" "github.com/thrasher-corp/gocryptotrader/backtester/funding" @@ -28,31 +25,100 @@ import ( "github.com/thrasher-corp/gocryptotrader/log" ) -// New returns a new BackTest instance -func New() (*BackTest, error) { - bt := &BackTest{ - shutdown: make(chan struct{}), - Datas: &data.HandlerPerCurrency{}, - EventQueue: &eventholder.Holder{}, - } - err := bt.SetupMetaData() - if err != nil { - return nil, err - } - return bt, nil -} - // Reset BackTest values to default -func (bt *BackTest) Reset() { - bt.EventQueue.Reset() - bt.Datas.Reset() - bt.Portfolio.Reset() - bt.Statistic.Reset() - bt.Exchange.Reset() - bt.Funding.Reset() +func (bt *BackTest) Reset() error { + if bt == nil { + return gctcommon.ErrNilPointer + } + var err error + if bt.orderManager != nil { + err = bt.orderManager.Stop() + if err != nil { + return err + } + } + if bt.databaseManager != nil { + err = bt.databaseManager.Stop() + if err != nil { + return err + } + } + err = bt.EventQueue.Reset() + if err != nil { + return err + } + err = bt.DataHolder.Reset() + if err != nil { + return err + } + err = bt.Portfolio.Reset() + if err != nil { + return err + } + err = bt.Statistic.Reset() + if err != nil { + return err + } + err = bt.Exchange.Reset() + if err != nil { + return err + } + err = bt.Funding.Reset() + if err != nil { + return err + } bt.exchangeManager = nil bt.orderManager = nil bt.databaseManager = nil + return nil +} + +// RunLive is a proof of concept function that does not yet support multi currency usage +// It tasks by constantly checking for new live datas and running through the list of events +// once new data is processed. It will run until application close event has been received +func (bt *BackTest) RunLive() error { + if bt.LiveDataHandler == nil { + return errLiveOnly + } + var err error + if bt.LiveDataHandler.IsRealOrders() { + err = bt.LiveDataHandler.UpdateFunding(false) + if err != nil { + return err + } + } + err = bt.LiveDataHandler.Start() + if err != nil { + return err + } + bt.wg.Add(1) + go func() { + err = bt.liveCheck() + if err != nil { + log.Error(common.LiveStrategy, err) + } + bt.wg.Done() + }() + + return nil +} + +func (bt *BackTest) liveCheck() error { + for { + select { + case <-bt.shutdown: + return bt.LiveDataHandler.Stop() + case <-bt.LiveDataHandler.HasShutdownFromError(): + return bt.Stop() + case <-bt.LiveDataHandler.HasShutdown(): + return nil + case <-bt.LiveDataHandler.Updated(): + err := bt.Run() + if err != nil { + return err + } + } + } } // ExecuteStrategy executes the strategy using the provided configs @@ -67,96 +133,115 @@ func (bt *BackTest) ExecuteStrategy(waitForOfflineCompletion bool) error { } if !bt.MetaData.Closed && !bt.MetaData.DateStarted.IsZero() { bt.m.Unlock() - return fmt.Errorf("%w %v %v", errRunIsRunning, bt.MetaData.ID, bt.MetaData.Strategy) + return fmt.Errorf("%w %v %v", errTaskIsRunning, bt.MetaData.ID, bt.MetaData.Strategy) } if bt.MetaData.Closed { bt.m.Unlock() return fmt.Errorf("%w %v %v", errAlreadyRan, bt.MetaData.ID, bt.MetaData.Strategy) } + if waitForOfflineCompletion && bt.MetaData.LiveTesting { + bt.m.Unlock() + return fmt.Errorf("%w cannot wait for a live task to finish", errCannotHandleRequest) + } bt.MetaData.DateStarted = time.Now() liveTesting := bt.MetaData.LiveTesting bt.m.Unlock() - var wg sync.WaitGroup - if waitForOfflineCompletion { - wg.Add(1) + + var err error + switch { + case waitForOfflineCompletion && !liveTesting: + err = bt.Run() + if err != nil { + log.Error(common.Backtester, err) + } + return bt.Stop() + case !waitForOfflineCompletion && liveTesting: + return bt.RunLive() + case !waitForOfflineCompletion && !liveTesting: + go func() { + err = bt.Run() + if err != nil { + log.Error(common.Backtester, err) + } + err = bt.Stop() + if err != nil { + log.Error(common.Backtester, err) + } + }() } - go func() { - if waitForOfflineCompletion { - defer wg.Done() - } - if liveTesting { - if waitForOfflineCompletion { - log.Errorf(common.Backtester, "%v cannot wait for completion of a live test", errCannotHandleRequest) - return - } - err := bt.RunLive() - if err != nil { - log.Error(log.Global, err) - } - } else { - bt.Run() - close(bt.shutdown) - bt.m.Lock() - bt.MetaData.Closed = true - bt.MetaData.DateEnded = time.Now() - bt.m.Unlock() - err := bt.Statistic.CalculateAllResults() - if err != nil { - log.Error(log.Global, err) - return - } - err = bt.Reports.GenerateReport() - if err != nil { - log.Error(log.Global, err) - } - } - }() - wg.Wait() return nil } // Run will iterate over loaded data events // save them and then handle the event based on its type -func (bt *BackTest) Run() { +func (bt *BackTest) Run() error { + // doubleNil allows the run function to exit if no new data is detected on a live run + var doubleNil bool if bt.MetaData.DateLoaded.IsZero() { - return + return errNotSetup } - log.Info(common.Backtester, "Running backtester against pre-defined data") -dataLoadingIssue: for ev := bt.EventQueue.NextEvent(); ; ev = bt.EventQueue.NextEvent() { if ev == nil { - dataHandlerMap := bt.Datas.GetAllData() - var hasProcessedData bool - for exchangeName, exchangeMap := range dataHandlerMap { - for assetItem, assetMap := range exchangeMap { - for currencyPair, dataHandler := range assetMap { - d := dataHandler.Next() - if d == nil { - if !bt.hasHandledEvent { - log.Errorf(common.Backtester, "Unable to perform `Next` for %v %v %v", exchangeName, assetItem, currencyPair) - } - break dataLoadingIssue - } - if bt.Strategy.UsingSimultaneousProcessing() && hasProcessedData { - // only append one event, as simultaneous processing - // will retrieve all relevant events to process under - // processSimultaneousDataEvents() - continue - } - bt.EventQueue.AppendEvent(d) - hasProcessedData = true + if bt.hasShutdown { + return nil + } + if doubleNil { + if bt.verbose { + log.Info(common.Backtester, "No new data on second check") + } + return nil + } + doubleNil = true + dataHandlers, err := bt.DataHolder.GetAllData() + if err != nil { + return err + } + for i := range dataHandlers { + var e data.Event + e, err = dataHandlers[i].Next() + if err != nil { + if errors.Is(err, data.ErrEndOfData) { + return nil } + return err + } + if e == nil { + if !bt.hasProcessedAnEvent && bt.LiveDataHandler == nil { + var ( + exch string + assetItem asset.Item + cp currency.Pair + ) + exch, assetItem, cp, err = dataHandlers[i].GetDetails() + if err != nil { + return err + } + log.Errorf(common.Backtester, "Unable to perform `Next` for %v %v %v", exch, assetItem, cp) + } + return nil + } + o := e.GetOffset() + if bt.Strategy.UsingSimultaneousProcessing() && bt.hasProcessedDataAtOffset[o] { + // only append one event, as simultaneous processing + // will retrieve all relevant events to process under + // processSimultaneousDataEvents() + continue + } + bt.EventQueue.AppendEvent(e) + if !bt.hasProcessedDataAtOffset[o] { + bt.hasProcessedDataAtOffset[o] = true } } } else { + doubleNil = false err := bt.handleEvent(ev) if err != nil { log.Error(common.Backtester, err) } - } - if !bt.hasHandledEvent { - bt.hasHandledEvent = true + if !bt.hasProcessedAnEvent { + bt.hasProcessedAnEvent = true + } } } } @@ -165,24 +250,19 @@ dataLoadingIssue: // after data has been loaded and Run has appended a data event to the queue, // handle event will process events and add further events to the queue if they // are required -func (bt *BackTest) handleEvent(ev common.EventHandler) error { +func (bt *BackTest) handleEvent(ev common.Event) error { if ev == nil { return fmt.Errorf("cannot handle event %w", errNilData) } + funds, err := bt.Funding.GetFundingForEvent(ev) if err != nil { return err } - if bt.Funding.HasFutures() { - err = bt.Funding.UpdateCollateral(ev) - if err != nil { - return err - } - } - switch eType := ev.(type) { - case common.DataEventHandler: + case kline.Event: + // using kline.Event as signal.Event also matches data.Event if bt.Strategy.UsingSimultaneousProcessing() { err = bt.processSimultaneousDataEvents() } else { @@ -194,8 +274,19 @@ func (bt *BackTest) handleEvent(ev common.EventHandler) error { err = bt.processOrderEvent(eType, funds.FundReleaser()) case fill.Event: err = bt.processFillEvent(eType, funds.FundReleaser()) + if bt.LiveDataHandler != nil { + // output log data per interval instead of at the end + result, logErr := bt.Statistic.CreateLog(eType) + if logErr != nil { + return logErr + } + if err != nil { + return err + } + log.Info(common.LiveStrategy, result) + } default: - return fmt.Errorf("handleEvent %w %T received, could not process", + err = fmt.Errorf("handleEvent %w %T received, could not process", errUnhandledDatatype, ev) } @@ -203,17 +294,16 @@ func (bt *BackTest) handleEvent(ev common.EventHandler) error { return err } - bt.Funding.CreateSnapshot(ev.GetTime()) - return nil + return bt.Funding.CreateSnapshot(ev.GetTime()) } // processSingleDataEvent will pass the event to the strategy and determine how it should be handled -func (bt *BackTest) processSingleDataEvent(ev common.DataEventHandler, funds funding.IFundReleaser) error { +func (bt *BackTest) processSingleDataEvent(ev data.Event, funds funding.IFundReleaser) error { err := bt.updateStatsForDataEvent(ev, funds) if err != nil { return err } - d, err := bt.Datas.GetDataForCurrency(ev) + d, err := bt.DataHolder.GetDataForCurrency(ev) if err != nil { return err } @@ -239,39 +329,54 @@ func (bt *BackTest) processSingleDataEvent(ev common.DataEventHandler, funds fun // to the event queue. It will pass all currency events to the strategy to determine what // currencies to act upon func (bt *BackTest) processSimultaneousDataEvents() error { - var dataEvents []data.Handler - dataHandlerMap := bt.Datas.GetAllData() - for _, exchangeMap := range dataHandlerMap { - for _, assetMap := range exchangeMap { - for _, dataHandler := range assetMap { - latestData := dataHandler.Latest() - funds, err := bt.Funding.GetFundingForEvent(latestData) - if err != nil { - return err + dataHolders, err := bt.DataHolder.GetAllData() + if err != nil { + return err + } + + dataEvents := make([]data.Handler, 0, len(dataHolders)) + for i := range dataHolders { + var latestData data.Event + latestData, err = dataHolders[i].Latest() + if err != nil { + return err + } + var funds funding.IFundingPair + funds, err = bt.Funding.GetFundingForEvent(latestData) + if err != nil { + return err + } + err = bt.updateStatsForDataEvent(latestData, funds.FundReleaser()) + if err != nil { + switch { + case errors.Is(err, statistics.ErrAlreadyProcessed): + if !bt.MetaData.Closed || !bt.MetaData.ClosePositionsOnStop { + // Closing positions on close reuses existing events and doesn't need to be logged + // any other scenario, this should be logged + log.Warnf(common.LiveStrategy, "%v %v", latestData.GetOffset(), err) } - err = bt.updateStatsForDataEvent(latestData, funds.FundReleaser()) - if err != nil { - switch { - case errors.Is(err, statistics.ErrAlreadyProcessed): - continue - case errors.Is(err, gctorder.ErrPositionLiquidated): - return nil - default: - log.Error(common.Backtester, err) - } - } - dataEvents = append(dataEvents, dataHandler) + continue + case errors.Is(err, gctorder.ErrPositionLiquidated): + return nil + default: + log.Error(common.Backtester, err) } } + dataEvents = append(dataEvents, dataHolders[i]) } signals, err := bt.Strategy.OnSimultaneousSignals(dataEvents, bt.Funding, bt.Portfolio) if err != nil { - if errors.Is(err, base.ErrTooMuchBadData) { + switch { + case errors.Is(err, base.ErrTooMuchBadData): // too much bad data is a severe error and backtesting must cease return err + case errors.Is(err, base.ErrNoDataToProcess) && bt.MetaData.Closed && bt.MetaData.ClosePositionsOnStop: + // event queue is being cleared with no data events to process + return nil + default: + log.Errorf(common.Backtester, "OnSimultaneousSignals %v", err) + return nil } - log.Errorf(common.Backtester, "OnSimultaneousSignals %v", err) - return nil } for i := range signals { err = bt.Statistic.SetEventForOffset(signals[i]) @@ -285,20 +390,20 @@ func (bt *BackTest) processSimultaneousDataEvents() error { // updateStatsForDataEvent makes various systems aware of price movements from // data events -func (bt *BackTest) updateStatsForDataEvent(ev common.DataEventHandler, funds funding.IFundReleaser) error { +func (bt *BackTest) updateStatsForDataEvent(ev data.Event, funds funding.IFundReleaser) error { if ev == nil { return common.ErrNilEvent } if funds == nil { - return fmt.Errorf("%v %v %v %w missing fund releaser", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), common.ErrNilArguments) + return fmt.Errorf("%v %v %v %w missing fund releaser", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), gctcommon.ErrNilPointer) } // update statistics with the latest price - err := bt.Statistic.SetupEventForTime(ev) + err := bt.Statistic.SetEventForOffset(ev) if err != nil { if errors.Is(err, statistics.ErrAlreadyProcessed) { return err } - log.Errorf(common.Backtester, "SetupEventForTime %v", err) + log.Errorf(common.Backtester, "SetEventForOffset %v", err) } // update portfolio manager with the latest price err = bt.Portfolio.UpdateHoldings(ev, funds) @@ -332,15 +437,17 @@ func (bt *BackTest) updateStatsForDataEvent(ev common.DataEventHandler, funds fu if pnl.Result.IsLiquidated { return nil } - err = bt.Portfolio.CheckLiquidationStatus(ev, cr, pnl) - if err != nil { - if errors.Is(err, gctorder.ErrPositionLiquidated) { - liquidErr := bt.triggerLiquidationsForExchange(ev, pnl) - if liquidErr != nil { - return liquidErr + if bt.LiveDataHandler == nil || (bt.LiveDataHandler != nil && !bt.LiveDataHandler.IsRealOrders()) { + err = bt.Portfolio.CheckLiquidationStatus(ev, cr, pnl) + if err != nil { + if errors.Is(err, gctorder.ErrPositionLiquidated) { + liquidErr := bt.triggerLiquidationsForExchange(ev, pnl) + if liquidErr != nil { + return liquidErr + } } + return err } - return err } return bt.Statistic.AddPNLForTime(pnl) @@ -349,66 +456,28 @@ func (bt *BackTest) updateStatsForDataEvent(ev common.DataEventHandler, funds fu return nil } -func (bt *BackTest) triggerLiquidationsForExchange(ev common.DataEventHandler, pnl *portfolio.PNLSummary) error { - if ev == nil { - return common.ErrNilEvent - } - if pnl == nil { - return fmt.Errorf("%w pnl summary", common.ErrNilArguments) - } - orders, err := bt.Portfolio.CreateLiquidationOrdersForExchange(ev, bt.Funding) - if err != nil { - return err - } - for i := range orders { - // these orders are raising events for event offsets - // which may not have been processed yet - // this will create and store stats for each order - // then liquidate it at the funding level - var datas data.Handler - datas, err = bt.Datas.GetDataForCurrency(orders[i]) - if err != nil { - return err - } - latest := datas.Latest() - err = bt.Statistic.SetupEventForTime(latest) - if err != nil && !errors.Is(err, statistics.ErrAlreadyProcessed) { - return err - } - bt.EventQueue.AppendEvent(orders[i]) - err = bt.Statistic.SetEventForOffset(orders[i]) - if err != nil { - log.Errorf(common.Backtester, "SetupEventForTime %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) - } - bt.Funding.Liquidate(orders[i]) - } - pnl.Result.IsLiquidated = true - pnl.Result.Status = gctorder.Liquidated - return bt.Statistic.AddPNLForTime(pnl) -} - // processSignalEvent receives an event from the strategy for processing under the portfolio func (bt *BackTest) processSignalEvent(ev signal.Event, funds funding.IFundReserver) error { if ev == nil { return common.ErrNilEvent } if funds == nil { - return fmt.Errorf("%w funds", common.ErrNilArguments) + return fmt.Errorf("%w funds", gctcommon.ErrNilPointer) } cs, err := bt.Exchange.GetCurrencySettings(ev.GetExchange(), ev.GetAssetType(), ev.Pair()) if err != nil { - log.Errorf(common.Backtester, "GetCurrencySettings %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) - return fmt.Errorf("GetCurrencySettings %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + log.Errorf(common.Backtester, "GetCurrencySettings %v", err) + return fmt.Errorf("GetCurrencySettings %v", err) } var o *order.Order o, err = bt.Portfolio.OnSignal(ev, &cs, funds) if err != nil { - log.Errorf(common.Backtester, "OnSignal %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + log.Errorf(common.Backtester, "OnSignal %v", err) return fmt.Errorf("OnSignal %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) } err = bt.Statistic.SetEventForOffset(o) if err != nil { - return fmt.Errorf("SetEventForOffset %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + return fmt.Errorf("SetEventForOffset %v", err) } bt.EventQueue.AppendEvent(o) @@ -420,9 +489,9 @@ func (bt *BackTest) processOrderEvent(ev order.Event, funds funding.IFundRelease return common.ErrNilEvent } if funds == nil { - return fmt.Errorf("%w funds", common.ErrNilArguments) + return fmt.Errorf("%w funds", gctcommon.ErrNilPointer) } - d, err := bt.Datas.GetDataForCurrency(ev) + d, err := bt.DataHolder.GetDataForCurrency(ev) if err != nil { return err } @@ -445,36 +514,27 @@ func (bt *BackTest) processOrderEvent(ev order.Event, funds funding.IFundRelease } func (bt *BackTest) processFillEvent(ev fill.Event, funds funding.IFundReleaser) error { - t, err := bt.Portfolio.OnFill(ev, funds) + _, err := bt.Portfolio.OnFill(ev, funds) if err != nil { return fmt.Errorf("OnFill %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) } - err = bt.Statistic.SetEventForOffset(t) + err = bt.Funding.UpdateCollateralForEvent(ev, false) if err != nil { - log.Errorf(common.Backtester, "SetEventForOffset %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + return fmt.Errorf("UpdateCollateralForEvent %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) } - - var holding *holdings.Holding - holding, err = bt.Portfolio.ViewHoldingAtTimePeriod(ev) + holding, err := bt.Portfolio.ViewHoldingAtTimePeriod(ev) if err != nil { log.Error(common.Backtester, err) } - if holding == nil { - log.Error(common.Backtester, "ViewHoldingAtTimePeriod why is holdings nil?") - } else { - err = bt.Statistic.AddHoldingsForTime(holding) - if err != nil { - log.Errorf(common.Backtester, "AddHoldingsForTime %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) - } - } - - var cp *compliance.Manager - cp, err = bt.Portfolio.GetComplianceManager(ev.GetExchange(), ev.GetAssetType(), ev.Pair()) + err = bt.Statistic.AddHoldingsForTime(holding) if err != nil { - log.Errorf(common.Backtester, "GetComplianceManager %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + log.Errorf(common.Backtester, "AddHoldingsForTime %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) } - snap := cp.GetLatestSnapshot() + snap, err := bt.Portfolio.GetLatestComplianceSnapshot(ev.GetExchange(), ev.GetAssetType(), ev.Pair()) + if err != nil { + log.Errorf(common.Backtester, "GetLatestComplianceSnapshot %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + } err = bt.Statistic.AddComplianceSnapshotForTime(snap, ev) if err != nil { log.Errorf(common.Backtester, "AddComplianceSnapshotForTime %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) @@ -498,81 +558,215 @@ func (bt *BackTest) processFillEvent(ev fill.Event, funds funding.IFundReleaser) if ev.GetAssetType().IsFutures() { return bt.processFuturesFillEvent(ev, funds) } + return nil } func (bt *BackTest) processFuturesFillEvent(ev fill.Event, funds funding.IFundReleaser) error { - if ev.GetOrder() != nil { - pnl, err := bt.Portfolio.TrackFuturesOrder(ev, funds) - if err != nil && !errors.Is(err, gctorder.ErrSubmissionIsNil) { - return fmt.Errorf("TrackFuturesOrder %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) - } + if ev.GetOrder() == nil { + return nil + } + pnl, err := bt.Portfolio.TrackFuturesOrder(ev, funds) + if err != nil && !errors.Is(err, gctorder.ErrSubmissionIsNil) { + return fmt.Errorf("TrackFuturesOrder %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + } - var exch gctexchange.IBotExchange - exch, err = bt.exchangeManager.GetExchangeByName(ev.GetExchange()) + var exch gctexchange.IBotExchange + exch, err = bt.exchangeManager.GetExchangeByName(ev.GetExchange()) + if err != nil { + return fmt.Errorf("GetExchangeByName %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + } + + rPNL := pnl.GetRealisedPNL() + if !rPNL.PNL.IsZero() { + var receivingCurrency currency.Code + var receivingAsset asset.Item + receivingCurrency, receivingAsset, err = exch.GetCurrencyForRealisedPNL(ev.GetAssetType(), ev.Pair()) if err != nil { - return fmt.Errorf("GetExchangeByName %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + return fmt.Errorf("GetCurrencyForRealisedPNL %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) } - - rPNL := pnl.GetRealisedPNL() - if !rPNL.PNL.IsZero() { - var receivingCurrency currency.Code - var receivingAsset asset.Item - receivingCurrency, receivingAsset, err = exch.GetCurrencyForRealisedPNL(ev.GetAssetType(), ev.Pair()) - if err != nil { - return fmt.Errorf("GetCurrencyForRealisedPNL %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) - } - err = bt.Funding.RealisePNL(ev.GetExchange(), receivingAsset, receivingCurrency, rPNL.PNL) - if err != nil { - return fmt.Errorf("RealisePNL %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) - } - } - - err = bt.Statistic.AddPNLForTime(pnl) + err = bt.Funding.RealisePNL(ev.GetExchange(), receivingAsset, receivingCurrency, rPNL.PNL) if err != nil { - log.Errorf(common.Backtester, "AddHoldingsForTime %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + return fmt.Errorf("RealisePNL %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) } } - err := bt.Funding.UpdateCollateral(ev) + + err = bt.Statistic.AddPNLForTime(pnl) if err != nil { - return fmt.Errorf("UpdateCollateral %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + return fmt.Errorf("AddPNLForTime %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + } + err = bt.Funding.UpdateCollateralForEvent(ev, false) + if err != nil { + return fmt.Errorf("UpdateCollateralForEvent %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) } return nil } // Stop shuts down the live data loop -func (bt *BackTest) Stop() { +func (bt *BackTest) Stop() error { if bt == nil { - return + return gctcommon.ErrNilPointer } bt.m.Lock() defer bt.m.Unlock() if bt.MetaData.Closed { - return + return errAlreadyRan } close(bt.shutdown) bt.MetaData.Closed = true bt.MetaData.DateEnded = time.Now() + if bt.MetaData.ClosePositionsOnStop { + err := bt.CloseAllPositions() + if err != nil { + log.Errorf(common.Backtester, "Could not close all positions on stop: %s", err) + } + } err := bt.Statistic.CalculateAllResults() if err != nil { - log.Error(log.Global, err) - return + return err } err = bt.Reports.GenerateReport() if err != nil { - log.Error(log.Global, err) + return err } + return nil } -// GenerateSummary creates a summary of a backtesting/livestrategy run -// this summary contains many details of a run -func (bt *BackTest) GenerateSummary() (*RunSummary, error) { +func (bt *BackTest) triggerLiquidationsForExchange(ev data.Event, pnl *portfolio.PNLSummary) error { + if ev == nil { + return common.ErrNilEvent + } + if pnl == nil { + return fmt.Errorf("%w pnl summary", gctcommon.ErrNilPointer) + } + orders, err := bt.Portfolio.CreateLiquidationOrdersForExchange(ev, bt.Funding) + if err != nil { + return err + } + for i := range orders { + // these orders are raising events for event offsets + // which may not have been processed yet + // this will create and store stats for each order + // then liquidate it at the funding level + var datas data.Handler + datas, err = bt.DataHolder.GetDataForCurrency(orders[i]) + if err != nil { + return err + } + var latest data.Event + latest, err = datas.Latest() + if err != nil { + return err + } + err = bt.Statistic.SetEventForOffset(latest) + if err != nil && !errors.Is(err, statistics.ErrAlreadyProcessed) { + return err + } + bt.EventQueue.AppendEvent(orders[i]) + err = bt.Statistic.SetEventForOffset(orders[i]) + if err != nil { + log.Errorf(common.Backtester, "SetEventForOffset %v %v %v %v", ev.GetExchange(), ev.GetAssetType(), ev.Pair(), err) + } + err = bt.Funding.Liquidate(orders[i]) + if err != nil { + return err + } + } + pnl.Result.IsLiquidated = true + pnl.Result.Status = gctorder.Liquidated + return bt.Statistic.AddPNLForTime(pnl) +} + +// CloseAllPositions will close sell any positions held on closure +// can only be with live testing and where a strategy supports it +func (bt *BackTest) CloseAllPositions() error { + if bt.LiveDataHandler == nil { + return errLiveOnly + } + err := bt.LiveDataHandler.UpdateFunding(true) + if err != nil { + return err + } + dataHolders, err := bt.DataHolder.GetAllData() + if err != nil { + return err + } + latestPrices := make([]data.Event, len(dataHolders)) + for i := range dataHolders { + var latest data.Event + latest, err = dataHolders[i].Latest() + if err != nil { + return err + } + latestPrices[i] = latest + } + events, err := bt.Strategy.CloseAllPositions(bt.Portfolio.GetLatestHoldingsForAllCurrencies(), latestPrices) + if err != nil { + if errors.Is(err, gctcommon.ErrFunctionNotSupported) { + log.Warnf(common.LiveStrategy, "Closing all positions is not supported by strategy %v", bt.Strategy.Name()) + return nil + } + return err + } + if len(events) == 0 { + return nil + } + err = bt.LiveDataHandler.SetDataForClosingAllPositions(events...) + if err != nil { + return err + } + for i := range events { + k := events[i].ToKline() + err = bt.Statistic.SetEventForOffset(k) + if err != nil { + return err + } + bt.EventQueue.AppendEvent(events[i]) + } + err = bt.Run() + if err != nil { + return err + } + + err = bt.LiveDataHandler.UpdateFunding(true) + if err != nil { + return err + } + + err = bt.Funding.CreateSnapshot(events[0].GetTime()) + if err != nil { + return err + } + for i := range events { + var funds funding.IFundingPair + funds, err = bt.Funding.GetFundingForEvent(events[i]) + if err != nil { + return err + } + err = bt.Portfolio.SetHoldingsForEvent(funds.FundReader(), events[i]) + if err != nil { + return err + } + } + her := bt.Portfolio.GetLatestHoldingsForAllCurrencies() + for i := range her { + err = bt.Statistic.AddHoldingsForTime(&her[i]) + if err != nil { + return err + } + } + return nil +} + +// GenerateSummary creates a summary of a strategy task +// this summary contains many details of a task +func (bt *BackTest) GenerateSummary() (*TaskSummary, error) { if bt == nil { return nil, gctcommon.ErrNilPointer } bt.m.Lock() defer bt.m.Unlock() - return &RunSummary{ + return &TaskSummary{ MetaData: bt.MetaData, }, nil } @@ -597,7 +791,7 @@ func (bt *BackTest) SetupMetaData() error { return nil } -// IsRunning checks if the run is running +// IsRunning checks if the task is running func (bt *BackTest) IsRunning() bool { if bt == nil { return false @@ -607,7 +801,7 @@ func (bt *BackTest) IsRunning() bool { return !bt.MetaData.DateStarted.IsZero() && !bt.MetaData.Closed } -// HasRan checks if the run has been ran +// HasRan checks if the task has been executed func (bt *BackTest) HasRan() bool { if bt == nil { return false @@ -617,7 +811,7 @@ func (bt *BackTest) HasRan() bool { return bt.MetaData.Closed } -// Equal checks if the incoming run matches +// Equal checks if the incoming task matches func (bt *BackTest) Equal(bt2 *BackTest) bool { if bt == nil || bt2 == nil { return false diff --git a/backtester/engine/backtest_test.go b/backtester/engine/backtest_test.go index 527b635c..6b44d95e 100644 --- a/backtester/engine/backtest_test.go +++ b/backtester/engine/backtest_test.go @@ -1,8 +1,11 @@ package engine import ( + "context" "errors" "path/filepath" + "strings" + "sync" "testing" "time" @@ -19,6 +22,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/size" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/binancecashandcarry" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" @@ -28,24 +33,667 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/funding" "github.com/thrasher-corp/gocryptotrader/backtester/report" gctcommon "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/database" + "github.com/thrasher-corp/gocryptotrader/database/drivers" "github.com/thrasher-corp/gocryptotrader/engine" + gctexchange "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/ftx" + "github.com/thrasher-corp/gocryptotrader/exchanges/binance" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) -const testExchange = "ftx" +const testExchange = "binance" var leet = decimal.NewFromInt(1337) +func TestSetupFromConfig(t *testing.T) { + t.Parallel() + bt, err := NewBacktester() + if !errors.Is(err, nil) { + t.Errorf("received %v, expected %v", err, nil) + } + err = bt.SetupFromConfig(nil, "", "", false) + if !errors.Is(err, errNilConfig) { + t.Errorf("received %v, expected %v", err, errNilConfig) + } + cfg := &config.Config{} + err = bt.SetupFromConfig(cfg, "", "", false) + if !errors.Is(err, base.ErrStrategyNotFound) { + t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound) + } + + cfg.CurrencySettings = []config.CurrencySettings{ + { + ExchangeName: testExchange, + Base: currency.BTC, + Quote: currency.NewCode("0624"), + Asset: asset.Spot, + }, + { + ExchangeName: testExchange, + Base: currency.BTC, + Quote: currency.NewCode("0624"), + Asset: asset.USDTMarginedFutures, + }, + } + err = bt.SetupFromConfig(cfg, "", "", false) + if !errors.Is(err, base.ErrStrategyNotFound) { + t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound) + } + + cfg.StrategySettings = config.StrategySettings{ + Name: dollarcostaverage.Name, + CustomSettings: map[string]interface{}{ + "hello": "moto", + }, + } + cfg.CurrencySettings[0].Base = currency.BTC + cfg.CurrencySettings[0].Quote = currency.USDT + cfg.DataSettings.APIData = &config.APIData{} + + err = bt.SetupFromConfig(cfg, "", "", false) + if err != nil && !strings.Contains(err.Error(), "unrecognised dataType") { + t.Error(err) + } + cfg.DataSettings.DataType = common.CandleStr + err = bt.SetupFromConfig(cfg, "", "", false) + if !errors.Is(err, errIntervalUnset) { + t.Errorf("received: %v, expected: %v", err, errIntervalUnset) + } + cfg.DataSettings.Interval = gctkline.OneMin + cfg.CurrencySettings[0].MakerFee = &decimal.Zero + cfg.CurrencySettings[0].TakerFee = &decimal.Zero + err = bt.SetupFromConfig(cfg, "", "", false) + if !errors.Is(err, gctcommon.ErrDateUnset) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset) + } + + cfg.DataSettings.APIData.StartDate = time.Now().Add(-time.Minute) + cfg.DataSettings.APIData.EndDate = time.Now() + cfg.DataSettings.APIData.InclusiveEndDate = true + err = bt.SetupFromConfig(cfg, "", "", false) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) + } + cfg.FundingSettings.UseExchangeLevelFunding = true + cfg.FundingSettings.ExchangeLevelFunding = []config.ExchangeLevelFunding{ + { + ExchangeName: testExchange, + Asset: asset.Spot, + Currency: currency.BTC, + InitialFunds: leet, + TransferFee: leet, + }, + { + ExchangeName: testExchange, + Asset: asset.USDTMarginedFutures, + Currency: currency.BTC, + InitialFunds: leet, + TransferFee: leet, + }, + } + err = bt.SetupFromConfig(cfg, "", "", false) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) + } +} + +func TestLoadDataAPI(t *testing.T) { + t.Parallel() + bt := BackTest{ + Reports: &report.Data{}, + } + cp := currency.NewPair(currency.BTC, currency.USDT) + cfg := &config.Config{ + CurrencySettings: []config.CurrencySettings{ + { + ExchangeName: "Binance", + Asset: asset.Spot, + Base: cp.Base, + Quote: cp.Quote, + SpotDetails: &config.SpotDetails{ + InitialQuoteFunds: &leet, + }, + }, + }, + DataSettings: config.DataSettings{ + DataType: common.CandleStr, + Interval: gctkline.OneMin, + APIData: &config.APIData{ + StartDate: time.Now().Add(-time.Minute * 5), + EndDate: time.Now(), + }}, + StrategySettings: config.StrategySettings{ + Name: dollarcostaverage.Name, + CustomSettings: map[string]interface{}{ + "hello": "moto", + }, + }, + } + em := engine.ExchangeManager{} + exch, err := em.NewExchangeByName("Binance") + if err != nil { + t.Fatal(err) + } + exch.SetDefaults() + b := exch.GetBase() + b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + Available: currency.Pairs{cp}, + Enabled: currency.Pairs{cp}, + AssetEnabled: convert.BoolPtr(true), + ConfigFormat: ¤cy.PairFormat{Uppercase: true}, + RequestFormat: ¤cy.PairFormat{Uppercase: true}} + + _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) + if err != nil { + t.Error(err) + } +} + +func TestLoadDataCSV(t *testing.T) { + t.Parallel() + bt := BackTest{ + Reports: &report.Data{}, + } + cp := currency.NewPair(currency.BTC, currency.USDT) + cfg := &config.Config{ + CurrencySettings: []config.CurrencySettings{ + { + ExchangeName: "Binance", + Asset: asset.Spot, + Base: cp.Base, + Quote: cp.Quote, + SpotDetails: &config.SpotDetails{ + InitialQuoteFunds: &leet, + }, + MakerFee: &decimal.Zero, + TakerFee: &decimal.Zero, + }, + }, + DataSettings: config.DataSettings{ + DataType: common.CandleStr, + Interval: gctkline.OneMin, + CSVData: &config.CSVData{ + FullPath: "test", + }}, + StrategySettings: config.StrategySettings{ + Name: dollarcostaverage.Name, + CustomSettings: map[string]interface{}{ + "hello": "moto", + }, + }, + } + em := engine.ExchangeManager{} + exch, err := em.NewExchangeByName("Binance") + if err != nil { + t.Fatal(err) + } + exch.SetDefaults() + b := exch.GetBase() + b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + Available: currency.Pairs{cp}, + Enabled: currency.Pairs{cp}, + AssetEnabled: convert.BoolPtr(true), + ConfigFormat: ¤cy.PairFormat{Uppercase: true}, + RequestFormat: ¤cy.PairFormat{Uppercase: true}} + _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) + if err != nil && + !strings.Contains(err.Error(), "The system cannot find the file specified.") && + !strings.Contains(err.Error(), "no such file or directory") { + t.Error(err) + } +} + +func TestLoadDataDatabase(t *testing.T) { + t.Parallel() + bt := BackTest{ + Reports: &report.Data{}, + shutdown: make(chan struct{}), + } + cp := currency.NewPair(currency.BTC, currency.USDT) + cfg := &config.Config{ + CurrencySettings: []config.CurrencySettings{ + { + ExchangeName: testExchange, + Asset: asset.Spot, + Base: cp.Base, + Quote: cp.Quote, + SpotDetails: &config.SpotDetails{ + InitialQuoteFunds: &leet, + }, + MakerFee: &decimal.Zero, + TakerFee: &decimal.Zero, + }, + }, + DataSettings: config.DataSettings{ + DataType: common.CandleStr, + Interval: gctkline.OneMin, + DatabaseData: &config.DatabaseData{ + Config: database.Config{ + Enabled: true, + Driver: "sqlite3", + ConnectionDetails: drivers.ConnectionDetails{ + Database: "gocryptotrader.db", + }, + }, + StartDate: time.Now().Add(-time.Minute), + EndDate: time.Now(), + InclusiveEndDate: true, + }}, + StrategySettings: config.StrategySettings{ + Name: dollarcostaverage.Name, + CustomSettings: map[string]interface{}{ + "hello": "moto", + }, + }, + } + em := engine.ExchangeManager{} + exch, err := em.NewExchangeByName(testExchange) + if err != nil { + t.Fatal(err) + } + exch.SetDefaults() + b := exch.GetBase() + b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + Available: currency.Pairs{cp}, + Enabled: currency.Pairs{cp}, + AssetEnabled: convert.BoolPtr(true), + ConfigFormat: ¤cy.PairFormat{Uppercase: true}, + RequestFormat: ¤cy.PairFormat{Uppercase: true}} + bt.databaseManager, err = engine.SetupDatabaseConnectionManager(&cfg.DataSettings.DatabaseData.Config) + if err != nil { + t.Fatal(err) + } + _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) + if err != nil && !strings.Contains(err.Error(), "unable to retrieve data from GoCryptoTrader database") { + t.Error(err) + } +} + +func TestLoadDataLive(t *testing.T) { + t.Parallel() + bt := BackTest{ + Reports: &fakeReport{}, + Funding: &funding.FundManager{}, + DataHolder: &data.HandlerHolder{}, + Statistic: &fakeStats{}, + exchangeManager: engine.SetupExchangeManager(), + shutdown: make(chan struct{}), + } + + cp := currency.NewPair(currency.BTC, currency.USDT) + cfg := &config.Config{ + CurrencySettings: []config.CurrencySettings{ + { + ExchangeName: testExchange, + Asset: asset.Spot, + Base: cp.Base, + Quote: cp.Quote, + SpotDetails: &config.SpotDetails{ + InitialQuoteFunds: &leet, + }, + MakerFee: &decimal.Zero, + TakerFee: &decimal.Zero, + }, + }, + DataSettings: config.DataSettings{ + DataType: common.CandleStr, + Interval: gctkline.OneMin, + LiveData: &config.LiveData{ + ExchangeCredentials: []config.Credentials{ + { + Exchange: testExchange, + Keys: account.Credentials{ + Key: "test", + Secret: "test", + ClientID: "test", + PEMKey: "test", + SubAccount: "test", + OneTimePassword: "test", + }, + }, + }, + RealOrders: true, + }}, + StrategySettings: config.StrategySettings{ + Name: dollarcostaverage.Name, + CustomSettings: map[string]interface{}{ + "hello": "moto", + }, + }, + } + exch, err := bt.exchangeManager.NewExchangeByName(testExchange) + if err != nil { + t.Fatal(err) + } + exch.SetDefaults() + err = bt.SetupLiveDataHandler(0, 0, false, false) + if !errors.Is(err, nil) { + t.Fatalf("received: %v, expected: %v", err, nil) + } + err = bt.LiveDataHandler.Start() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + b := exch.GetBase() + b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) + b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ + Available: currency.Pairs{cp}, + Enabled: currency.Pairs{cp}, + AssetEnabled: convert.BoolPtr(true), + ConfigFormat: ¤cy.PairFormat{Uppercase: true}, + RequestFormat: ¤cy.PairFormat{Uppercase: true}} + _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + err = bt.Stop() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } +} + +func TestReset(t *testing.T) { + t.Parallel() + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, false, false) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + bt := &BackTest{ + shutdown: make(chan struct{}), + DataHolder: &data.HandlerHolder{}, + Strategy: &dollarcostaverage.Strategy{}, + Portfolio: &portfolio.Portfolio{}, + Exchange: &exchange.Exchange{}, + Statistic: &statistics.Statistic{}, + EventQueue: &eventholder.Holder{}, + Reports: &report.Data{}, + Funding: f, + } + err = bt.Reset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if bt.Funding.IsUsingExchangeLevelFunding() { + t.Error("expected false") + } + + bt = nil + err = bt.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestFullCycle(t *testing.T) { + t.Parallel() + ex := testExchange + cp := currency.NewPair(currency.BTC, currency.USDT) + a := asset.Spot + tt := time.Now() + + stats := &statistics.Statistic{} + stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + stats.ExchangeAssetPairStatistics[ex][a] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + stats.ExchangeAssetPairStatistics[ex][a][cp.Base.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic) + + port, err := portfolio.Setup(&size.Size{ + BuySide: exchange.MinMax{}, + SellSide: exchange.MinMax{}, + }, &risk.Risk{}, decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + fx := &binance.Binance{} + fx.Name = testExchange + err = port.SetCurrencySettingsMap(&exchange.Settings{Exchange: fx, Asset: a, Pair: cp}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true, false) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + b, err := funding.CreateItem(ex, a, cp.Base, decimal.Zero, decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + quote, err := funding.CreateItem(ex, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + pair, err := funding.CreatePair(b, quote) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + err = f.AddPair(pair) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + bt := BackTest{ + DataHolder: &data.HandlerHolder{}, + Strategy: &dollarcostaverage.Strategy{}, + Portfolio: port, + Exchange: &exchange.Exchange{}, + Statistic: stats, + EventQueue: &eventholder.Holder{}, + Reports: &report.Data{}, + hasProcessedDataAtOffset: make(map[int64]bool), + Funding: f, + shutdown: make(chan struct{}), + } + + bt.DataHolder = data.NewHandlerHolder() + k := &kline.DataFromKline{ + Item: gctkline.Item{ + Exchange: ex, + Pair: cp, + Asset: a, + Interval: gctkline.FifteenMin, + Candles: []gctkline.Candle{{ + Time: tt, + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }}, + }, + Base: &data.Base{}, + RangeHolder: &gctkline.IntervalRangeHolder{ + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + Ranges: []gctkline.IntervalRange{ + { + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + Intervals: []gctkline.IntervalData{ + { + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + HasData: true, + }, + }, + }, + }, + }, + } + err = k.Load() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + err = bt.DataHolder.SetDataForCurrency(ex, a, cp, k) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + bt.MetaData.DateLoaded = time.Now() + err = bt.Run() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } +} + +func TestStop(t *testing.T) { + t.Parallel() + bt := &BackTest{ + shutdown: make(chan struct{}), + Statistic: &fakeStats{}, + Reports: &fakeReport{}, + } + err := bt.Stop() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + tt := bt.MetaData.DateEnded + + err = bt.Stop() + if !errors.Is(err, errAlreadyRan) { + t.Errorf("received: %v, expected: %v", err, errAlreadyRan) + } + if !tt.Equal(bt.MetaData.DateEnded) { + t.Errorf("received '%v' expected '%v'", bt.MetaData.DateEnded, tt) + } + + bt = nil + err = bt.Stop() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) + } +} + +func TestFullCycleMulti(t *testing.T) { + t.Parallel() + ex := testExchange + cp := currency.NewPair(currency.BTC, currency.USDT) + a := asset.Spot + tt := time.Now() + + stats := &statistics.Statistic{} + stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + stats.ExchangeAssetPairStatistics[ex][a] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + stats.ExchangeAssetPairStatistics[ex][a][cp.Base.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic) + + port, err := portfolio.Setup(&size.Size{ + BuySide: exchange.MinMax{}, + SellSide: exchange.MinMax{}, + }, &risk.Risk{}, decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + err = port.SetCurrencySettingsMap(&exchange.Settings{Exchange: &binance.Binance{}, Asset: a, Pair: cp}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true, false) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + b, err := funding.CreateItem(ex, a, cp.Base, decimal.Zero, decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + quote, err := funding.CreateItem(ex, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + pair, err := funding.CreatePair(b, quote) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + err = f.AddPair(pair) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + bt := BackTest{ + DataHolder: &data.HandlerHolder{}, + Portfolio: port, + Exchange: &exchange.Exchange{}, + Statistic: stats, + EventQueue: &eventholder.Holder{}, + Reports: &report.Data{}, + Funding: f, + hasProcessedDataAtOffset: make(map[int64]bool), + shutdown: make(chan struct{}), + } + + bt.Strategy, err = strategies.LoadStrategyByName(dollarcostaverage.Name, true) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + bt.DataHolder = data.NewHandlerHolder() + k := &kline.DataFromKline{ + Item: gctkline.Item{ + Exchange: ex, + Pair: cp, + Asset: a, + Interval: gctkline.FifteenMin, + Candles: []gctkline.Candle{{ + Time: tt, + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }}, + }, + Base: &data.Base{}, + RangeHolder: &gctkline.IntervalRangeHolder{ + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + Ranges: []gctkline.IntervalRange{ + { + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + Intervals: []gctkline.IntervalData{ + { + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + HasData: true, + }, + }, + }, + }, + }, + } + err = k.Load() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + err = bt.DataHolder.SetDataForCurrency(ex, a, cp, k) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + err = bt.Run() + if !errors.Is(err, errNotSetup) { + t.Errorf("received: %v, expected: %v", err, errNotSetup) + } + + bt.MetaData.DateLoaded = time.Now() + err = bt.Run() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } +} + type portfolioOverride struct { Err error portfolio.Portfolio } -func (p portfolioOverride) CreateLiquidationOrdersForExchange(ev common.DataEventHandler, _ funding.IFundingManager) ([]order.Event, error) { +func (p portfolioOverride) CreateLiquidationOrdersForExchange(ev data.Event, _ funding.IFundingManager) ([]order.Event, error) { if p.Err != nil { return nil, p.Err } @@ -58,255 +706,11 @@ func (p portfolioOverride) CreateLiquidationOrdersForExchange(ev common.DataEven }, nil } -func TestReset(t *testing.T) { - t.Parallel() - f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, false) - if err != nil { - t.Error(err) - } - bt := BackTest{ - shutdown: make(chan struct{}), - Datas: &data.HandlerPerCurrency{}, - Strategy: &dollarcostaverage.Strategy{}, - Portfolio: &portfolio.Portfolio{}, - Exchange: &exchange.Exchange{}, - Statistic: &statistics.Statistic{}, - EventQueue: &eventholder.Holder{}, - Reports: &report.Data{}, - Funding: f, - } - bt.Reset() - if bt.Funding.IsUsingExchangeLevelFunding() { - t.Error("expected false") - } -} - -func TestFullCycle(t *testing.T) { - t.Parallel() - ex := testExchange - cp := currency.NewPair(currency.BTC, currency.USD) - a := asset.Spot - tt := time.Now() - - stats := &statistics.Statistic{} - stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - stats.ExchangeAssetPairStatistics[ex][a] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) - - port, err := portfolio.Setup(&size.Size{ - BuySide: exchange.MinMax{}, - SellSide: exchange.MinMax{}, - }, &risk.Risk{}, decimal.Zero) - if err != nil { - t.Error(err) - } - fx := &ftx.FTX{} - fx.Name = testExchange - err = port.SetupCurrencySettingsMap(&exchange.Settings{Exchange: fx, Asset: a, Pair: cp}) - if err != nil { - t.Error(err) - } - f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) - if err != nil { - t.Error(err) - } - b, err := funding.CreateItem(ex, a, cp.Base, decimal.Zero, decimal.Zero) - if err != nil { - t.Error(err) - } - quote, err := funding.CreateItem(ex, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) - if err != nil { - t.Error(err) - } - pair, err := funding.CreatePair(b, quote) - if err != nil { - t.Error(err) - } - err = f.AddPair(pair) - if err != nil { - t.Error(err) - } - bt := BackTest{ - shutdown: nil, - Datas: &data.HandlerPerCurrency{}, - Strategy: &dollarcostaverage.Strategy{}, - Portfolio: port, - Exchange: &exchange.Exchange{}, - Statistic: stats, - EventQueue: &eventholder.Holder{}, - Reports: &report.Data{}, - Funding: f, - } - - bt.Datas.Setup() - k := kline.DataFromKline{ - Item: gctkline.Item{ - Exchange: ex, - Pair: cp, - Asset: a, - Interval: gctkline.FifteenMin, - Candles: []gctkline.Candle{{ - Time: tt, - Open: 1337, - High: 1337, - Low: 1337, - Close: 1337, - Volume: 1337, - }}, - }, - Base: data.Base{}, - RangeHolder: &gctkline.IntervalRangeHolder{ - Start: gctkline.CreateIntervalTime(tt), - End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), - Ranges: []gctkline.IntervalRange{ - { - Start: gctkline.CreateIntervalTime(tt), - End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), - Intervals: []gctkline.IntervalData{ - { - Start: gctkline.CreateIntervalTime(tt), - End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), - HasData: true, - }, - }, - }, - }, - }, - } - err = k.Load() - if err != nil { - t.Error(err) - } - bt.Datas.SetDataForCurrency(ex, a, cp, &k) - - bt.Run() -} - -func TestStop(t *testing.T) { - t.Parallel() - bt := &BackTest{ - shutdown: make(chan struct{}), - Statistic: &statistics.Statistic{}, - } - bt.Stop() - tt := bt.MetaData.DateEnded - - bt.Stop() - if !tt.Equal(bt.MetaData.DateEnded) { - t.Errorf("received '%v' expected '%v'", bt.MetaData.DateEnded, tt) - } - - bt = nil - bt.Stop() -} - -func TestFullCycleMulti(t *testing.T) { - t.Parallel() - ex := testExchange - cp := currency.NewPair(currency.BTC, currency.USD) - a := asset.Spot - tt := time.Now() - - stats := &statistics.Statistic{} - stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - stats.ExchangeAssetPairStatistics[ex][a] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) - - port, err := portfolio.Setup(&size.Size{ - BuySide: exchange.MinMax{}, - SellSide: exchange.MinMax{}, - }, &risk.Risk{}, decimal.Zero) - if err != nil { - t.Error(err) - } - err = port.SetupCurrencySettingsMap(&exchange.Settings{Exchange: &ftx.FTX{}, Asset: a, Pair: cp}) - if err != nil { - t.Error(err) - } - f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) - if err != nil { - t.Error(err) - } - b, err := funding.CreateItem(ex, a, cp.Base, decimal.Zero, decimal.Zero) - if err != nil { - t.Error(err) - } - quote, err := funding.CreateItem(ex, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) - if err != nil { - t.Error(err) - } - pair, err := funding.CreatePair(b, quote) - if err != nil { - t.Error(err) - } - err = f.AddPair(pair) - if err != nil { - t.Error(err) - } - bt := BackTest{ - shutdown: nil, - Datas: &data.HandlerPerCurrency{}, - Portfolio: port, - Exchange: &exchange.Exchange{}, - Statistic: stats, - EventQueue: &eventholder.Holder{}, - Reports: &report.Data{}, - Funding: f, - } - - bt.Strategy, err = strategies.LoadStrategyByName(dollarcostaverage.Name, true) - if err != nil { - t.Error(err) - } - - bt.Datas.Setup() - k := kline.DataFromKline{ - Item: gctkline.Item{ - Exchange: ex, - Pair: cp, - Asset: a, - Interval: gctkline.FifteenMin, - Candles: []gctkline.Candle{{ - Time: tt, - Open: 1337, - High: 1337, - Low: 1337, - Close: 1337, - Volume: 1337, - }}, - }, - Base: data.Base{}, - RangeHolder: &gctkline.IntervalRangeHolder{ - Start: gctkline.CreateIntervalTime(tt), - End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), - Ranges: []gctkline.IntervalRange{ - { - Start: gctkline.CreateIntervalTime(tt), - End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), - Intervals: []gctkline.IntervalData{ - { - Start: gctkline.CreateIntervalTime(tt), - End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), - HasData: true, - }, - }, - }, - }, - }, - } - err = k.Load() - if err != nil { - t.Error(err) - } - - bt.Datas.SetDataForCurrency(ex, a, cp, &k) - - bt.Run() -} - func TestTriggerLiquidationsForExchange(t *testing.T) { t.Parallel() - bt := BackTest{} + bt := BackTest{ + shutdown: make(chan struct{}), + } expectedError := common.ErrNilEvent err := bt.triggerLiquidationsForExchange(nil, nil) if !errors.Is(err, expectedError) { @@ -314,8 +718,8 @@ func TestTriggerLiquidationsForExchange(t *testing.T) { } cp := currency.NewPair(currency.BTC, currency.USDT) - a := asset.Futures - expectedError = common.ErrNilArguments + a := asset.USDTMarginedFutures + expectedError = gctcommon.ErrNilPointer ev := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, @@ -328,9 +732,9 @@ func TestTriggerLiquidationsForExchange(t *testing.T) { bt.Portfolio = &portfolioOverride{} pnl := &portfolio.PNLSummary{} - bt.Datas = &data.HandlerPerCurrency{} - d := data.Base{} - d.SetStream([]common.DataEventHandler{&evkline.Kline{ + bt.DataHolder = &data.HandlerHolder{} + d := &data.Base{} + err = d.SetStream([]data.Event{&evkline.Kline{ Base: &event.Base{ Exchange: testExchange, Time: time.Now(), @@ -344,7 +748,13 @@ func TestTriggerLiquidationsForExchange(t *testing.T) { High: leet, Volume: leet, }}) - d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } da := &kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, @@ -359,13 +769,16 @@ func TestTriggerLiquidationsForExchange(t *testing.T) { bt.EventQueue = &eventholder.Holder{} bt.Funding = &funding.FundManager{} - bt.Datas.SetDataForCurrency(testExchange, a, cp, da) - err = bt.Statistic.SetupEventForTime(ev) + err = bt.DataHolder.SetDataForCurrency(testExchange, a, cp, da) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + err = bt.Statistic.SetEventForOffset(ev) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } pnl.Exchange = ev.Exchange - pnl.Item = ev.AssetType + pnl.Asset = ev.AssetType pnl.Pair = ev.CurrencyPair err = bt.triggerLiquidationsForExchange(ev, pnl) if !errors.Is(err, expectedError) { @@ -383,11 +796,11 @@ func TestTriggerLiquidationsForExchange(t *testing.T) { func TestUpdateStatsForDataEvent(t *testing.T) { t.Parallel() - pt := &portfolio.Portfolio{} bt := &BackTest{ - Statistic: &statistics.Statistic{}, + Statistic: &fakeStats{}, Funding: &funding.FundManager{}, - Portfolio: pt, + Portfolio: &fakeFolio{}, + shutdown: make(chan struct{}), } expectedError := common.ErrNilEvent err := bt.updateStatsForDataEvent(nil, nil) @@ -398,18 +811,20 @@ func TestUpdateStatsForDataEvent(t *testing.T) { cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Futures ev := &evkline.Kline{ - Base: &event.Base{Exchange: testExchange, + Base: &event.Base{ + Exchange: testExchange, AssetType: a, - CurrencyPair: cp}, + CurrencyPair: cp, + }, } - expectedError = common.ErrNilArguments + expectedError = gctcommon.ErrNilPointer err = bt.updateStatsForDataEvent(ev, nil) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } expectedError = nil - f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true, false) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -426,16 +841,8 @@ func TestUpdateStatsForDataEvent(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Funding = f - exch := &ftx.FTX{} + exch := &binance.Binance{} exch.Name = testExchange - err = pt.SetupCurrencySettingsMap(&exchange.Settings{ - Exchange: exch, - Pair: cp, - Asset: a, - }) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } ev.Time = time.Now() fl := &fill.Fill{ Base: ev.Base, @@ -457,7 +864,7 @@ func TestUpdateStatsForDataEvent(t *testing.T) { Date: time.Now(), }, } - _, err = pt.TrackFuturesOrder(fl, pair) + _, err = bt.Portfolio.TrackFuturesOrder(fl, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -471,25 +878,22 @@ func TestUpdateStatsForDataEvent(t *testing.T) { func TestProcessSignalEvent(t *testing.T) { t.Parallel() var expectedError error - pt, err := portfolio.Setup(&size.Size{}, &risk.Risk{}, decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } bt := &BackTest{ - Statistic: &statistics.Statistic{}, + Statistic: &fakeStats{}, Funding: &funding.FundManager{}, - Portfolio: pt, + Portfolio: &fakeFolio{}, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, + shutdown: make(chan struct{}), } cp := currency.NewPair(currency.BTC, currency.USDT) - a := asset.Futures + a := asset.USDTMarginedFutures de := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } - err = bt.Statistic.SetupEventForTime(de) + err := bt.Statistic.SetEventForOffset(de) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -497,7 +901,7 @@ func TestProcessSignalEvent(t *testing.T) { Base: de.Base, } - f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true, false) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -514,17 +918,8 @@ func TestProcessSignalEvent(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Funding = f - exch := &ftx.FTX{} + exch := &binance.Binance{} exch.Name = testExchange - err = pt.SetupCurrencySettingsMap(&exchange.Settings{ - Exchange: exch, - Pair: cp, - Asset: a, - }) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ Exchange: exch, Pair: cp, @@ -554,16 +949,17 @@ func TestProcessOrderEvent(t *testing.T) { Portfolio: pt, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, + shutdown: make(chan struct{}), } cp := currency.NewPair(currency.BTC, currency.USDT) - a := asset.Futures + a := asset.USDTMarginedFutures de := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } - err = bt.Statistic.SetupEventForTime(de) + err = bt.Statistic.SetEventForOffset(de) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -571,7 +967,7 @@ func TestProcessOrderEvent(t *testing.T) { Base: de.Base, } - f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true, false) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -588,15 +984,15 @@ func TestProcessOrderEvent(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Funding = f - exch := &ftx.FTX{} + exch := &binance.Binance{} exch.Name = testExchange - err = pt.SetupCurrencySettingsMap(&exchange.Settings{ + err = pt.SetCurrencySettingsMap(&exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNotYetImplemented) } bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ @@ -610,8 +1006,8 @@ func TestProcessOrderEvent(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, expectedError) } tt := time.Now() - bt.Datas.Setup() - k := kline.DataFromKline{ + bt.DataHolder = data.NewHandlerHolder() + k := &kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, Pair: cp, @@ -626,7 +1022,7 @@ func TestProcessOrderEvent(t *testing.T) { Volume: 1337, }}, }, - Base: data.Base{}, + Base: &data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), @@ -646,11 +1042,14 @@ func TestProcessOrderEvent(t *testing.T) { }, } err = k.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - bt.Datas.SetDataForCurrency(testExchange, a, cp, &k) + err = bt.DataHolder.SetDataForCurrency(testExchange, a, cp, k) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } err = bt.processOrderEvent(ev, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) @@ -663,29 +1062,29 @@ func TestProcessOrderEvent(t *testing.T) { func TestProcessFillEvent(t *testing.T) { t.Parallel() - var expectedError error - pt, err := portfolio.Setup(&size.Size{}, &risk.Risk{}, decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } bt := &BackTest{ - Statistic: &statistics.Statistic{}, - Funding: &funding.FundManager{}, - Portfolio: pt, + Statistic: &fakeStats{}, + Funding: &fakeFunding{}, + Portfolio: &fakeFolio{}, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, + shutdown: make(chan struct{}), } - cp := currency.NewPair(currency.BTC, currency.USD) + cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Futures + tt := time.Now() de := &evkline.Kline{ - Base: &event.Base{Exchange: testExchange, + Base: &event.Base{ + Exchange: testExchange, AssetType: a, - CurrencyPair: cp}, + CurrencyPair: cp, + Time: tt, + }, } - err = bt.Statistic.SetupEventForTime(de) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) + err := bt.Statistic.SetEventForOffset(de) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } ev := &fill.Fill{ Base: de.Base, @@ -696,66 +1095,18 @@ func TestProcessFillEvent(t *testing.T) { t.Fatal(err) } exch.SetDefaults() - cfg, err := exch.GetDefaultConfig() - if err != nil { - t.Fatal(err) - } - err = exch.Setup(cfg) - if err != nil { - t.Fatal(err) - } em.Add(exch) - f, err := funding.SetupFundingManager(em, false, true) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } quote, err := funding.CreateItem(testExchange, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } pair, err := funding.CreateCollateral(b, quote) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - - err = f.AddItem(b) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - err = f.AddItem(quote) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - - spotBase, err := funding.CreateItem(testExchange, asset.Spot, cp.Base, decimal.Zero, decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - spotQuote, err := funding.CreateItem(testExchange, asset.Spot, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - spotPair, err := funding.CreatePair(spotBase, spotQuote) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - err = f.AddPair(spotPair) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - - bt.Funding = f - err = pt.SetupCurrencySettingsMap(&exchange.Settings{ - Exchange: exch, - Pair: cp, - Asset: a, - }) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ @@ -765,12 +1116,11 @@ func TestProcessFillEvent(t *testing.T) { }) ev.Direction = gctorder.Short err = bt.Statistic.SetEventForOffset(ev) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } - tt := time.Now() - bt.Datas.Setup() - k := kline.DataFromKline{ + bt.DataHolder = data.NewHandlerHolder() + k := &kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, Pair: cp, @@ -785,7 +1135,7 @@ func TestProcessFillEvent(t *testing.T) { Volume: 1337, }}, }, - Base: data.Base{}, + Base: &data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), @@ -805,40 +1155,41 @@ func TestProcessFillEvent(t *testing.T) { }, } err = k.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - bt.Datas.SetDataForCurrency(testExchange, a, cp, &k) + err = bt.DataHolder.SetDataForCurrency(testExchange, a, cp, k) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } err = bt.processFillEvent(ev, pair) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } } func TestProcessFuturesFillEvent(t *testing.T) { t.Parallel() var expectedError error - pt, err := portfolio.Setup(&size.Size{}, &risk.Risk{}, decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } bt := &BackTest{ - Statistic: &statistics.Statistic{}, + Statistic: &fakeStats{}, Funding: &funding.FundManager{}, - Portfolio: pt, + Portfolio: &fakeFolio{}, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, + shutdown: make(chan struct{}), } - cp := currency.NewPair(currency.BTC, currency.USD) + cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Futures de := &evkline.Kline{ - Base: &event.Base{Exchange: testExchange, + Base: &event.Base{ + Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } - err = bt.Statistic.SetupEventForTime(de) + err := bt.Statistic.SetEventForOffset(de) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -851,20 +1202,7 @@ func TestProcessFuturesFillEvent(t *testing.T) { t.Fatal(err) } exch.SetDefaults() - cfg, err := exch.GetDefaultConfig() - if err != nil { - t.Fatal(err) - } - err = exch.Setup(cfg) - if err != nil { - t.Fatal(err) - } - em.Add(exch) - f, err := funding.SetupFundingManager(em, false, true) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) @@ -878,43 +1216,7 @@ func TestProcessFuturesFillEvent(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, expectedError) } - err = f.AddItem(b) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - err = f.AddItem(quote) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - - spotBase, err := funding.CreateItem(testExchange, asset.Spot, cp.Base, decimal.Zero, decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - spotQuote, err := funding.CreateItem(testExchange, asset.Spot, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - spotPair, err := funding.CreatePair(spotBase, spotQuote) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - err = f.AddPair(spotPair) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } bt.exchangeManager = em - bt.Funding = f - - err = pt.SetupCurrencySettingsMap(&exchange.Settings{ - Exchange: exch, - Pair: cp, - Asset: a, - }) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v'", err, expectedError) - } - bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ Exchange: exch, Pair: cp, @@ -926,8 +1228,8 @@ func TestProcessFuturesFillEvent(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, expectedError) } tt := time.Now() - bt.Datas.Setup() - k := kline.DataFromKline{ + bt.DataHolder = data.NewHandlerHolder() + k := &kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, Pair: cp, @@ -942,7 +1244,7 @@ func TestProcessFuturesFillEvent(t *testing.T) { Volume: 1337, }}, }, - Base: data.Base{}, + Base: &data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), @@ -962,8 +1264,8 @@ func TestProcessFuturesFillEvent(t *testing.T) { }, } err = k.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } ev.Order = &gctorder.Detail{ Exchange: testExchange, @@ -975,16 +1277,338 @@ func TestProcessFuturesFillEvent(t *testing.T) { OrderID: "1", Date: time.Now(), } - bt.Datas.SetDataForCurrency(testExchange, a, cp, &k) + err = bt.DataHolder.SetDataForCurrency(testExchange, a, cp, k) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } err = bt.processFuturesFillEvent(ev, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } } +func TestCloseAllPositions(t *testing.T) { + t.Parallel() + bt, err := NewBacktester() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + pt := &portfolio.Portfolio{} + bt.Portfolio = pt + bt.Strategy = &dollarcostaverage.Strategy{} + + err = bt.CloseAllPositions() + if !errors.Is(err, errLiveOnly) { + t.Errorf("received '%v' expected '%v'", err, errLiveOnly) + } + + bt.shutdown = make(chan struct{}) + dc := &dataChecker{ + realOrders: true, + shutdown: make(chan bool), + } + bt.LiveDataHandler = dc + err = bt.CloseAllPositions() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + bt.shutdown = make(chan struct{}) + bt.Strategy = &binancecashandcarry.Strategy{} + err = bt.CloseAllPositions() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + bt.shutdown = make(chan struct{}) + bt.Portfolio = &fakeFolio{} + bt.Strategy = &fakeStrat{} + bt.Exchange = &exchange.Exchange{} + bt.Statistic = &fakeStats{} + bt.Reports = &fakeReport{} + bt.Funding = &fakeFunding{} + bt.DataHolder = &fakeDataHolder{} + dc.dataHolder = bt.DataHolder + dc.report = &report.Data{} + dc.funding = bt.Funding + cp := currency.NewPair(currency.BTC, currency.USD) + dc.sourcesToCheck = append(dc.sourcesToCheck, &liveDataSourceDataHandler{ + exchange: &binance.Binance{}, + exchangeName: testExchange, + asset: asset.Spot, + pair: cp, + underlyingPair: cp, + dataType: common.DataCandle, + dataRequestRetryTolerance: 1, + pairCandles: &kline.DataFromKline{ + Base: &data.Base{}, + Item: gctkline.Item{ + Exchange: testExchange, + Pair: cp, + UnderlyingPair: cp, + Asset: asset.Spot, + Interval: gctkline.OneMin, + Candles: []gctkline.Candle{ + { + Time: time.Now(), + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }, + }, + }, + }, + }) + err = bt.CloseAllPositions() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } +} + +func TestRunLive(t *testing.T) { + t.Parallel() + bt, err := NewBacktester() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + err = bt.RunLive() + if !errors.Is(err, errLiveOnly) { + t.Errorf("received '%v' expected '%v'", err, errLiveOnly) + } + + bt.Funding = &funding.FundManager{} + bt.Reports = &report.Data{} + + dc := &dataChecker{ + exchangeManager: bt.exchangeManager, + eventTimeout: defaultEventTimeout, + dataCheckInterval: defaultDataCheckInterval, + dataHolder: bt.DataHolder, + report: bt.Reports, + funding: bt.Funding, + shutdown: make(chan bool), + } + bt.LiveDataHandler = dc + err = bt.RunLive() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + close(bt.shutdown) + bt.wg.Wait() + bt.shutdown = make(chan struct{}) + + dc = &dataChecker{ + exchangeManager: bt.exchangeManager, + eventTimeout: defaultEventTimeout, + dataCheckInterval: defaultDataCheckInterval, + dataHolder: bt.DataHolder, + report: bt.Reports, + shutdown: make(chan bool), + dataUpdated: make(chan bool), + shutdownErr: make(chan bool), + funding: bt.Funding, + } + bt.LiveDataHandler = dc + cp := currency.NewPair(currency.BTC, currency.USD) + i := &gctkline.Item{ + Exchange: testExchange, + Pair: cp, + UnderlyingPair: cp, + Asset: asset.Spot, + Interval: gctkline.FifteenSecond, + Candles: []gctkline.Candle{ + { + Time: time.Now(), + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }, + }, + } + // AppendDataSource(exchange gctexchange.IBotExchange, interval gctkline.Interval, asset asset.Asset, pair, underlyingPair currency.Pair, dataType int64) error + setup := &liveDataSourceSetup{ + exchange: &binance.Binance{}, + interval: i.Interval, + asset: i.Asset, + pair: i.Pair, + underlyingPair: i.UnderlyingPair, + dataType: common.DataCandle, + } + err = dc.AppendDataSource(setup) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + bt.Reports = &report.Data{} + bt.Funding = &fakeFunding{} + bt.Statistic = &fakeStats{} + dc.started = 0 + err = bt.RunLive() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } +} + +func TestLiveLoop(t *testing.T) { + t.Parallel() + bt, err := NewBacktester() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + bt.Reports = &fakeReport{} + bt.Funding = &fakeFunding{} + bt.Statistic = &fakeStats{} + + dc := &dataChecker{ + dataUpdated: make(chan bool), + shutdownErr: make(chan bool), + shutdown: make(chan bool), + } + bt.LiveDataHandler = dc + + // dataUpdated case + var wg sync.WaitGroup + wg.Add(1) + go func() { + err = bt.liveCheck() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + wg.Done() + }() + dc.dataUpdated <- true + dc.shutdown <- true + wg.Wait() + + // shutdown from error case + wg.Add(1) + dc.started = 0 + go func() { + defer wg.Done() + err = bt.liveCheck() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + }() + dc.shutdownErr <- true + wg.Wait() + + // shutdown case + dc.started = 1 + bt.shutdown = make(chan struct{}) + wg.Add(1) + go func() { + defer wg.Done() + err = bt.liveCheck() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + }() + dc.shutdown <- true + wg.Wait() + + // backtester has shutdown + wg.Add(1) + bt.shutdown = make(chan struct{}) + go func() { + defer wg.Done() + err = bt.liveCheck() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + }() + close(bt.shutdown) + wg.Wait() +} + +func TestSetExchangeCredentials(t *testing.T) { + t.Parallel() + err := setExchangeCredentials(nil, nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + cfg := &config.Config{} + f := &binance.Binance{} + f.SetDefaults() + b := f.GetBase() + err = setExchangeCredentials(cfg, b) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + ld := &config.LiveData{} + cfg.DataSettings = config.DataSettings{ + LiveData: ld, + } + err = setExchangeCredentials(cfg, b) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + ld.RealOrders = true + err = setExchangeCredentials(cfg, b) + if !errors.Is(err, errIntervalUnset) { + t.Errorf("received '%v' expected '%v'", err, errIntervalUnset) + } + + cfg.DataSettings.Interval = gctkline.OneMin + err = setExchangeCredentials(cfg, b) + if !errors.Is(err, errNoCredsNoLive) { + t.Errorf("received '%v' expected '%v'", err, errNoCredsNoLive) + } + + cfg.DataSettings.LiveData.ExchangeCredentials = []config.Credentials{{}} + err = setExchangeCredentials(cfg, b) + if !errors.Is(err, gctexchange.ErrCredentialsAreEmpty) { + t.Errorf("received '%v' expected '%v'", err, gctexchange.ErrCredentialsAreEmpty) + } + + // requires valid credentials here to get complete coverage + // enter them here + cfg.DataSettings.LiveData.ExchangeCredentials = []config.Credentials{{ + Exchange: testExchange, + Keys: account.Credentials{ + Key: "test", + Secret: "test", + }, + }} + err = setExchangeCredentials(cfg, b) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } +} + +func TestGetFees(t *testing.T) { + t.Parallel() + _, _, err := getFees(context.Background(), nil, currency.EMPTYPAIR) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + f := &binance.Binance{} + f.SetDefaults() + _, _, err = getFees(context.Background(), f, currency.EMPTYPAIR) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf("received '%v' expected '%v'", err, currency.ErrCurrencyPairEmpty) + } + + maker, taker, err := getFees(context.Background(), f, currency.NewPair(currency.BTC, currency.USDT)) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, currency.ErrCurrencyPairEmpty) + } + if maker.IsZero() || taker.IsZero() { + t.Error("expected maker and taker fees") + } +} + func TestGenerateSummary(t *testing.T) { t.Parallel() - bt := &BackTest{} + bt := &BackTest{ + shutdown: make(chan struct{}), + } sum, err := bt.GenerateSummary() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) @@ -1014,7 +1638,9 @@ func TestGenerateSummary(t *testing.T) { func TestSetupMetaData(t *testing.T) { t.Parallel() - bt := &BackTest{} + bt := &BackTest{ + shutdown: make(chan struct{}), + } err := bt.SetupMetaData() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) @@ -1040,17 +1666,23 @@ func TestSetupMetaData(t *testing.T) { func TestIsRunning(t *testing.T) { t.Parallel() - bt := &BackTest{} + bt := &BackTest{ + shutdown: make(chan struct{}), + } if bt.IsRunning() { t.Errorf("received '%v' expected '%v'", true, false) } + bt.m.Lock() bt.MetaData.DateStarted = time.Now() + bt.m.Unlock() if !bt.IsRunning() { t.Errorf("received '%v' expected '%v'", false, true) } + bt.m.Lock() bt.MetaData.Closed = true + bt.m.Unlock() if bt.IsRunning() { t.Errorf("received '%v' expected '%v'", true, false) } @@ -1063,17 +1695,23 @@ func TestIsRunning(t *testing.T) { func TestHasRan(t *testing.T) { t.Parallel() - bt := &BackTest{} + bt := &BackTest{ + shutdown: make(chan struct{}), + } if bt.HasRan() { t.Errorf("received '%v' expected '%v'", true, false) } + bt.m.Lock() bt.MetaData.DateStarted = time.Now() + bt.m.Unlock() if bt.HasRan() { t.Errorf("received '%v' expected '%v'", false, true) } + bt.m.Lock() bt.MetaData.Closed = true + bt.m.Unlock() if !bt.HasRan() { t.Errorf("received '%v' expected '%v'", true, false) } @@ -1154,55 +1792,83 @@ func TestMatchesID(t *testing.T) { func TestExecuteStrategy(t *testing.T) { t.Parallel() - bt := &BackTest{} + bt := &BackTest{ + DataHolder: &fakeDataHolder{}, + Strategy: &fakeStrat{}, + Portfolio: &fakeFolio{}, + Statistic: &fakeStats{}, + Reports: &fakeReport{}, + Funding: &fakeFunding{}, + EventQueue: &eventholder.Holder{}, + shutdown: make(chan struct{}), + } err := bt.ExecuteStrategy(false) if !errors.Is(err, errNotSetup) { t.Errorf("received '%v' expected '%v'", err, errNotSetup) } + id, err := uuid.NewV4() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + bt.m.Lock() + bt.MetaData.ID = id bt.MetaData.DateLoaded = time.Now() bt.MetaData.DateStarted = time.Now() + bt.m.Unlock() err = bt.ExecuteStrategy(false) - if !errors.Is(err, errRunIsRunning) { - t.Errorf("received '%v' expected '%v'", err, errRunIsRunning) + if !errors.Is(err, errTaskIsRunning) { + t.Errorf("received '%v' expected '%v'", err, errTaskIsRunning) } - strat1 := filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat") - cfg, err := config.ReadStrategyConfigFromFile(strat1) + err = bt.Stop() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - bt, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - bt.Stop() - err = bt.ExecuteStrategy(true) if !errors.Is(err, errAlreadyRan) { t.Errorf("received '%v' expected '%v'", err, errAlreadyRan) } - strat2 := filepath.Join("..", "config", "strategyexamples", "dca-candles-live.strat") - cfg, err = config.ReadStrategyConfigFromFile(strat2) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - bt, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } + bt.m.Lock() + bt.MetaData.DateStarted = time.Time{} + bt.MetaData.DateEnded = time.Time{} + bt.MetaData.Closed = false + bt.shutdown = make(chan struct{}) + bt.m.Unlock() + err = bt.ExecuteStrategy(true) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } + bt.m.Lock() bt.MetaData.DateStarted = time.Time{} + bt.MetaData.DateEnded = time.Time{} + bt.MetaData.Closed = false + bt.shutdown = make(chan struct{}) + bt.m.Unlock() err = bt.ExecuteStrategy(false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - bt.Stop() + + bt.m.Lock() + bt.MetaData.LiveTesting = true + bt.MetaData.DateStarted = time.Time{} + bt.MetaData.DateEnded = time.Time{} + bt.MetaData.Closed = false + bt.shutdown = make(chan struct{}) + bt.m.Unlock() + err = bt.ExecuteStrategy(true) + if !errors.Is(err, errCannotHandleRequest) { + t.Errorf("received '%v' expected '%v'", err, errCannotHandleRequest) + } + + err = bt.ExecuteStrategy(false) + if !errors.Is(err, errLiveOnly) { + t.Errorf("received '%v' expected '%v'", err, errLiveOnly) + } bt = nil err = bt.ExecuteStrategy(false) @@ -1210,3 +1876,138 @@ func TestExecuteStrategy(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } } + +func TestNewBacktesterFromConfigs(t *testing.T) { + t.Parallel() + _, err := NewBacktesterFromConfigs(nil, nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + strat1 := filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat") + cfg, err := config.ReadStrategyConfigFromFile(strat1) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + dc, err := config.GenerateDefaultConfig() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + _, err = NewBacktesterFromConfigs(cfg, nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + _, err = NewBacktesterFromConfigs(nil, dc) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + bt, err := NewBacktesterFromConfigs(cfg, dc) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if bt.MetaData.DateLoaded.IsZero() { + t.Errorf("received '%v' expected '%v'", bt.MetaData.DateLoaded, "a date") + } +} + +func TestProcessSingleDataEvent(t *testing.T) { + t.Parallel() + bt := &BackTest{ + Strategy: &fakeStrat{}, + Portfolio: &fakeFolio{}, + Statistic: &fakeStats{}, + Reports: &fakeReport{}, + Funding: &fakeFunding{}, + DataHolder: &data.HandlerHolder{}, + EventQueue: &eventholder.Holder{}, + } + + err := bt.processSingleDataEvent(nil, nil) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received '%v' expected '%v'", err, common.ErrNilEvent) + } + cp := currency.NewPair(currency.BTC, currency.USDT) + a := asset.Spot + ev := &evkline.Kline{ + Base: &event.Base{ + Exchange: testExchange, + Time: time.Now(), + Interval: gctkline.FifteenMin, + CurrencyPair: cp, + AssetType: a, + }, + } + err = bt.processSingleDataEvent(ev, nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true, false) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + quote, err := funding.CreateItem(testExchange, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + collateral, err := funding.CreateCollateral(b, quote) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + bt.Funding = f + tt := time.Now() + bt.DataHolder = data.NewHandlerHolder() + k := &kline.DataFromKline{ + Item: gctkline.Item{ + Exchange: testExchange, + Pair: cp, + Asset: a, + Interval: gctkline.FifteenMin, + Candles: []gctkline.Candle{{ + Time: tt, + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }}, + }, + Base: &data.Base{}, + RangeHolder: &gctkline.IntervalRangeHolder{ + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + Ranges: []gctkline.IntervalRange{ + { + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + Intervals: []gctkline.IntervalData{ + { + Start: gctkline.CreateIntervalTime(tt), + End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), + HasData: true, + }, + }, + }, + }, + }, + } + err = k.Load() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + err = bt.DataHolder.SetDataForCurrency(testExchange, a, cp, k) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + err = bt.processSingleDataEvent(ev, collateral) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } +} diff --git a/backtester/engine/backtest_types.go b/backtester/engine/backtest_types.go index 04f7a2ca..94a9b25d 100644 --- a/backtester/engine/backtest_types.go +++ b/backtester/engine/backtest_types.go @@ -18,57 +18,61 @@ import ( ) var ( - errNilConfig = errors.New("unable to setup backtester with nil config") - errAmbiguousDataSource = errors.New("ambiguous settings received. Only one data type can be set") - errNoDataSource = errors.New("no data settings set in config") - errIntervalUnset = errors.New("candle interval unset") - errUnhandledDatatype = errors.New("unhandled datatype") - errLiveDataTimeout = errors.New("no data returned in 5 minutes, shutting down") - errNilData = errors.New("nil data received") - errNilExchange = errors.New("nil exchange received") - errLiveUSDTrackingNotSupported = errors.New("USD tracking not supported for live data") - errNotSetup = errors.New("backtesting run not setup") + errNilConfig = errors.New("unable to setup backtester with nil config") + errAmbiguousDataSource = errors.New("ambiguous settings received. Only one data type can be set") + errNoDataSource = errors.New("no data settings set in config") + errIntervalUnset = errors.New("candle interval unset") + errUnhandledDatatype = errors.New("unhandled datatype") + errNilData = errors.New("nil data received") + errLiveOnly = errors.New("close all positions is only supported by live data type") + errNotSetup = errors.New("backtesting task not setup") ) // BackTest is the main holder of all backtesting functionality type BackTest struct { - m sync.Mutex - hasHandledEvent bool - MetaData RunMetaData - shutdown chan struct{} - Datas data.Holder - Strategy strategies.Handler - Portfolio portfolio.Handler - Exchange exchange.ExecutionHandler - Statistic statistics.Handler - EventQueue eventholder.EventHolder - Reports report.Handler - Funding funding.IFundingManager - exchangeManager *engine.ExchangeManager - orderManager *engine.OrderManager - databaseManager *engine.DatabaseConnectionManager + m sync.Mutex + wg sync.WaitGroup + verbose bool + hasProcessedAnEvent bool + hasShutdown bool + shutdown chan struct{} + MetaData TaskMetaData + DataHolder data.Holder + LiveDataHandler Handler + Strategy strategies.Handler + Portfolio portfolio.Handler + Exchange exchange.ExecutionHandler + Statistic statistics.Handler + EventQueue eventholder.EventHolder + Reports report.Handler + Funding funding.IFundingManager + exchangeManager *engine.ExchangeManager + orderManager *engine.OrderManager + databaseManager *engine.DatabaseConnectionManager + hasProcessedDataAtOffset map[int64]bool } -// RunSummary holds details of a BackTest +// TaskSummary holds details of a BackTest // rather than passing entire contents around -type RunSummary struct { - MetaData RunMetaData +type TaskSummary struct { + MetaData TaskMetaData } -// RunMetaData contains details about a run such as when it was loaded -type RunMetaData struct { - ID uuid.UUID - Strategy string - DateLoaded time.Time - DateStarted time.Time - DateEnded time.Time - Closed bool - LiveTesting bool - RealOrders bool +// TaskMetaData contains details about a run such as when it was loaded +type TaskMetaData struct { + ID uuid.UUID + Strategy string + DateLoaded time.Time + DateStarted time.Time + DateEnded time.Time + Closed bool + ClosePositionsOnStop bool + LiveTesting bool + RealOrders bool } -// RunManager contains all backtesting/livestrategy runs -type RunManager struct { - m sync.Mutex - runs []*BackTest +// TaskManager contains all strategy tasks +type TaskManager struct { + m sync.Mutex + tasks []*BackTest } diff --git a/backtester/engine/fakeinterfaces_test.go b/backtester/engine/fakeinterfaces_test.go new file mode 100644 index 00000000..0fe32186 --- /dev/null +++ b/backtester/engine/fakeinterfaces_test.go @@ -0,0 +1,338 @@ +package engine + +import ( + "time" + + "github.com/gofrs/uuid" + "github.com/shopspring/decimal" + "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" + "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + "github.com/thrasher-corp/gocryptotrader/backtester/funding" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" + gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" +) + +// Overriding functions +// these are designed to override interface implementations +// so there is less requirement gathering per test as the functions are +// tested in their own package + +type fakeFolio struct{} + +func (f fakeFolio) GetLatestComplianceSnapshot(string, asset.Item, currency.Pair) (*compliance.Snapshot, error) { + return &compliance.Snapshot{}, nil +} + +func (f fakeFolio) GetPositions(common.Event) ([]gctorder.Position, error) { + return nil, nil +} + +func (f fakeFolio) SetHoldingsForEvent(funding.IFundReader, common.Event) error { + return nil +} + +func (f fakeFolio) SetHoldingsForTimestamp(*holdings.Holding) error { + return nil +} + +func (f fakeFolio) OnSignal(signal.Event, *exchange.Settings, funding.IFundReserver) (*order.Order, error) { + return nil, nil +} + +func (f fakeFolio) OnFill(fill.Event, funding.IFundReleaser) (fill.Event, error) { + return nil, nil +} + +func (f fakeFolio) GetLatestOrderSnapshotForEvent(common.Event) (compliance.Snapshot, error) { + return compliance.Snapshot{}, nil +} + +func (f fakeFolio) GetLatestOrderSnapshots() ([]compliance.Snapshot, error) { + return nil, nil +} + +func (f fakeFolio) ViewHoldingAtTimePeriod(common.Event) (*holdings.Holding, error) { + return nil, nil +} + +func (f fakeFolio) UpdateHoldings(data.Event, funding.IFundReleaser) error { + return nil +} + +func (f fakeFolio) GetComplianceManager(string, asset.Item, currency.Pair) (*compliance.Manager, error) { + return nil, nil +} + +func (f fakeFolio) TrackFuturesOrder(fill.Event, funding.IFundReleaser) (*portfolio.PNLSummary, error) { + return &portfolio.PNLSummary{}, nil +} + +func (f fakeFolio) UpdatePNL(common.Event, decimal.Decimal) error { + return nil +} + +func (f fakeFolio) GetLatestPNLForEvent(common.Event) (*portfolio.PNLSummary, error) { + return &portfolio.PNLSummary{}, nil +} + +func (f fakeFolio) GetLatestPNLs() []portfolio.PNLSummary { + return nil +} + +func (f fakeFolio) CheckLiquidationStatus(data.Event, funding.ICollateralReader, *portfolio.PNLSummary) error { + return nil +} + +func (f fakeFolio) CreateLiquidationOrdersForExchange(data.Event, funding.IFundingManager) ([]order.Event, error) { + return nil, nil +} + +func (f fakeFolio) GetLatestHoldingsForAllCurrencies() []holdings.Holding { + return nil +} + +func (f fakeFolio) Reset() error { + return nil +} + +type fakeReport struct{} + +func (f fakeReport) GenerateReport() error { + return nil +} + +func (f fakeReport) SetKlineData(*gctkline.Item) error { + return nil +} + +func (f fakeReport) UseDarkMode(bool) {} + +type fakeStats struct{} + +func (f *fakeStats) SetStrategyName(string) { + +} + +func (f *fakeStats) SetEventForOffset(common.Event) error { + return nil +} + +func (f *fakeStats) AddHoldingsForTime(*holdings.Holding) error { + return nil +} + +func (f *fakeStats) AddComplianceSnapshotForTime(*compliance.Snapshot, common.Event) error { + return nil +} + +func (f *fakeStats) CalculateAllResults() error { + return nil +} + +func (f *fakeStats) Reset() error { + return nil +} + +func (f *fakeStats) Serialise() (string, error) { + return "", nil +} + +func (f *fakeStats) AddPNLForTime(*portfolio.PNLSummary) error { + return nil +} + +func (f *fakeStats) CreateLog(common.Event) (string, error) { + return "", nil +} + +type fakeDataHolder struct{} + +func (f fakeDataHolder) Setup() { +} + +func (f fakeDataHolder) SetDataForCurrency(string, asset.Item, currency.Pair, data.Handler) error { + return nil +} + +func (f fakeDataHolder) GetAllData() ([]data.Handler, error) { + cp := currency.NewPair(currency.BTC, currency.USD) + return []data.Handler{&kline.DataFromKline{ + Base: &data.Base{}, + Item: gctkline.Item{ + Exchange: testExchange, + Pair: cp, + UnderlyingPair: cp, + Asset: asset.Spot, + Interval: gctkline.OneMin, + Candles: []gctkline.Candle{ + { + Time: time.Now(), + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }, + }, + SourceJobID: uuid.UUID{}, + ValidationJobID: uuid.UUID{}, + }, + RangeHolder: &gctkline.IntervalRangeHolder{}, + }, + }, nil +} + +func (f fakeDataHolder) GetDataForCurrency(common.Event) (data.Handler, error) { + return nil, nil +} + +func (f fakeDataHolder) Reset() error { + return nil +} + +type fakeFunding struct { + hasFutures bool +} + +func (f fakeFunding) UpdateCollateralForEvent(common.Event, bool) error { + return nil +} + +func (f fakeFunding) UpdateAllCollateral(bool, bool) error { + return nil +} + +func (f fakeFunding) UpdateFundingFromLiveData(bool) error { + return nil +} + +func (f fakeFunding) SetFunding(string, asset.Item, *account.Balance, bool) error { + return nil +} + +func (f fakeFunding) Reset() error { + return nil +} + +func (f fakeFunding) IsUsingExchangeLevelFunding() bool { + return true +} + +func (f fakeFunding) GetFundingForEvent(common.Event) (funding.IFundingPair, error) { + return &funding.SpotPair{}, nil +} + +func (f fakeFunding) Transfer(decimal.Decimal, *funding.Item, *funding.Item, bool) error { + return nil +} + +func (f fakeFunding) GenerateReport() (*funding.Report, error) { + return nil, nil +} + +func (f fakeFunding) AddUSDTrackingData(*kline.DataFromKline) error { + return nil +} + +func (f fakeFunding) CreateSnapshot(time.Time) error { + return nil +} + +func (f fakeFunding) USDTrackingDisabled() bool { + return false +} + +func (f fakeFunding) Liquidate(common.Event) error { + return nil +} + +func (f fakeFunding) GetAllFunding() ([]funding.BasicItem, error) { + return nil, nil +} + +func (f fakeFunding) UpdateCollateral() error { + return nil +} + +func (f fakeFunding) HasFutures() bool { + return f.hasFutures +} + +func (f fakeFunding) HasExchangeBeenLiquidated(common.Event) bool { + return false +} + +func (f fakeFunding) RealisePNL(string, asset.Item, currency.Code, decimal.Decimal) error { + return nil +} + +type fakeStrat struct{} + +func (f fakeStrat) Name() string { + return "fake" +} + +func (f fakeStrat) Description() string { + return "fake" +} + +func (f fakeStrat) OnSignal(data.Handler, funding.IFundingTransferer, portfolio.Handler) (signal.Event, error) { + return nil, nil +} + +func (f fakeStrat) OnSimultaneousSignals([]data.Handler, funding.IFundingTransferer, portfolio.Handler) ([]signal.Event, error) { + return nil, nil +} + +func (f fakeStrat) UsingSimultaneousProcessing() bool { + return true +} + +func (f fakeStrat) SupportsSimultaneousProcessing() bool { + return true +} + +func (f fakeStrat) SetSimultaneousProcessing(bool) {} + +func (f fakeStrat) SetCustomSettings(map[string]interface{}) error { + return nil +} + +func (f fakeStrat) SetDefaults() {} + +func (f fakeStrat) CloseAllPositions([]holdings.Holding, []data.Event) ([]signal.Event, error) { + return []signal.Event{ + &signal.Signal{ + Base: &event.Base{ + Offset: 1, + Exchange: testExchange, + Time: time.Now(), + Interval: gctkline.FifteenSecond, + CurrencyPair: currency.NewPair(currency.BTC, currency.USD), + UnderlyingPair: currency.NewPair(currency.BTC, currency.USD), + AssetType: asset.Spot, + }, + OpenPrice: leet, + HighPrice: leet, + LowPrice: leet, + ClosePrice: leet, + Volume: leet, + BuyLimit: leet, + SellLimit: leet, + Amount: leet, + Direction: gctorder.Buy, + }, + }, nil +} diff --git a/backtester/engine/grpcserver.go b/backtester/engine/grpcserver.go index cb223a27..0278946e 100644 --- a/backtester/engine/grpcserver.go +++ b/backtester/engine/grpcserver.go @@ -9,13 +9,13 @@ import ( "net/http" "path/filepath" "strings" + "time" "github.com/gofrs/uuid" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/btrpc" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/config" gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" @@ -23,6 +23,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/database/drivers" gctengine "github.com/thrasher-corp/gocryptotrader/engine" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/gctrpc/auth" @@ -42,16 +43,16 @@ var ( type GRPCServer struct { btrpc.BacktesterServiceServer config *config.BacktesterConfig - manager *RunManager + manager *TaskManager } // SetupRPCServer sets up the gRPC server -func SetupRPCServer(cfg *config.BacktesterConfig, manager *RunManager) (*GRPCServer, error) { +func SetupRPCServer(cfg *config.BacktesterConfig, manager *TaskManager) (*GRPCServer, error) { if cfg == nil { - return nil, fmt.Errorf("%w backtester config", common.ErrNilArguments) + return nil, fmt.Errorf("%w backtester config", gctcommon.ErrNilPointer) } if manager == nil { - return nil, fmt.Errorf("%w run manager", common.ErrNilArguments) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } return &GRPCServer{ config: cfg, @@ -100,7 +101,7 @@ func StartRPCServer(server *GRPCServer) error { // StartRPCRESTProxy starts a gRPC proxy func (s *GRPCServer) StartRPCRESTProxy() error { - log.Debugf(log.GRPCSys, "GRPC proxy server support enabled. Starting gRPC proxy server on http://%v.\n", s.config.GRPC.GRPCProxyListenAddress) + log.Debugf(log.GRPCSys, "GRPC proxy server support enabled. Starting gRPC proxy server on %v\n", s.config.GRPC.GRPCProxyListenAddress) targetDir := utils.GetTLSDir(s.config.GRPC.TLSDir) creds, err := credentials.NewClientTLSFromFile(filepath.Join(targetDir, "cert.pem"), "") if err != nil { @@ -121,7 +122,13 @@ func (s *GRPCServer) StartRPCRESTProxy() error { } go func() { - if err = http.ListenAndServe(s.config.GRPC.GRPCProxyListenAddress, mux); err != nil { + server := &http.Server{ + Addr: s.config.GRPC.GRPCProxyListenAddress, + ReadHeaderTimeout: time.Minute, + ReadTimeout: time.Minute, + } + + if err = server.ListenAndServe(); err != nil { log.Errorf(log.GRPCSys, "GRPC proxy failed to server: %s\n", err) } }() @@ -161,25 +168,25 @@ func (s *GRPCServer) authenticateClient(ctx context.Context) (context.Context, e return ctx, nil } -// convertSummary converts a run summary into a RPC format -func convertSummary(run *RunSummary) *btrpc.RunSummary { - runSummary := &btrpc.RunSummary{ - Id: run.MetaData.ID.String(), - StrategyName: run.MetaData.Strategy, - Closed: run.MetaData.Closed, - LiveTesting: run.MetaData.LiveTesting, - RealOrders: run.MetaData.RealOrders, +// convertSummary converts a task summary into a RPC format +func convertSummary(task *TaskSummary) *btrpc.TaskSummary { + taskSummary := &btrpc.TaskSummary{ + Id: task.MetaData.ID.String(), + StrategyName: task.MetaData.Strategy, + Closed: task.MetaData.Closed, + LiveTesting: task.MetaData.LiveTesting, + RealOrders: task.MetaData.RealOrders, } - if !run.MetaData.DateStarted.IsZero() { - runSummary.DateStarted = run.MetaData.DateStarted.Format(gctcommon.SimpleTimeFormatWithTimezone) + if !task.MetaData.DateStarted.IsZero() { + taskSummary.DateStarted = task.MetaData.DateStarted.Format(gctcommon.SimpleTimeFormatWithTimezone) } - if !run.MetaData.DateLoaded.IsZero() { - runSummary.DateLoaded = run.MetaData.DateLoaded.Format(gctcommon.SimpleTimeFormatWithTimezone) + if !task.MetaData.DateLoaded.IsZero() { + taskSummary.DateLoaded = task.MetaData.DateLoaded.Format(gctcommon.SimpleTimeFormatWithTimezone) } - if !run.MetaData.DateEnded.IsZero() { - runSummary.DateEnded = run.MetaData.DateEnded.Format(gctcommon.SimpleTimeFormatWithTimezone) + if !task.MetaData.DateEnded.IsZero() { + taskSummary.DateEnded = task.MetaData.DateEnded.Format(gctcommon.SimpleTimeFormatWithTimezone) } - return runSummary + return taskSummary } // ExecuteStrategyFromFile will backtest a strategy from the filepath provided @@ -188,13 +195,13 @@ func (s *GRPCServer) ExecuteStrategyFromFile(_ context.Context, request *btrpc.E return nil, fmt.Errorf("%w server config", gctcommon.ErrNilPointer) } if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } if request == nil { - return nil, fmt.Errorf("%w nil request", common.ErrNilArguments) + return nil, fmt.Errorf("%w request", gctcommon.ErrNilPointer) } if request.DoNotRunImmediately && request.DoNotStore { - return nil, fmt.Errorf("%w cannot manage a run with both dnr and dns", errCannotHandleRequest) + return nil, fmt.Errorf("%w cannot manage a task with both dnr and dns", errCannotHandleRequest) } dir := request.StrategyFilePath @@ -208,7 +215,7 @@ func (s *GRPCServer) ExecuteStrategyFromFile(_ context.Context, request *btrpc.E return nil, err } if cfg == nil { - err = fmt.Errorf("%w backtester config", common.ErrNilArguments) + err = fmt.Errorf("%w backtester config", gctcommon.ErrNilPointer) return nil, err } @@ -217,13 +224,13 @@ func (s *GRPCServer) ExecuteStrategyFromFile(_ context.Context, request *btrpc.E s.config.Report.TemplatePath = "" } - bt, err := NewFromConfig(cfg, s.config.Report.TemplatePath, s.config.Report.OutputPath, s.config.Verbose) + bt, err := NewBacktesterFromConfigs(cfg, s.config) if err != nil { return nil, err } if !request.DoNotStore { - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if err != nil { return nil, err } @@ -240,7 +247,7 @@ func (s *GRPCServer) ExecuteStrategyFromFile(_ context.Context, request *btrpc.E return nil, err } return &btrpc.ExecuteStrategyResponse{ - Run: convertSummary(btSum), + Task: convertSummary(btSum), }, nil } @@ -252,13 +259,13 @@ func (s *GRPCServer) ExecuteStrategyFromConfig(_ context.Context, request *btrpc return nil, fmt.Errorf("%w server config", gctcommon.ErrNilPointer) } if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } if request == nil || request.Config == nil { - return nil, fmt.Errorf("%w nil request", common.ErrNilArguments) + return nil, fmt.Errorf("%w request", gctcommon.ErrNilPointer) } if request.DoNotRunImmediately && request.DoNotStore { - return nil, fmt.Errorf("%w cannot manage a run with both dnr and dns", errCannotHandleRequest) + return nil, fmt.Errorf("%w cannot manage a task with both dnr and dns", errCannotHandleRequest) } rfr, err := decimal.NewFromString(request.Config.StatisticSettings.RiskFreeRate) @@ -518,13 +525,28 @@ func (s *GRPCServer) ExecuteStrategyFromConfig(_ context.Context, request *btrpc } var liveData *config.LiveData if request.Config.DataSettings.LiveData != nil { + creds := make([]config.Credentials, len(request.Config.DataSettings.LiveData.Credentials)) + for i := range request.Config.DataSettings.LiveData.Credentials { + creds[i] = config.Credentials{ + Exchange: request.Config.DataSettings.LiveData.Credentials[i].Exchange, + Keys: account.Credentials{ + Key: request.Config.DataSettings.LiveData.Credentials[i].Keys.Key, + Secret: request.Config.DataSettings.LiveData.Credentials[i].Keys.Secret, + ClientID: request.Config.DataSettings.LiveData.Credentials[i].Keys.ClientId, + PEMKey: request.Config.DataSettings.LiveData.Credentials[i].Keys.PemKey, + SubAccount: request.Config.DataSettings.LiveData.Credentials[i].Keys.SubAccount, + OneTimePassword: request.Config.DataSettings.LiveData.Credentials[i].Keys.OneTimePassword, + }, + } + } liveData = &config.LiveData{ - APIKeyOverride: request.Config.DataSettings.LiveData.ApiKeyOverride, - APISecretOverride: request.Config.DataSettings.LiveData.ApiSecretOverride, - APIClientIDOverride: request.Config.DataSettings.LiveData.ApiClientIdOverride, - API2FAOverride: request.Config.DataSettings.LiveData.Api_2FaOverride, - APISubAccountOverride: request.Config.DataSettings.LiveData.ApiSubAccountOverride, - RealOrders: request.Config.DataSettings.LiveData.UseRealOrders, + NewEventTimeout: time.Duration(request.Config.DataSettings.LiveData.NewEventTimeout), + DataCheckTimer: time.Duration(request.Config.DataSettings.LiveData.DataCheckTimer), + RealOrders: request.Config.DataSettings.LiveData.RealOrders, + ClosePositionsOnStop: request.Config.DataSettings.LiveData.ClosePositionsOnStop, + DataRequestRetryTolerance: request.Config.DataSettings.LiveData.DataRequestRetryTolerance, + DataRequestRetryWaitTime: time.Duration(request.Config.DataSettings.LiveData.DataRequestRetryWaitTime), + ExchangeCredentials: creds, } } var csvData *config.CSVData @@ -584,13 +606,13 @@ func (s *GRPCServer) ExecuteStrategyFromConfig(_ context.Context, request *btrpc s.config.Report.TemplatePath = "" } - bt, err := NewFromConfig(cfg, s.config.Report.TemplatePath, s.config.Report.OutputPath, s.config.Verbose) + bt, err := NewBacktesterFromConfigs(cfg, s.config) if err != nil { return nil, err } if !request.DoNotStore { - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if err != nil { return nil, err } @@ -607,157 +629,157 @@ func (s *GRPCServer) ExecuteStrategyFromConfig(_ context.Context, request *btrpc return nil, err } return &btrpc.ExecuteStrategyResponse{ - Run: convertSummary(btSum), + Task: convertSummary(btSum), }, nil } -// ListAllRuns returns all backtesting/livestrategy runs managed by the server -func (s *GRPCServer) ListAllRuns(_ context.Context, _ *btrpc.ListAllRunsRequest) (*btrpc.ListAllRunsResponse, error) { +// ListAllTasks returns all strategy tasks managed by the server +func (s *GRPCServer) ListAllTasks(_ context.Context, _ *btrpc.ListAllTasksRequest) (*btrpc.ListAllTasksResponse, error) { if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } list, err := s.manager.List() if err != nil { return nil, err } - response := make([]*btrpc.RunSummary, len(list)) + response := make([]*btrpc.TaskSummary, len(list)) for i := range list { response[i] = convertSummary(list[i]) } - return &btrpc.ListAllRunsResponse{ - Runs: response, + return &btrpc.ListAllTasksResponse{ + Tasks: response, }, nil } -// StopRun stops a backtest/livestrategy run in its tracks -func (s *GRPCServer) StopRun(_ context.Context, req *btrpc.StopRunRequest) (*btrpc.StopRunResponse, error) { +// StopTask stops a strategy task in its tracks +func (s *GRPCServer) StopTask(_ context.Context, req *btrpc.StopTaskRequest) (*btrpc.StopTaskResponse, error) { if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } if req == nil { - return nil, fmt.Errorf("%w StopRunRequest", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w StopTaskRequest", gctcommon.ErrNilPointer) } id, err := uuid.FromString(req.Id) if err != nil { return nil, err } - run, err := s.manager.GetSummary(id) + task, err := s.manager.GetSummary(id) if err != nil { return nil, err } - err = s.manager.StopRun(id) + err = s.manager.StopTask(id) if err != nil { return nil, err } - return &btrpc.StopRunResponse{ - StoppedRun: convertSummary(run), + return &btrpc.StopTaskResponse{ + StoppedTask: convertSummary(task), }, nil } -// StopAllRuns stops all backtest/livestrategy runs in its tracks -func (s *GRPCServer) StopAllRuns(_ context.Context, _ *btrpc.StopAllRunsRequest) (*btrpc.StopAllRunsResponse, error) { +// StopAllTasks stops all strategy tasks in its tracks +func (s *GRPCServer) StopAllTasks(_ context.Context, _ *btrpc.StopAllTasksRequest) (*btrpc.StopAllTasksResponse, error) { if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } - stopped, err := s.manager.StopAllRuns() + stopped, err := s.manager.StopAllTasks() if err != nil { return nil, err } - stoppedRuns := make([]*btrpc.RunSummary, len(stopped)) + stoppedTasks := make([]*btrpc.TaskSummary, len(stopped)) for i := range stopped { - stoppedRuns[i] = convertSummary(stopped[i]) + stoppedTasks[i] = convertSummary(stopped[i]) } - return &btrpc.StopAllRunsResponse{ - RunsStopped: stoppedRuns, + return &btrpc.StopAllTasksResponse{ + TasksStopped: stoppedTasks, }, nil } -// StartRun starts a backtest/livestrategy that was set to not start automatically -func (s *GRPCServer) StartRun(_ context.Context, req *btrpc.StartRunRequest) (*btrpc.StartRunResponse, error) { +// StartTask starts a strategy that was set to not start automatically +func (s *GRPCServer) StartTask(_ context.Context, req *btrpc.StartTaskRequest) (*btrpc.StartTaskResponse, error) { if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } if req == nil { - return nil, fmt.Errorf("%w StartRunRequest", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w StartTaskRequest", gctcommon.ErrNilPointer) } id, err := uuid.FromString(req.Id) if err != nil { return nil, err } - err = s.manager.StartRun(id) + err = s.manager.StartTask(id) if err != nil { return nil, err } - return &btrpc.StartRunResponse{ + return &btrpc.StartTaskResponse{ Started: true, }, nil } -// StartAllRuns starts all backtest/livestrategy runs -func (s *GRPCServer) StartAllRuns(_ context.Context, _ *btrpc.StartAllRunsRequest) (*btrpc.StartAllRunsResponse, error) { +// StartAllTasks starts all strategy tasks +func (s *GRPCServer) StartAllTasks(_ context.Context, _ *btrpc.StartAllTasksRequest) (*btrpc.StartAllTasksResponse, error) { if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } - started, err := s.manager.StartAllRuns() + started, err := s.manager.StartAllTasks() if err != nil { return nil, err } - startedRuns := make([]string, len(started)) + startedTasks := make([]string, len(started)) for i := range started { - startedRuns[i] = started[i].String() + startedTasks[i] = started[i].String() } - return &btrpc.StartAllRunsResponse{ - RunsStarted: startedRuns, + return &btrpc.StartAllTasksResponse{ + TasksStarted: startedTasks, }, nil } -// ClearRun removes a run from memory, but only if it is not running -func (s *GRPCServer) ClearRun(_ context.Context, req *btrpc.ClearRunRequest) (*btrpc.ClearRunResponse, error) { +// ClearTask removes a task from memory, but only if it is not running +func (s *GRPCServer) ClearTask(_ context.Context, req *btrpc.ClearTaskRequest) (*btrpc.ClearTaskResponse, error) { if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } if req == nil { - return nil, fmt.Errorf("%w ClearRunRequest", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w ClearTaskRequest", gctcommon.ErrNilPointer) } id, err := uuid.FromString(req.Id) if err != nil { return nil, err } - run, err := s.manager.GetSummary(id) + task, err := s.manager.GetSummary(id) if err != nil { return nil, err } - err = s.manager.ClearRun(id) + err = s.manager.ClearTask(id) if err != nil { return nil, err } - return &btrpc.ClearRunResponse{ - ClearedRun: convertSummary(run), + return &btrpc.ClearTaskResponse{ + ClearedTask: convertSummary(task), }, nil } -// ClearAllRuns removes all runs from memory, but only if they are not running -func (s *GRPCServer) ClearAllRuns(_ context.Context, _ *btrpc.ClearAllRunsRequest) (*btrpc.ClearAllRunsResponse, error) { +// ClearAllTasks removes all tasks from memory, but only if they are not running +func (s *GRPCServer) ClearAllTasks(_ context.Context, _ *btrpc.ClearAllTasksRequest) (*btrpc.ClearAllTasksResponse, error) { if s.manager == nil { - return nil, fmt.Errorf("%w run manager", gctcommon.ErrNilPointer) + return nil, fmt.Errorf("%w task manager", gctcommon.ErrNilPointer) } - clearedRuns, remainingRuns, err := s.manager.ClearAllRuns() + clearedTasks, remainingTasks, err := s.manager.ClearAllTasks() if err != nil { return nil, err } - clearedResponse := make([]*btrpc.RunSummary, len(clearedRuns)) - for i := range clearedRuns { - clearedResponse[i] = convertSummary(clearedRuns[i]) + clearedResponse := make([]*btrpc.TaskSummary, len(clearedTasks)) + for i := range clearedTasks { + clearedResponse[i] = convertSummary(clearedTasks[i]) } - remainingResponse := make([]*btrpc.RunSummary, len(remainingRuns)) - for i := range remainingRuns { - remainingResponse[i] = convertSummary(remainingRuns[i]) + remainingResponse := make([]*btrpc.TaskSummary, len(remainingTasks)) + for i := range remainingTasks { + remainingResponse[i] = convertSummary(remainingTasks[i]) } - return &btrpc.ClearAllRunsResponse{ - ClearedRuns: clearedResponse, - RemainingRuns: remainingResponse, + return &btrpc.ClearAllTasksResponse{ + ClearedTasks: clearedResponse, + RemainingTasks: remainingResponse, }, nil } diff --git a/backtester/engine/grpcserver_test.go b/backtester/engine/grpcserver_test.go index 5b5a7707..a9584d54 100644 --- a/backtester/engine/grpcserver_test.go +++ b/backtester/engine/grpcserver_test.go @@ -14,7 +14,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/binancecashandcarry" gctcommon "github.com/thrasher-corp/gocryptotrader/common" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -39,10 +39,10 @@ func TestExecuteStrategyFromFile(t *testing.T) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() + s.manager = NewTaskManager() _, err = s.ExecuteStrategyFromFile(context.Background(), nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } _, err = s.ExecuteStrategyFromFile(context.Background(), &btrpc.ExecuteStrategyFromFileRequest{}) @@ -85,10 +85,10 @@ func TestExecuteStrategyFromConfig(t *testing.T) { } s.config.Report.GenerateReport = false - s.manager = SetupRunManager() + s.manager = NewTaskManager() _, err = s.ExecuteStrategyFromConfig(context.Background(), nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expecting '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } defaultConfig, err := config.ReadStrategyConfigFromFile(dcaConfigPath) @@ -188,13 +188,23 @@ func TestExecuteStrategyFromConfig(t *testing.T) { } } 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{ - ApiKeyOverride: defaultConfig.DataSettings.LiveData.APIKeyOverride, - ApiSecretOverride: defaultConfig.DataSettings.LiveData.APISecretOverride, - ApiClientIdOverride: defaultConfig.DataSettings.LiveData.APIClientIDOverride, - Api_2FaOverride: defaultConfig.DataSettings.LiveData.API2FAOverride, - ApiSubAccountOverride: defaultConfig.DataSettings.LiveData.APISubAccountOverride, - UseRealOrders: defaultConfig.DataSettings.LiveData.RealOrders, + RealOrders: defaultConfig.DataSettings.LiveData.RealOrders, + Credentials: creds, } } if defaultConfig.DataSettings.CSVData != nil { @@ -325,278 +335,281 @@ func TestExecuteStrategyFromConfig(t *testing.T) { } } -func TestListAllRuns(t *testing.T) { +func TestListAllTasks(t *testing.T) { t.Parallel() s := &GRPCServer{} - _, err := s.ListAllRuns(context.Background(), nil) + _, err := s.ListAllTasks(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() - _, err = s.ListAllRuns(context.Background(), nil) + s.manager = NewTaskManager() + _, err = s.ListAllTasks(context.Background(), nil) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - resp, err := s.ListAllRuns(context.Background(), &btrpc.ListAllRunsRequest{}) + resp, err := s.ListAllTasks(context.Background(), &btrpc.ListAllTasksRequest{}) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if len(resp.Runs) != 1 { - t.Errorf("received '%v' expecting '%v'", len(resp.Runs), 1) + if len(resp.Tasks) != 1 { + t.Errorf("received '%v' expecting '%v'", len(resp.Tasks), 1) } } -func TestGRPCStopRun(t *testing.T) { +func TestGRPCStopTask(t *testing.T) { t.Parallel() s := &GRPCServer{} - _, err := s.StopRun(context.Background(), nil) + _, err := s.StopTask(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() - _, err = s.StopRun(context.Background(), nil) + s.manager = NewTaskManager() + _, err = s.StopTask(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &fakeStrat{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, - Statistic: &statistics.Statistic{}, + DataHolder: &data.HandlerHolder{}, + Statistic: &fakeStats{}, + Reports: &fakeReport{}, shutdown: make(chan struct{}), } - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - _, err = s.StopRun(context.Background(), &btrpc.StopRunRequest{ + _, err = s.StopTask(context.Background(), &btrpc.StopTaskRequest{ Id: bt.MetaData.ID.String(), }) - if !errors.Is(err, errRunHasNotRan) { - t.Errorf("received '%v' expecting '%v'", err, errRunHasNotRan) + if !errors.Is(err, errTaskHasNotRan) { + t.Errorf("received '%v' expecting '%v'", err, errTaskHasNotRan) } - if len(s.manager.runs) != 1 { - t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1) + if len(s.manager.tasks) != 1 { + t.Fatalf("received '%v' expecting '%v'", len(s.manager.tasks), 1) } - s.manager.runs[0].MetaData.DateStarted = time.Now() - _, err = s.StopRun(context.Background(), &btrpc.StopRunRequest{ + s.manager.tasks[0].MetaData.DateStarted = time.Now() + _, err = s.StopTask(context.Background(), &btrpc.StopTaskRequest{ Id: bt.MetaData.ID.String(), }) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if s.manager.runs[0].MetaData.DateEnded.IsZero() { - t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateEnded, "a date") + if s.manager.tasks[0].MetaData.DateEnded.IsZero() { + t.Errorf("received '%v' expecting '%v'", s.manager.tasks[0].MetaData.DateEnded, "a date") } } -func TestGRPCStopAllRuns(t *testing.T) { +func TestGRPCStopAllTasks(t *testing.T) { t.Parallel() s := &GRPCServer{} - _, err := s.StopAllRuns(context.Background(), nil) + _, err := s.StopAllTasks(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() - _, err = s.StopAllRuns(context.Background(), nil) + s.manager = NewTaskManager() + _, err = s.StopAllTasks(context.Background(), nil) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &fakeStrat{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, - Statistic: &statistics.Statistic{}, + DataHolder: &data.HandlerHolder{}, + Statistic: &fakeStats{}, + Reports: &fakeReport{}, shutdown: make(chan struct{}), } - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - resp, err := s.StopAllRuns(context.Background(), &btrpc.StopAllRunsRequest{}) + resp, err := s.StopAllTasks(context.Background(), &btrpc.StopAllTasksRequest{}) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if len(s.manager.runs) != 1 { - t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1) + if len(s.manager.tasks) != 1 { + t.Fatalf("received '%v' expecting '%v'", len(s.manager.tasks), 1) } - if len(resp.RunsStopped) != 0 { - t.Errorf("received '%v' expecting '%v'", len(resp.RunsStopped), 0) + if len(resp.TasksStopped) != 0 { + t.Errorf("received '%v' expecting '%v'", len(resp.TasksStopped), 0) } - s.manager.runs[0].MetaData.DateStarted = time.Now() - resp, err = s.StopAllRuns(context.Background(), &btrpc.StopAllRunsRequest{}) + s.manager.tasks[0].MetaData.DateStarted = time.Now() + resp, err = s.StopAllTasks(context.Background(), &btrpc.StopAllTasksRequest{}) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if s.manager.runs[0].MetaData.DateEnded.IsZero() { - t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateEnded, "a date") + if s.manager.tasks[0].MetaData.DateEnded.IsZero() { + t.Errorf("received '%v' expecting '%v'", s.manager.tasks[0].MetaData.DateEnded, "a date") } - if len(resp.RunsStopped) != 1 { - t.Errorf("received '%v' expecting '%v'", len(resp.RunsStopped), 1) + if len(resp.TasksStopped) != 1 { + t.Errorf("received '%v' expecting '%v'", len(resp.TasksStopped), 1) } } -func TestGRPCStartRun(t *testing.T) { +func TestGRPCStartTask(t *testing.T) { t.Parallel() s := &GRPCServer{} - _, err := s.StartRun(context.Background(), nil) + _, err := s.StartTask(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() - _, err = s.StartRun(context.Background(), nil) + s.manager = NewTaskManager() + _, err = s.StartTask(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &fakeStrat{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, - Statistic: &statistics.Statistic{}, + DataHolder: &data.HandlerHolder{}, + Statistic: &fakeStats{}, + Reports: &fakeReport{}, shutdown: make(chan struct{}), } - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - _, err = s.StartRun(context.Background(), &btrpc.StartRunRequest{ + _, err = s.StartTask(context.Background(), &btrpc.StartTaskRequest{ Id: bt.MetaData.ID.String(), }) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if len(s.manager.runs) != 1 { - t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1) + if len(s.manager.tasks) != 1 { + t.Fatalf("received '%v' expecting '%v'", len(s.manager.tasks), 1) } - if s.manager.runs[0].MetaData.DateStarted.IsZero() { - t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateStarted, "a date") + if s.manager.tasks[0].MetaData.DateStarted.IsZero() { + t.Errorf("received '%v' expecting '%v'", s.manager.tasks[0].MetaData.DateStarted, "a date") } } -func TestGRPCStartAllRuns(t *testing.T) { +func TestGRPCStartAllTasks(t *testing.T) { t.Parallel() s := &GRPCServer{} - _, err := s.StartAllRuns(context.Background(), nil) + _, err := s.StartAllTasks(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() - _, err = s.StartAllRuns(context.Background(), nil) + s.manager = NewTaskManager() + _, err = s.StartAllTasks(context.Background(), nil) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - _, err = s.StartAllRuns(context.Background(), &btrpc.StartAllRunsRequest{}) + _, err = s.StartAllTasks(context.Background(), &btrpc.StartAllTasksRequest{}) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if len(s.manager.runs) != 1 { - t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 1) + if len(s.manager.tasks) != 1 { + t.Fatalf("received '%v' expecting '%v'", len(s.manager.tasks), 1) } - if s.manager.runs[0].MetaData.DateStarted.IsZero() { - t.Errorf("received '%v' expecting '%v'", s.manager.runs[0].MetaData.DateStarted, "a date") + if s.manager.tasks[0].MetaData.DateStarted.IsZero() { + t.Errorf("received '%v' expecting '%v'", s.manager.tasks[0].MetaData.DateStarted, "a date") } } -func TestGRPCClearRun(t *testing.T) { +func TestGRPCClearTask(t *testing.T) { t.Parallel() s := &GRPCServer{} - _, err := s.ClearRun(context.Background(), nil) + _, err := s.ClearTask(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() - _, err = s.ClearRun(context.Background(), nil) + s.manager = NewTaskManager() + _, err = s.ClearTask(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - _, err = s.ClearRun(context.Background(), &btrpc.ClearRunRequest{ + _, err = s.ClearTask(context.Background(), &btrpc.ClearTaskRequest{ Id: bt.MetaData.ID.String(), }) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if len(s.manager.runs) != 0 { - t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 0) + if len(s.manager.tasks) != 0 { + t.Fatalf("received '%v' expecting '%v'", len(s.manager.tasks), 0) } } -func TestGRPCClearAllRuns(t *testing.T) { +func TestGRPCClearAllTasks(t *testing.T) { t.Parallel() s := &GRPCServer{} - _, err := s.ClearAllRuns(context.Background(), nil) + _, err := s.ClearAllTasks(context.Background(), nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expecting '%v'", err, gctcommon.ErrNilPointer) } - s.manager = SetupRunManager() - _, err = s.ClearAllRuns(context.Background(), nil) + s.manager = NewTaskManager() + _, err = s.ClearAllTasks(context.Background(), nil) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = s.manager.AddRun(bt) + err = s.manager.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - _, err = s.ClearAllRuns(context.Background(), &btrpc.ClearAllRunsRequest{}) + _, err = s.ClearAllTasks(context.Background(), &btrpc.ClearAllTasksRequest{}) if !errors.Is(err, nil) { t.Errorf("received '%v' expecting '%v'", err, nil) } - if len(s.manager.runs) != 0 { - t.Fatalf("received '%v' expecting '%v'", len(s.manager.runs), 0) + if len(s.manager.tasks) != 0 { + t.Fatalf("received '%v' expecting '%v'", len(s.manager.tasks), 0) } } diff --git a/backtester/engine/live.go b/backtester/engine/live.go index 1fb5d5bf..3238d565 100644 --- a/backtester/engine/live.go +++ b/backtester/engine/live.go @@ -2,138 +2,525 @@ package engine import ( "context" + "errors" + "fmt" + "strings" + "sync" + "sync/atomic" "time" "github.com/thrasher-corp/gocryptotrader/backtester/common" - "github.com/thrasher-corp/gocryptotrader/backtester/config" - "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline/live" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" - gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/log" ) -// RunLive is a proof of concept function that does not yet support multi currency usage -// It runs by constantly checking for new live datas and running through the list of events -// once new data is processed. It will run until application close event has been received -func (bt *BackTest) RunLive() error { - log.Info(common.Backtester, "Running backtester against live data") - timeoutTimer := time.NewTimer(time.Minute * 5) - // a frequent timer so that when a new candle is released by an exchange - // that it can be processed quickly - processEventTicker := time.NewTicker(time.Second) - doneARun := false +// SetupLiveDataHandler creates a live data handler to retrieve and append +// live data as it comes in +func (bt *BackTest) SetupLiveDataHandler(eventTimeout, dataCheckInterval time.Duration, realOrders, verbose bool) error { + if bt == nil { + return fmt.Errorf("%w backtester", gctcommon.ErrNilPointer) + } + if bt.exchangeManager == nil { + return fmt.Errorf("%w engine manager", gctcommon.ErrNilPointer) + } + if bt.DataHolder == nil { + return fmt.Errorf("%w data holder", gctcommon.ErrNilPointer) + } + if bt.Reports == nil { + return fmt.Errorf("%w reports", gctcommon.ErrNilPointer) + } + if bt.Funding == nil { + return fmt.Errorf("%w funding manager", gctcommon.ErrNilPointer) + } + if eventTimeout <= 0 { + log.Warnf(common.LiveStrategy, "Invalid event timeout '%v', defaulting to '%v'", eventTimeout, defaultEventTimeout) + eventTimeout = defaultEventTimeout + } + if dataCheckInterval <= 0 { + log.Warnf(common.LiveStrategy, "Invalid data check interval '%v', defaulting to '%v'", dataCheckInterval, defaultDataCheckInterval) + dataCheckInterval = defaultDataCheckInterval + } + bt.LiveDataHandler = &dataChecker{ + verboseDataCheck: verbose, + realOrders: realOrders, + hasUpdatedFunding: false, + exchangeManager: bt.exchangeManager, + sourcesToCheck: nil, + eventTimeout: eventTimeout, + dataCheckInterval: dataCheckInterval, + dataHolder: bt.DataHolder, + shutdownErr: make(chan bool), + dataUpdated: make(chan bool), + report: bt.Reports, + funding: bt.Funding, + } + return nil +} + +// Start begins fetching and appending live data +func (d *dataChecker) Start() error { + if d == nil { + return gctcommon.ErrNilPointer + } + if !atomic.CompareAndSwapUint32(&d.started, 0, 1) { + return engine.ErrSubSystemAlreadyStarted + } + d.wg.Add(1) + d.shutdown = make(chan bool) + d.dataUpdated = make(chan bool) + d.shutdownErr = make(chan bool) + go func() { + err := d.DataFetcher() + if err != nil { + stopErr := d.SignalStopFromError(err) + if stopErr != nil { + log.Error(common.LiveStrategy, stopErr) + } + } + }() + + return nil +} + +// IsRunning verifies whether the live data checker is running +func (d *dataChecker) IsRunning() bool { + return d != nil && atomic.LoadUint32(&d.started) == 1 +} + +// Stop ceases fetching and processing live data +func (d *dataChecker) Stop() error { + if d == nil { + return gctcommon.ErrNilPointer + } + if !atomic.CompareAndSwapUint32(&d.started, 1, 0) { + return engine.ErrSubSystemNotStarted + } + close(d.shutdown) + return nil +} + +// SignalStopFromError ceases fetching and processing live data +func (d *dataChecker) SignalStopFromError(err error) error { + if err == nil { + return errNilError + } + if d == nil { + return gctcommon.ErrNilPointer + } + if !atomic.CompareAndSwapUint32(&d.started, 1, 0) { + return engine.ErrSubSystemNotStarted + } + log.Error(common.LiveStrategy, err) + d.shutdownErr <- true + return nil +} + +// DataFetcher will fetch and append live data +func (d *dataChecker) DataFetcher() error { + if d == nil { + return fmt.Errorf("%w dataChecker", gctcommon.ErrNilPointer) + } + d.wg.Done() + if atomic.LoadUint32(&d.started) == 0 { + return engine.ErrSubSystemNotStarted + } + checkTimer := time.NewTimer(0) + timeoutTimer := time.NewTimer(d.eventTimeout) for { select { - case <-bt.shutdown: + case <-d.shutdown: return nil case <-timeoutTimer.C: - return errLiveDataTimeout - case <-processEventTicker.C: - for e := bt.EventQueue.NextEvent(); ; e = bt.EventQueue.NextEvent() { - if e == nil { - // as live only supports singular currency, just get the proper reference manually - var d data.Handler - dd := bt.Datas.GetAllData() - for k1, v1 := range dd { - for k2, v2 := range v1 { - for k3 := range v2 { - d = dd[k1][k2][k3] - } - } - } - de := d.Next() - if de == nil { - break - } - - bt.EventQueue.AppendEvent(de) - doneARun = true - continue - } - err := bt.handleEvent(e) - if err != nil { - return err - } - } - if doneARun { - timeoutTimer = time.NewTimer(time.Minute * 5) - } - } - } -} - -// loadLiveDataLoop is an incomplete function to continuously retrieve exchange data on a loop -// from live. Its purpose is to be able to perform strategy analysis against current data -func (bt *BackTest) loadLiveDataLoop(resp *kline.DataFromKline, cfg *config.Config, exch gctexchange.IBotExchange, fPair currency.Pair, a asset.Item, dataType int64) { - startDate := time.Now().Add(-cfg.DataSettings.Interval.Duration() * 2) - dates, err := gctkline.CalculateCandleDateRanges( - startDate, - startDate.AddDate(1, 0, 0), - cfg.DataSettings.Interval, - 0) - if err != nil { - log.Errorf(common.Backtester, "%v. Please check your GoCryptoTrader configuration", err) - return - } - candles, err := live.LoadData(context.TODO(), - exch, - dataType, - cfg.DataSettings.Interval.Duration(), - fPair, - a) - if err != nil { - log.Errorf(common.Backtester, "%v. Please check your GoCryptoTrader configuration", err) - return - } - dates.SetHasDataFromCandles(candles.Candles) - resp.RangeHolder = dates - resp.Item = *candles - - loadNewDataTimer := time.NewTimer(time.Second * 5) - for { - select { - case <-bt.shutdown: - return - case <-loadNewDataTimer.C: - log.Infof(common.Backtester, "Fetching data for %v %v %v %v", exch.GetName(), a, fPair, cfg.DataSettings.Interval) - loadNewDataTimer.Reset(time.Second * 15) - err = bt.loadLiveData(resp, cfg, exch, fPair, a, dataType) + return fmt.Errorf("%w of %v", ErrLiveDataTimeout, d.eventTimeout) + case <-checkTimer.C: + err := d.checkData() if err != nil { - log.Error(common.Backtester, err) - return + return err } + checkTimer.Reset(d.dataCheckInterval) + if !timeoutTimer.Stop() { + <-timeoutTimer.C + } + timeoutTimer.Reset(d.eventTimeout) } } } -func (bt *BackTest) loadLiveData(resp *kline.DataFromKline, cfg *config.Config, exch gctexchange.IBotExchange, fPair currency.Pair, a asset.Item, dataType int64) error { - if resp == nil { - return errNilData - } - if cfg == nil { - return errNilConfig - } - if exch == nil { - return errNilExchange - } - candles, err := live.LoadData(context.TODO(), - exch, - dataType, - cfg.DataSettings.Interval.Duration(), - fPair, - a) +func (d *dataChecker) checkData() error { + hasDataUpdated, err := d.FetchLatestData() if err != nil { return err } - if len(candles.Candles) == 0 { + if !hasDataUpdated { return nil } - resp.AppendResults(candles) - bt.Reports.UpdateItem(&resp.Item) - log.Info(common.Backtester, "Sleeping for 30 seconds before checking for new candle data") + d.dataUpdated <- hasDataUpdated + if d.realOrders { + go func() { + err = d.UpdateFunding(false) + if err != nil { + log.Errorf(common.LiveStrategy, "Could not update funding: %v", err) + } + }() + } return nil } + +// UpdateFunding requests and updates funding levels +func (d *dataChecker) UpdateFunding(force bool) error { + switch { + case d == nil: + return fmt.Errorf("%w datachecker", gctcommon.ErrNilPointer) + case d.funding == nil: + return fmt.Errorf("%w datachecker funding manager", gctcommon.ErrNilPointer) + case force: + atomic.StoreUint32(&d.updatingFunding, 1) + case !atomic.CompareAndSwapUint32(&d.updatingFunding, 0, 1): + // already processing funding and can't go any faster + return nil + } + + defer atomic.StoreUint32(&d.updatingFunding, 0) + var err error + if d.funding.HasFutures() { + err = d.funding.UpdateAllCollateral(d.realOrders, d.hasUpdatedFunding) + if err != nil { + return err + } + } + + if d.realOrders { + // TODO: design a more sophisticated way of keeping funds up to date + // with current data type retrieval, this still functions appropriately + err = d.funding.UpdateFundingFromLiveData(d.hasUpdatedFunding) + if err != nil { + return err + } + } + if !d.hasUpdatedFunding { + d.hasUpdatedFunding = true + } + + return nil +} + +func closedChan() chan bool { + immediateClosure := make(chan bool) + close(immediateClosure) + return immediateClosure +} + +// Updated gives other endpoints the ability to listen to +// when data is dataUpdated from live sources +func (d *dataChecker) Updated() chan bool { + if d == nil { + return closedChan() + } + return d.dataUpdated +} + +// HasShutdown indicates when the live data checker +// has been shutdown +func (d *dataChecker) HasShutdown() chan bool { + if d == nil { + return closedChan() + } + + return d.shutdown +} + +// HasShutdownFromError indicates when the live data checker +// has been shutdown from encountering an error +func (d *dataChecker) HasShutdownFromError() chan bool { + if d == nil { + return closedChan() + } + return d.shutdownErr +} + +// Reset clears all stored data +func (d *dataChecker) Reset() error { + if d == nil { + return gctcommon.ErrNilPointer + } + d.m.Lock() + defer d.m.Unlock() + d.wg = sync.WaitGroup{} + d.started = 0 + d.updatingFunding = 0 + d.verboseDataCheck = false + d.realOrders = false + d.hasUpdatedFunding = false + d.exchangeManager = nil + d.sourcesToCheck = nil + d.eventTimeout = 0 + d.dataCheckInterval = 0 + d.dataHolder = nil + d.report = nil + d.funding = nil + + return nil +} + +// AppendDataSource stores params to allow the datachecker to fetch and append live data +func (d *dataChecker) AppendDataSource(dataSource *liveDataSourceSetup) error { + if d == nil { + return fmt.Errorf("%w dataChecker", gctcommon.ErrNilPointer) + } + if dataSource == nil { + return fmt.Errorf("%w live data source", gctcommon.ErrNilPointer) + } + if dataSource.exchange == nil { + return fmt.Errorf("%w IBotExchange", gctcommon.ErrNilPointer) + } + if dataSource.dataType != common.DataCandle && dataSource.dataType != common.DataTrade { + return fmt.Errorf("%w '%v'", common.ErrInvalidDataType, dataSource.dataType) + } + if !dataSource.asset.IsValid() { + return fmt.Errorf("%w '%v'", asset.ErrNotSupported, dataSource.asset) + } + if dataSource.pair.IsEmpty() { + return fmt.Errorf("main %w", currency.ErrCurrencyPairEmpty) + } + if dataSource.interval.Duration() == 0 { + return gctkline.ErrUnsetInterval + } + d.m.Lock() + defer d.m.Unlock() + exchName := strings.ToLower(dataSource.exchange.GetName()) + for i := range d.sourcesToCheck { + if d.sourcesToCheck[i].exchangeName == exchName && + d.sourcesToCheck[i].asset == dataSource.asset && + d.sourcesToCheck[i].pair.Equal(dataSource.pair) { + return fmt.Errorf("%w %v %v %v", errDataSourceExists, exchName, dataSource.asset, dataSource.pair) + } + } + k := kline.NewDataFromKline() + k.Item = gctkline.Item{ + Exchange: exchName, + Pair: dataSource.pair, + UnderlyingPair: dataSource.underlyingPair, + Asset: dataSource.asset, + Interval: dataSource.interval, + } + + err := k.SetLive(true) + if err != nil { + return err + } + if dataSource.dataRequestRetryTolerance <= 0 { + log.Warnf(common.LiveStrategy, "Invalid data retry tolerance, setting %v to %v", dataSource.dataRequestRetryTolerance, defaultDataRetryAttempts) + dataSource.dataRequestRetryTolerance = defaultDataRetryAttempts + } + if dataSource.dataRequestRetryWaitTime <= 0 { + log.Warnf(common.LiveStrategy, "Invalid data request wait time, setting %v to %v", dataSource.dataRequestRetryWaitTime, defaultDataRequestWaitTime) + dataSource.dataRequestRetryWaitTime = defaultDataRequestWaitTime + } + d.sourcesToCheck = append(d.sourcesToCheck, &liveDataSourceDataHandler{ + exchange: dataSource.exchange, + exchangeName: exchName, + asset: dataSource.asset, + pair: dataSource.pair, + underlyingPair: dataSource.underlyingPair, + pairCandles: k, + dataType: dataSource.dataType, + processedData: make(map[int64]struct{}), + dataRequestRetryTolerance: dataSource.dataRequestRetryTolerance, + dataRequestRetryWaitTime: dataSource.dataRequestRetryWaitTime, + verboseExchangeRequest: dataSource.verboseExchangeRequest, + }) + + return nil +} + +// FetchLatestData loads the latest data for all stored data sources +func (d *dataChecker) FetchLatestData() (bool, error) { + if d == nil { + return false, fmt.Errorf("%w dataChecker", gctcommon.ErrNilPointer) + } + if atomic.LoadUint32(&d.started) == 0 { + return false, engine.ErrSubSystemNotStarted + } + d.m.Lock() + defer d.m.Unlock() + var err error + + results := make([]bool, len(d.sourcesToCheck)) + // timeToRetrieve ensures consistent data retrieval + // in the event of a candle rollover mid-loop + timeToRetrieve := time.Now() + for i := range d.sourcesToCheck { + if d.verboseDataCheck { + log.Infof(common.LiveStrategy, "%v %v %v checking for new data", d.sourcesToCheck[i].exchangeName, d.sourcesToCheck[i].asset, d.sourcesToCheck[i].pair) + } + var updated bool + updated, err = d.sourcesToCheck[i].loadCandleData(timeToRetrieve) + if err != nil { + return false, err + } + results[i] = updated + } + for i := range results { + if !results[i] { + return false, nil + } + } + for i := range d.sourcesToCheck { + if d.verboseDataCheck { + log.Infof(common.LiveStrategy, "%v %v %v found new data", d.sourcesToCheck[i].exchangeName, d.sourcesToCheck[i].asset, d.sourcesToCheck[i].pair) + } + err = d.sourcesToCheck[i].pairCandles.AppendResults(d.sourcesToCheck[i].candlesToAppend) + if err != nil { + return false, err + } + d.sourcesToCheck[i].candlesToAppend.Candles = nil + err = d.dataHolder.SetDataForCurrency(d.sourcesToCheck[i].exchangeName, d.sourcesToCheck[i].asset, d.sourcesToCheck[i].pair, d.sourcesToCheck[i].pairCandles) + if err != nil { + return false, err + } + err = d.report.SetKlineData(&d.sourcesToCheck[i].pairCandles.Item) + if err != nil { + return false, err + } + err = d.funding.AddUSDTrackingData(d.sourcesToCheck[i].pairCandles) + if err != nil && !errors.Is(err, funding.ErrUSDTrackingDisabled) { + return false, err + } + } + if !d.hasUpdatedFunding { + err = d.UpdateFunding(false) + if err != nil { + if err != nil { + log.Error(common.LiveStrategy, err) + } + } + } + + return true, nil +} + +// SetDataForClosingAllPositions is triggered on a live data run +// when closing all positions on close is true. +// it will ensure all data is set such as USD tracking data +func (d *dataChecker) SetDataForClosingAllPositions(s ...signal.Event) error { + if d == nil { + return fmt.Errorf("%w dataChecker", gctcommon.ErrNilPointer) + } + if len(s) == 0 { + return fmt.Errorf("%w signal events", gctcommon.ErrNilPointer) + } + d.m.Lock() + defer d.m.Unlock() + var err error + + setData := false + for x := range s { + if s[x] == nil { + return fmt.Errorf("%w signal events", errNilData) + } + for y := range d.sourcesToCheck { + if s[x].GetExchange() != d.sourcesToCheck[y].exchangeName || + s[x].GetAssetType() != d.sourcesToCheck[y].asset || + !s[x].Pair().Equal(d.sourcesToCheck[y].pair) { + continue + } + d.sourcesToCheck[y].pairCandles.Item.Candles = append(d.sourcesToCheck[y].pairCandles.Item.Candles, gctkline.Candle{ + Time: s[x].GetTime(), + Open: s[x].GetOpenPrice().InexactFloat64(), + High: s[x].GetHighPrice().InexactFloat64(), + Low: s[x].GetLowPrice().InexactFloat64(), + Close: s[x].GetClosePrice().InexactFloat64(), + Volume: s[x].GetVolume().InexactFloat64(), + }) + err = d.sourcesToCheck[y].pairCandles.AppendResults(&d.sourcesToCheck[y].pairCandles.Item) + if err != nil { + log.Errorf(common.LiveStrategy, "%v %v %v issue appending kline data: %v", d.sourcesToCheck[y].exchangeName, d.sourcesToCheck[y].asset, d.sourcesToCheck[y].pair, err) + continue + } + err = d.report.SetKlineData(&d.sourcesToCheck[y].pairCandles.Item) + if err != nil { + log.Errorf(common.LiveStrategy, "%v %v %v issue processing kline data: %v", d.sourcesToCheck[y].exchangeName, d.sourcesToCheck[y].asset, d.sourcesToCheck[y].pair, err) + continue + } + err = d.funding.AddUSDTrackingData(d.sourcesToCheck[y].pairCandles) + if err != nil && !errors.Is(err, funding.ErrUSDTrackingDisabled) { + log.Errorf(common.LiveStrategy, "%v %v %v issue processing USD tracking data: %v", d.sourcesToCheck[y].exchangeName, d.sourcesToCheck[y].asset, d.sourcesToCheck[y].pair, err) + continue + } + setData = true + } + } + if !setData { + return errNoDataSetForClosingPositions + } + + return nil +} + +// IsRealOrders is a quick check for if the strategy is using real orders +func (d *dataChecker) IsRealOrders() bool { + return d.realOrders +} + +// loadCandleData fetches data from the exchange API and appends it +// to the candles to be added to the backtester event queue +func (c *liveDataSourceDataHandler) loadCandleData(timeToRetrieve time.Time) (bool, error) { + if c == nil { + return false, fmt.Errorf("%w live data source data handler", gctcommon.ErrNilPointer) + } + if c.pairCandles == nil { + return false, fmt.Errorf("%w pair candles", gctcommon.ErrNilPointer) + } + var candles *gctkline.Item + var err error + for i := int64(1); i <= c.dataRequestRetryTolerance; i++ { + candles, err = live.LoadData(context.TODO(), + timeToRetrieve, + c.exchange, + c.dataType, + c.pairCandles.Item.Interval.Duration(), + c.pair, + c.underlyingPair, + c.asset, + c.verboseExchangeRequest) + if err != nil { + if i < c.dataRequestRetryTolerance { + log.Errorf(common.Data, "%v %v %v failed to retrieve data %v of %v attempts: %v", c.exchangeName, c.asset, c.pair, i, c.dataRequestRetryTolerance, err) + continue + } else { + return false, err + } + } + break + } + if candles == nil { + return false, fmt.Errorf("%w kline Asset", gctcommon.ErrNilPointer) + } + if len(candles.Candles) == 0 { + return false, nil + } + + unprocessedCandles := make([]gctkline.Candle, 0, len(candles.Candles)) + for i := range candles.Candles { + if _, ok := c.processedData[candles.Candles[i].Time.UnixNano()]; !ok { + unprocessedCandles = append(unprocessedCandles, candles.Candles[i]) + c.processedData[candles.Candles[i].Time.UnixNano()] = struct{}{} + } + } + if len(unprocessedCandles) > 0 { + if c.candlesToAppend == nil { + c.candlesToAppend = candles + } + c.candlesToAppend.Candles = append(c.candlesToAppend.Candles, unprocessedCandles...) + return true, nil + } + return false, nil +} diff --git a/backtester/engine/live_test.go b/backtester/engine/live_test.go index 4badd890..b3b8e4a9 100644 --- a/backtester/engine/live_test.go +++ b/backtester/engine/live_test.go @@ -2,59 +2,629 @@ package engine import ( "errors" + "sync" + "sync/atomic" "testing" + "time" "github.com/thrasher-corp/gocryptotrader/backtester/common" - "github.com/thrasher-corp/gocryptotrader/backtester/config" - gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges" - gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" + "github.com/thrasher-corp/gocryptotrader/backtester/data" + datakline "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + "github.com/thrasher-corp/gocryptotrader/backtester/funding" + "github.com/thrasher-corp/gocryptotrader/backtester/report" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/engine" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/binance" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" ) -func TestLoadLiveData(t *testing.T) { +func TestSetupLiveDataHandler(t *testing.T) { t.Parallel() - err := loadLiveData(nil, nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Error(err) - } - cfg := &config.Config{} - err = loadLiveData(cfg, nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Error(err) - } - b := &gctexchange.Base{ - Name: testExchange, - API: gctexchange.API{ - CredentialsValidator: gctexchange.CredentialsValidator{ - RequiresPEM: true, - RequiresKey: true, - RequiresSecret: true, - RequiresClientID: true, - RequiresBase64DecodeSecret: true, - }, - }, + bt := &BackTest{} + var err error + err = bt.SetupLiveDataHandler(-1, -1, false, false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } - err = loadLiveData(cfg, b) - if !errors.Is(err, common.ErrNilArguments) { - t.Error(err) - } - cfg.DataSettings.LiveData = &config.LiveData{ - RealOrders: true, - } - cfg.DataSettings.Interval = gctkline.OneDay - cfg.DataSettings.DataType = common.CandleStr - err = loadLiveData(cfg, b) - if err != nil { - t.Error(err) + bt.exchangeManager = engine.SetupExchangeManager() + err = bt.SetupLiveDataHandler(-1, -1, false, false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } - cfg.DataSettings.LiveData.APIKeyOverride = "1234" - cfg.DataSettings.LiveData.APISecretOverride = "1234" - cfg.DataSettings.LiveData.APIClientIDOverride = "1234" - cfg.DataSettings.LiveData.API2FAOverride = "1234" - cfg.DataSettings.LiveData.APISubAccountOverride = "1234" - err = loadLiveData(cfg, b) - if err != nil { - t.Error(err) + bt.DataHolder = &data.HandlerHolder{} + err = bt.SetupLiveDataHandler(-1, -1, false, false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + bt.Reports = &report.Data{} + err = bt.SetupLiveDataHandler(-1, -1, false, false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + bt.Funding = &funding.FundManager{} + err = bt.SetupLiveDataHandler(-1, -1, false, false) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + dc, ok := bt.LiveDataHandler.(*dataChecker) + if !ok { + t.Fatalf("received '%T' expected '%v'", dc, "dataChecker") + } + if dc.eventTimeout != defaultEventTimeout { + t.Errorf("received '%v' expected '%v'", dc.eventTimeout, defaultEventTimeout) + } + if dc.dataCheckInterval != defaultDataCheckInterval { + t.Errorf("received '%v' expected '%v'", dc.dataCheckInterval, defaultDataCheckInterval) + } + + bt = nil + err = bt.SetupLiveDataHandler(-1, -1, false, false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestStart(t *testing.T) { + t.Parallel() + dc := &dataChecker{ + shutdown: make(chan bool), + } + err := dc.Start() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + close(dc.shutdown) + dc.wg.Wait() + atomic.CompareAndSwapUint32(&dc.started, 0, 1) + err = dc.Start() + if !errors.Is(err, engine.ErrSubSystemAlreadyStarted) { + t.Errorf("received '%v' expected '%v'", err, engine.ErrSubSystemAlreadyStarted) + } + + var dh *dataChecker + err = dh.Start() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestDataCheckerIsRunning(t *testing.T) { + t.Parallel() + dataHandler := &dataChecker{} + if dataHandler.IsRunning() { + t.Errorf("received '%v' expected '%v'", true, false) + } + + dataHandler.started = 1 + + if !dataHandler.IsRunning() { + t.Errorf("received '%v' expected '%v'", false, true) + } + + var dh *dataChecker + if dh.IsRunning() { + t.Errorf("received '%v' expected '%v'", true, false) + } +} + +func TestLiveHandlerStop(t *testing.T) { + t.Parallel() + dc := &dataChecker{ + shutdown: make(chan bool), + } + err := dc.Stop() + if !errors.Is(err, engine.ErrSubSystemNotStarted) { + t.Errorf("received '%v' expected '%v'", err, engine.ErrSubSystemNotStarted) + } + + dc.started = 1 + err = dc.Stop() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + dc.shutdown = make(chan bool) + err = dc.Stop() + if !errors.Is(err, engine.ErrSubSystemNotStarted) { + t.Errorf("received '%v' expected '%v'", err, engine.ErrSubSystemNotStarted) + } + + var dh *dataChecker + err = dh.Stop() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestLiveHandlerStopFromError(t *testing.T) { + t.Parallel() + dc := &dataChecker{ + shutdownErr: make(chan bool, 10), + } + err := dc.SignalStopFromError(errNoCredsNoLive) + if !errors.Is(err, engine.ErrSubSystemNotStarted) { + t.Errorf("received '%v' expected '%v'", err, engine.ErrSubSystemNotStarted) + } + + err = dc.SignalStopFromError(nil) + if !errors.Is(err, errNilError) { + t.Errorf("received '%v' expected '%v'", err, errNilError) + } + dc.started = 1 + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + err = dc.SignalStopFromError(errNoCredsNoLive) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + }() + wg.Wait() + + var dh *dataChecker + err = dh.SignalStopFromError(errNoCredsNoLive) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestDataFetcher(t *testing.T) { + t.Parallel() + dc := &dataChecker{ + dataCheckInterval: time.Second, + eventTimeout: time.Millisecond, + shutdown: make(chan bool, 10), + shutdownErr: make(chan bool, 10), + dataUpdated: make(chan bool, 10), + } + dc.wg.Add(1) + err := dc.DataFetcher() + if !errors.Is(err, engine.ErrSubSystemNotStarted) { + t.Errorf("received '%v' expected '%v'", err, engine.ErrSubSystemNotStarted) + } + + dc.started = 1 + dc.wg.Add(1) + err = dc.DataFetcher() + if !errors.Is(err, ErrLiveDataTimeout) { + t.Errorf("received '%v' expected '%v'", err, ErrLiveDataTimeout) + } + + var dh *dataChecker + err = dh.DataFetcher() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestUpdated(t *testing.T) { + t.Parallel() + dc := &dataChecker{ + dataUpdated: make(chan bool, 10), + } + var wg sync.WaitGroup + wg.Add(1) + go func() { + _ = dc.Updated() + wg.Done() + }() + wg.Wait() + + dc = nil + wg.Add(1) + go func() { + _ = dc.Updated() + wg.Done() + }() + wg.Wait() +} + +func TestLiveHandlerReset(t *testing.T) { + t.Parallel() + dataHandler := &dataChecker{ + eventTimeout: 1, + } + err := dataHandler.Reset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if dataHandler.eventTimeout != 0 { + t.Errorf("received '%v' expected '%v'", dataHandler.eventTimeout, 0) + } + var dh *dataChecker + err = dh.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestAppendDataSource(t *testing.T) { + t.Parallel() + dataHandler := &dataChecker{} + err := dataHandler.AppendDataSource(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + setup := &liveDataSourceSetup{} + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + setup.exchange = &binance.Binance{} + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, common.ErrInvalidDataType) { + t.Errorf("received '%v' expected '%v'", err, common.ErrInvalidDataType) + } + + setup.dataType = common.DataCandle + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, asset.ErrNotSupported) { + t.Errorf("received '%v' expected '%v'", err, asset.ErrNotSupported) + } + + setup.asset = asset.Spot + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf("received '%v' expected '%v'", err, currency.ErrCurrencyPairEmpty) + } + + setup.pair = currency.NewPair(currency.BTC, currency.USDT) + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, kline.ErrUnsetInterval) { + t.Errorf("received '%v' expected '%v'", err, kline.ErrUnsetInterval) + } + + setup.interval = kline.OneDay + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + if len(dataHandler.sourcesToCheck) != 1 { + t.Errorf("received '%v' expected '%v'", len(dataHandler.sourcesToCheck), 1) + } + + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, errDataSourceExists) { + t.Errorf("received '%v' expected '%v'", err, errDataSourceExists) + } + + dataHandler = nil + err = dataHandler.AppendDataSource(setup) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestFetchLatestData(t *testing.T) { + t.Parallel() + dataHandler := &dataChecker{ + report: &report.Data{}, + funding: &fakeFunding{}, + } + _, err := dataHandler.FetchLatestData() + if !errors.Is(err, engine.ErrSubSystemNotStarted) { + t.Errorf("received '%v' expected '%v'", err, engine.ErrSubSystemNotStarted) + } + + dataHandler.started = 1 + _, err = dataHandler.FetchLatestData() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + cp := currency.NewPair(currency.BTC, currency.USDT).Format( + currency.PairFormat{ + Uppercase: true, + }) + f := &binance.Binance{} + f.SetDefaults() + fb := f.GetBase() + fbA := fb.CurrencyPairs.Pairs[asset.Spot] + fbA.Enabled = fbA.Enabled.Add(cp) + fbA.Available = fbA.Available.Add(cp) + dataHandler.sourcesToCheck = []*liveDataSourceDataHandler{ + { + exchange: f, + exchangeName: testExchange, + asset: asset.Spot, + pair: cp, + dataRequestRetryWaitTime: defaultDataRequestWaitTime, + dataRequestRetryTolerance: 1, + underlyingPair: cp, + pairCandles: &datakline.DataFromKline{ + Base: &data.Base{}, + Item: kline.Item{ + Exchange: testExchange, + Pair: cp, + UnderlyingPair: cp, + Asset: asset.Spot, + Interval: kline.OneHour, + Candles: []kline.Candle{ + { + Time: time.Now(), + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }, + }, + }, + }, + dataType: common.DataCandle, + processedData: make(map[int64]struct{}), + }, + } + dataHandler.dataHolder = &fakeDataHolder{} + _, err = dataHandler.FetchLatestData() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + var dh *dataChecker + _, err = dh.FetchLatestData() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestLoadCandleData(t *testing.T) { + t.Parallel() + l := &liveDataSourceDataHandler{ + dataRequestRetryTolerance: 1, + dataRequestRetryWaitTime: defaultDataRequestWaitTime, + processedData: make(map[int64]struct{}), + } + _, err := l.loadCandleData(time.Now()) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + exch := &binance.Binance{} + exch.SetDefaults() + cp := currency.NewPair(currency.BTC, currency.USDT).Format( + currency.PairFormat{ + Uppercase: true, + }) + eba := exch.CurrencyPairs.Pairs[asset.Spot] + eba.Available = eba.Available.Add(cp) + eba.Enabled = eba.Enabled.Add(cp) + eba.AssetEnabled = convert.BoolPtr(true) + l.exchange = exch + l.dataType = common.DataCandle + l.asset = asset.Spot + l.pair = cp + l.pairCandles = &datakline.DataFromKline{ + Base: &data.Base{}, + Item: kline.Item{ + Exchange: testExchange, + Asset: asset.Spot, + Pair: cp, + UnderlyingPair: cp, + Interval: kline.OneHour, + }, + } + updated, err := l.loadCandleData(time.Now()) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if !updated { + t.Errorf("received '%v' expected '%v'", updated, true) + } + + var ldh *liveDataSourceDataHandler + _, err = ldh.loadCandleData(time.Now()) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestSetDataForClosingAllPositions(t *testing.T) { + t.Parallel() + dataHandler := &dataChecker{ + report: &fakeReport{}, + funding: &fakeFunding{}, + } + + dataHandler.started = 1 + cp := currency.NewPair(currency.BTC, currency.USDT).Format( + currency.PairFormat{ + Uppercase: true, + }) + f := &binance.Binance{} + f.SetDefaults() + fb := f.GetBase() + fbA := fb.CurrencyPairs.Pairs[asset.Spot] + fbA.Enabled = fbA.Enabled.Add(cp) + fbA.Available = fbA.Available.Add(cp) + dataHandler.sourcesToCheck = []*liveDataSourceDataHandler{ + { + exchange: f, + exchangeName: testExchange, + asset: asset.Spot, + pair: cp, + dataRequestRetryWaitTime: defaultDataRequestWaitTime, + dataRequestRetryTolerance: 1, + underlyingPair: cp, + pairCandles: &datakline.DataFromKline{ + Base: &data.Base{}, + Item: kline.Item{ + Exchange: testExchange, + Pair: cp, + UnderlyingPair: cp, + Asset: asset.Spot, + Interval: kline.OneHour, + Candles: []kline.Candle{ + { + Time: time.Now(), + Open: 1337, + High: 1337, + Low: 1337, + Close: 1337, + Volume: 1337, + }, + }, + }, + }, + dataType: common.DataCandle, + processedData: make(map[int64]struct{}), + }, + } + dataHandler.dataHolder = &fakeDataHolder{} + _, err := dataHandler.FetchLatestData() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + err = dataHandler.SetDataForClosingAllPositions() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + err = dataHandler.SetDataForClosingAllPositions(nil) + if !errors.Is(err, errNilData) { + t.Errorf("received '%v' expected '%v'", err, errNilData) + } + err = dataHandler.SetDataForClosingAllPositions(&signal.Signal{ + Base: &event.Base{ + Offset: 3, + Exchange: testExchange, + Time: time.Now(), + Interval: kline.OneHour, + CurrencyPair: cp, + UnderlyingPair: cp, + AssetType: asset.Spot, + }, + OpenPrice: leet, + HighPrice: leet, + LowPrice: leet, + ClosePrice: leet, + Volume: leet, + BuyLimit: leet, + SellLimit: leet, + Amount: leet, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + err = dataHandler.SetDataForClosingAllPositions(&signal.Signal{ + Base: &event.Base{ + Offset: 4, + Exchange: testExchange, + Time: time.Now(), + Interval: kline.OneHour, + CurrencyPair: cp, + UnderlyingPair: cp, + AssetType: asset.Spot, + }, + OpenPrice: leet, + HighPrice: leet, + LowPrice: leet, + ClosePrice: leet, + Volume: leet, + BuyLimit: leet, + SellLimit: leet, + Amount: leet, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + dataHandler = nil + err = dataHandler.SetDataForClosingAllPositions() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestIsRealOrders(t *testing.T) { + t.Parallel() + d := &dataChecker{} + if d.IsRealOrders() { + t.Error("expected false") + } + d.realOrders = true + if !d.IsRealOrders() { + t.Error("expected true") + } +} + +func TestUpdateFunding(t *testing.T) { + t.Parallel() + d := &dataChecker{} + err := d.UpdateFunding(false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } + + ff := &fakeFunding{} + d.funding = ff + err = d.UpdateFunding(false) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + err = d.UpdateFunding(true) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + d.realOrders = true + err = d.UpdateFunding(true) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + ff.hasFutures = true + err = d.UpdateFunding(true) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + d.updatingFunding = 1 + err = d.UpdateFunding(true) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + d.updatingFunding = 1 + err = d.UpdateFunding(false) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + + d = nil + err = d.UpdateFunding(false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } +} + +func TestClosedChan(t *testing.T) { + t.Parallel() + chantel := closedChan() + if chantel == nil { + t.Errorf("expected channel, received %v", nil) + } + <-chantel + // demonstrate nil channel still functions on a select case + chantel = nil + select { + case <-chantel: + t.Error("woah") + default: } } diff --git a/backtester/engine/live_types.go b/backtester/engine/live_types.go new file mode 100644 index 00000000..3fa0acad --- /dev/null +++ b/backtester/engine/live_types.go @@ -0,0 +1,107 @@ +package engine + +import ( + "errors" + "sync" + "time" + + "github.com/thrasher-corp/gocryptotrader/backtester/data" + "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + "github.com/thrasher-corp/gocryptotrader/backtester/funding" + "github.com/thrasher-corp/gocryptotrader/backtester/report" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/engine" + gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" +) + +var ( + // ErrLiveDataTimeout returns when an event has not been processed within the timeframe + ErrLiveDataTimeout = errors.New("no data processed within timeframe") + + errDataSourceExists = errors.New("data source already exists") + errInvalidCredentials = errors.New("credentials are invalid, please check your config") + errNoCredsNoLive = errors.New("cannot use real orders without credentials to fulfil those real orders") + errNoDataSetForClosingPositions = errors.New("no data was set for closing positions") + errNilError = errors.New("nil error received when expecting an error") +) + +var ( + defaultEventTimeout = time.Minute + defaultDataCheckInterval = time.Second + defaultDataRetryAttempts int64 = 1 + defaultDataRequestWaitTime = time.Millisecond * 500 +) + +// Handler is all the functionality required in order to +// run a backtester with live data +type Handler interface { + AppendDataSource(*liveDataSourceSetup) error + FetchLatestData() (bool, error) + Start() error + IsRunning() bool + DataFetcher() error + Stop() error + Reset() error + Updated() chan bool + HasShutdown() chan bool + HasShutdownFromError() chan bool + SetDataForClosingAllPositions(events ...signal.Event) error + UpdateFunding(force bool) error + IsRealOrders() bool +} + +// dataChecker is responsible for managing all data retrieval +// for a live data option +type dataChecker struct { + m sync.Mutex + wg sync.WaitGroup + started uint32 + updatingFunding uint32 + verboseDataCheck bool + realOrders bool + hasUpdatedFunding bool + exchangeManager *engine.ExchangeManager + sourcesToCheck []*liveDataSourceDataHandler + eventTimeout time.Duration + dataCheckInterval time.Duration + dataHolder data.Holder + shutdownErr chan bool + shutdown chan bool + dataUpdated chan bool + report report.Handler + funding funding.IFundingManager +} + +// liveDataSourceSetup is used to add new data sources +// to retrieve live data +type liveDataSourceSetup struct { + exchange gctexchange.IBotExchange + interval gctkline.Interval + asset asset.Item + pair currency.Pair + underlyingPair currency.Pair + dataType int64 + dataRequestRetryTolerance int64 + dataRequestRetryWaitTime time.Duration + verboseExchangeRequest bool +} + +// liveDataSourceDataHandler is used to collect +// and store live data +type liveDataSourceDataHandler struct { + exchange gctexchange.IBotExchange + exchangeName string + asset asset.Item + pair currency.Pair + underlyingPair currency.Pair + dataType int64 + pairCandles *kline.DataFromKline + processedData map[int64]struct{} + candlesToAppend *gctkline.Item + dataRequestRetryTolerance int64 + dataRequestRetryWaitTime time.Duration + verboseExchangeRequest bool +} diff --git a/backtester/engine/runmanager.go b/backtester/engine/runmanager.go deleted file mode 100644 index 0020c229..00000000 --- a/backtester/engine/runmanager.go +++ /dev/null @@ -1,214 +0,0 @@ -package engine - -import ( - "errors" - "fmt" - - "github.com/gofrs/uuid" - gctcommon "github.com/thrasher-corp/gocryptotrader/common" -) - -var ( - errRunNotFound = errors.New("run not found") - errRunAlreadyMonitored = errors.New("run already monitored") - errAlreadyRan = errors.New("run already ran") - errRunHasNotRan = errors.New("run hasn't ran yet") - errRunIsRunning = errors.New("run is already running") - errCannotClear = errors.New("cannot clear run") -) - -// SetupRunManager creates a run manager to allow the backtester to manage multiple strategies -func SetupRunManager() *RunManager { - return &RunManager{} -} - -// AddRun adds a run to the manager -func (r *RunManager) AddRun(b *BackTest) error { - if r == nil { - return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - if b == nil { - return fmt.Errorf("%w BackTest", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - for i := range r.runs { - if r.runs[i].Equal(b) { - return fmt.Errorf("%w %s %s", errRunAlreadyMonitored, b.MetaData.ID, b.MetaData.Strategy) - } - } - - err := b.SetupMetaData() - if err != nil { - return err - } - r.runs = append(r.runs, b) - return nil -} - -// List details all backtesting/livestrategy runs -func (r *RunManager) List() ([]*RunSummary, error) { - if r == nil { - return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - resp := make([]*RunSummary, len(r.runs)) - for i := range r.runs { - sum, err := r.runs[i].GenerateSummary() - if err != nil { - return nil, err - } - resp[i] = sum - } - return resp, nil -} - -// GetSummary returns details about a completed backtesting/livestrategy run -func (r *RunManager) GetSummary(id uuid.UUID) (*RunSummary, error) { - if r == nil { - return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - for i := range r.runs { - if !r.runs[i].MatchesID(id) { - continue - } - return r.runs[i].GenerateSummary() - } - return nil, fmt.Errorf("%s %w", id, errRunNotFound) -} - -// StopRun stops a backtesting/livestrategy run if enabled, this will run CloseAllPositions -func (r *RunManager) StopRun(id uuid.UUID) error { - if r == nil { - return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - for i := range r.runs { - switch { - case !r.runs[i].MatchesID(id): - continue - case r.runs[i].IsRunning(): - r.runs[i].Stop() - return nil - case r.runs[i].HasRan(): - return fmt.Errorf("%w %v", errAlreadyRan, id) - default: - return fmt.Errorf("%w %v", errRunHasNotRan, id) - } - } - return fmt.Errorf("%s %w", id, errRunNotFound) -} - -// StopAllRuns stops all running strategies -func (r *RunManager) StopAllRuns() ([]*RunSummary, error) { - if r == nil { - return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - resp := make([]*RunSummary, 0, len(r.runs)) - for i := range r.runs { - if !r.runs[i].IsRunning() { - continue - } - r.runs[i].Stop() - sum, err := r.runs[i].GenerateSummary() - if err != nil { - return nil, err - } - resp = append(resp, sum) - } - return resp, nil -} - -// StartRun executes a strategy if found -func (r *RunManager) StartRun(id uuid.UUID) error { - if r == nil { - return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - for i := range r.runs { - switch { - case !r.runs[i].MatchesID(id): - continue - case r.runs[i].IsRunning(): - return fmt.Errorf("%w %v", errRunIsRunning, id) - case r.runs[i].HasRan(): - return fmt.Errorf("%w %v", errAlreadyRan, id) - default: - return r.runs[i].ExecuteStrategy(false) - } - } - return fmt.Errorf("%s %w", id, errRunNotFound) -} - -// StartAllRuns executes all strategies -func (r *RunManager) StartAllRuns() ([]uuid.UUID, error) { - if r == nil { - return nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - executedRuns := make([]uuid.UUID, 0, len(r.runs)) - for i := range r.runs { - if r.runs[i].HasRan() { - continue - } - executedRuns = append(executedRuns, r.runs[i].MetaData.ID) - err := r.runs[i].ExecuteStrategy(false) - if err != nil { - return nil, err - } - } - - return executedRuns, nil -} - -// ClearRun removes a run from memory -func (r *RunManager) ClearRun(id uuid.UUID) error { - if r == nil { - return fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - for i := range r.runs { - if !r.runs[i].MatchesID(id) { - continue - } - if r.runs[i].IsRunning() { - return fmt.Errorf("%w %v, currently running. Stop it first", errCannotClear, r.runs[i].MetaData.ID) - } - r.runs = append(r.runs[:i], r.runs[i+1:]...) - return nil - } - return fmt.Errorf("%s %w", id, errRunNotFound) -} - -// ClearAllRuns removes all runs from memory -func (r *RunManager) ClearAllRuns() (clearedRuns, remainingRuns []*RunSummary, err error) { - if r == nil { - return nil, nil, fmt.Errorf("%w RunManager", gctcommon.ErrNilPointer) - } - r.m.Lock() - defer r.m.Unlock() - for i := 0; i < len(r.runs); i++ { - var run *RunSummary - run, err = r.runs[i].GenerateSummary() - if err != nil { - return nil, nil, err - } - if r.runs[i].IsRunning() { - remainingRuns = append(remainingRuns, run) - } else { - clearedRuns = append(clearedRuns, run) - r.runs = append(r.runs[:i], r.runs[i+1:]...) - i-- - } - } - return clearedRuns, remainingRuns, nil -} diff --git a/backtester/engine/setup.go b/backtester/engine/setup.go index ae809a9b..1f93d6a5 100644 --- a/backtester/engine/setup.go +++ b/backtester/engine/setup.go @@ -8,15 +8,16 @@ import ( "runtime" "strings" "sync" - "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/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline/api" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline/csv" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline/database" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange/slippage" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" @@ -37,45 +38,57 @@ import ( "github.com/thrasher-corp/gocryptotrader/engine" gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/currencystate" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/log" ) -// NewFromConfig takes a strategy config and configures a backtester variable to run -func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool) (*BackTest, error) { - log.Infoln(common.Setup, "Loading config...") - if cfg == nil { - return nil, errNilConfig +// NewBacktester returns a new BackTest instance +func NewBacktester() (*BackTest, error) { + bt := &BackTest{ + shutdown: make(chan struct{}), + DataHolder: &data.HandlerHolder{}, + EventQueue: &eventholder.Holder{}, + hasProcessedDataAtOffset: make(map[int64]bool), } - bt, err := New() + err := bt.SetupMetaData() if err != nil { return nil, err } bt.exchangeManager = engine.SetupExchangeManager() - bt.orderManager, err = engine.SetupOrderManager(bt.exchangeManager, &engine.CommunicationManager{}, &sync.WaitGroup{}, false, false, 0) - if err != nil { - return nil, err + + return bt, nil +} + +// SetupFromConfig takes a strategy config and configures a backtester variable to run +func (bt *BackTest) SetupFromConfig(cfg *config.Config, templatePath, output string, verbose bool) error { + var err error + defer func() { + if err != nil { + log.Errorf(common.Backtester, "Could not setup backtester %v: %v", cfg.Nickname, err) + } + }() + log.Infoln(common.Setup, "Loading config...") + if cfg == nil { + return errNilConfig } - err = bt.orderManager.Start() - if err != nil { - return nil, err - } if cfg.DataSettings.DatabaseData != nil { bt.databaseManager, err = engine.SetupDatabaseConnectionManager(&cfg.DataSettings.DatabaseData.Config) if err != nil { - return nil, err + return err } } + bt.verbose = verbose + bt.DataHolder = data.NewHandlerHolder() reports := &report.Data{ Config: cfg, TemplatePath: templatePath, OutputPath: output, } bt.Reports = reports - buyRule := exchange.MinMax{ MinimumSize: cfg.PortfolioSettings.BuySide.MinimumSize, MaximumSize: cfg.PortfolioSettings.BuySide.MaximumSize, @@ -95,12 +108,13 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool bt.exchangeManager, cfg.FundingSettings.UseExchangeLevelFunding, cfg.StrategySettings.DisableUSDTracking, + bt.verbose, ) if err != nil { - return nil, err + return err } - if cfg.FundingSettings.UseExchangeLevelFunding { + if cfg.FundingSettings.UseExchangeLevelFunding && !(cfg.DataSettings.LiveData != nil && cfg.DataSettings.LiveData.RealOrders) { for i := range cfg.FundingSettings.ExchangeLevelFunding { a := cfg.FundingSettings.ExchangeLevelFunding[i].Asset cq := cfg.FundingSettings.ExchangeLevelFunding[i].Currency @@ -111,71 +125,101 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool cfg.FundingSettings.ExchangeLevelFunding[i].InitialFunds, cfg.FundingSettings.ExchangeLevelFunding[i].TransferFee) if err != nil { - return nil, err + return err } err = funds.AddItem(item) if err != nil { - return nil, err + return err } } } - - var emm = make(map[string]gctexchange.IBotExchange) for i := range cfg.CurrencySettings { - _, ok := emm[cfg.CurrencySettings[i].ExchangeName] - if ok { - continue - } var exch gctexchange.IBotExchange - exch, err = bt.exchangeManager.NewExchangeByName(cfg.CurrencySettings[i].ExchangeName) + exch, err = bt.exchangeManager.GetExchangeByName(cfg.CurrencySettings[i].ExchangeName) if err != nil { - return nil, err - } - var conf *gctconfig.Exchange - conf, err = exch.GetDefaultConfig() - if err != nil { - return nil, err - } - conf.Enabled = true - conf.WebsocketTrafficTimeout = time.Second - conf.Websocket = convert.BoolPtr(false) - conf.WebsocketResponseCheckTimeout = time.Second - conf.WebsocketResponseMaxLimit = time.Second - conf.Verbose = verbose - err = exch.Setup(conf) - if err != nil { - return nil, err + if errors.Is(err, engine.ErrExchangeNotFound) { + exch, err = bt.exchangeManager.NewExchangeByName(cfg.CurrencySettings[i].ExchangeName) + if err != nil { + return err + } + exch.SetDefaults() + exchBase := exch.GetBase() + exchBase.Verbose = cfg.DataSettings.VerboseExchangeRequests + exchBase.Config = &gctconfig.Exchange{ + Name: exchBase.Name, + HTTPTimeout: gctexchange.DefaultHTTPTimeout, + BaseCurrencies: exchBase.BaseCurrencies, + CurrencyPairs: ¤cy.PairsManager{}, + } + err = exch.UpdateTradablePairs(context.TODO(), true) + if err != nil { + return err + } + if cfg.DataSettings.LiveData != nil && cfg.DataSettings.LiveData.RealOrders { + exchBase.States = currencystate.NewCurrencyStates() + } + if cfg.CurrencySettings[i].CanUseExchangeLimits || (cfg.DataSettings.LiveData != nil && cfg.DataSettings.LiveData.RealOrders) { + err = exch.UpdateOrderExecutionLimits(context.TODO(), cfg.CurrencySettings[i].Asset) + if err != nil && !errors.Is(err, gctcommon.ErrNotYetImplemented) { + return err + } + } + bt.exchangeManager.Add(exch) + } else { + return err + } } exchBase := exch.GetBase() - err = exch.UpdateTradablePairs(context.Background(), true) - if err != nil { - return nil, err + exchangeAsset, ok := exchBase.CurrencyPairs.Pairs[cfg.CurrencySettings[i].Asset] + if !ok { + return fmt.Errorf("%v %v %w", cfg.CurrencySettings[i].ExchangeName, cfg.CurrencySettings[i].Asset, asset.ErrNotSupported) } - assets := exchBase.CurrencyPairs.GetAssetTypes(false) - for i := range assets { - exchBase.CurrencyPairs.Pairs[assets[i]].AssetEnabled = convert.BoolPtr(true) - err = exch.SetPairs(exchBase.CurrencyPairs.Pairs[assets[i]].Available, assets[i], true) - if err != nil { - return nil, err - } - } - - bt.exchangeManager.Add(exch) - emm[cfg.CurrencySettings[i].ExchangeName] = exch + exchangeAsset.AssetEnabled = convert.BoolPtr(true) + cp := currency.NewPair(cfg.CurrencySettings[i].Base, cfg.CurrencySettings[i].Quote).Format(*exchangeAsset.RequestFormat) + exchangeAsset.Available = exchangeAsset.Available.Add(cp) + exchangeAsset.Enabled = exchangeAsset.Enabled.Add(cp) + exchBase.Verbose = verbose + exchBase.CurrencyPairs.Pairs[cfg.CurrencySettings[i].Asset] = exchangeAsset } portfolioRisk := &risk.Risk{ - CurrencySettings: make(map[string]map[asset.Item]map[currency.Pair]*risk.CurrencySettings), + CurrencySettings: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*risk.CurrencySettings), + } + + bt.Funding = funds + var trackFuturesPositions bool + if cfg.DataSettings.LiveData != nil { + trackFuturesPositions = cfg.DataSettings.LiveData.RealOrders + err = bt.SetupLiveDataHandler(cfg.DataSettings.LiveData.NewEventTimeout, cfg.DataSettings.LiveData.DataCheckTimer, cfg.DataSettings.LiveData.RealOrders, verbose) + if err != nil { + return err + } + } + + bt.orderManager, err = engine.SetupOrderManager( + bt.exchangeManager, + &engine.CommunicationManager{}, + &sync.WaitGroup{}, + verbose, + trackFuturesPositions, + 0) + if err != nil { + return err + } + + err = bt.orderManager.Start() + if err != nil { + return err } for i := range cfg.CurrencySettings { if portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName] == nil { - portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName] = make(map[asset.Item]map[currency.Pair]*risk.CurrencySettings) + portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*risk.CurrencySettings) } a := cfg.CurrencySettings[i].Asset if !a.IsValid() { - return nil, fmt.Errorf( + return fmt.Errorf( "%w for %v %v %v-%v. Err %v", asset.ErrNotSupported, cfg.CurrencySettings[i].ExchangeName, @@ -185,46 +229,28 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool err) } if portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a] == nil { - portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a] = make(map[currency.Pair]*risk.CurrencySettings) + portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a] = make(map[*currency.Item]map[*currency.Item]*risk.CurrencySettings) + } + if portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a][cfg.CurrencySettings[i].Base.Item] == nil { + portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a][cfg.CurrencySettings[i].Base.Item] = make(map[*currency.Item]*risk.CurrencySettings) } var curr currency.Pair var b, q currency.Code b = cfg.CurrencySettings[i].Base q = cfg.CurrencySettings[i].Quote - curr = currency.NewPair(b, q) + curr = currency.NewPair(b, q).Format(currency.EMPTYFORMAT) var exch gctexchange.IBotExchange exch, err = bt.exchangeManager.GetExchangeByName(cfg.CurrencySettings[i].ExchangeName) if err != nil { - return nil, err + return err } - exchBase := exch.GetBase() - var requestFormat currency.PairFormat - requestFormat, err = exchBase.GetPairFormat(a, true) - if err != nil { - return nil, fmt.Errorf("could not get pair format %v, %w", curr, err) + if cfg.DataSettings.LiveData != nil && cfg.DataSettings.LiveData.RealOrders { + exchBase := exch.GetBase() + err = setExchangeCredentials(cfg, exchBase) + if err != nil { + return err + } } - curr = curr.Format(requestFormat) - var avail, enabled currency.Pairs - avail, err = exch.GetAvailablePairs(a) - if err != nil { - return nil, fmt.Errorf("could not format currency %v, %w", curr, err) - } - enabled, err = exch.GetEnabledPairs(a) - if err != nil { - return nil, fmt.Errorf("could not format currency %v, %w", curr, err) - } - - avail = avail.Add(curr) - enabled = enabled.Add(curr) - err = exch.SetPairs(enabled, a, true) - if err != nil { - return nil, fmt.Errorf("could not format currency %v, %w", curr, err) - } - err = exch.SetPairs(avail, a, false) - if err != nil { - return nil, fmt.Errorf("could not format currency %v, %w", curr, err) - } - portSet := &risk.CurrencySettings{ MaximumHoldingRatio: cfg.CurrencySettings[i].MaximumHoldingsRatio, } @@ -232,7 +258,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool portSet.MaximumOrdersWithLeverageRatio = cfg.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrdersWithLeverageRatio portSet.MaxLeverageRate = cfg.CurrencySettings[i].FuturesDetails.Leverage.MaximumOrderLeverageRate } - portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a][curr] = portSet + portfolioRisk.CurrencySettings[cfg.CurrencySettings[i].ExchangeName][a][curr.Base.Item][curr.Quote.Item] = portSet if cfg.CurrencySettings[i].MakerFee != nil && cfg.CurrencySettings[i].TakerFee != nil && cfg.CurrencySettings[i].MakerFee.GreaterThan(*cfg.CurrencySettings[i].TakerFee) { @@ -242,7 +268,8 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool } var baseItem, quoteItem, futureItem *funding.Item - if cfg.FundingSettings.UseExchangeLevelFunding { + switch { + case cfg.FundingSettings.UseExchangeLevelFunding: switch { case a == asset.Spot: // add any remaining currency items that have no funding data in the strategy config @@ -252,7 +279,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool decimal.Zero, decimal.Zero) if err != nil { - return nil, err + return err } quoteItem, err = funding.CreateItem(cfg.CurrencySettings[i].ExchangeName, a, @@ -260,15 +287,15 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool decimal.Zero, decimal.Zero) if err != nil { - return nil, err + return err } err = funds.AddItem(baseItem) if err != nil && !errors.Is(err, funding.ErrAlreadyExists) { - return nil, err + return err } err = funds.AddItem(quoteItem) if err != nil && !errors.Is(err, funding.ErrAlreadyExists) { - return nil, err + return err } case a.IsFutures(): // setup contract items @@ -279,27 +306,27 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool decimal.Zero, decimal.Zero) if err != nil { - return nil, err + return err } var collateralCurrency currency.Code collateralCurrency, _, err = exch.GetCollateralCurrencyForContract(a, currency.NewPair(b, q)) if err != nil { - return nil, err + return err } err = funds.LinkCollateralCurrency(futureItem, collateralCurrency) if err != nil { - return nil, err + return err } err = funds.AddItem(futureItem) if err != nil { - return nil, err + return err } default: - return nil, fmt.Errorf("%w: %v", asset.ErrNotSupported, a) + return fmt.Errorf("%w: %v", asset.ErrNotSupported, a) } - } else { + default: var bFunds, qFunds decimal.Decimal if cfg.CurrencySettings[i].SpotDetails != nil { if cfg.CurrencySettings[i].SpotDetails.InitialBaseFunds != nil { @@ -316,7 +343,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool bFunds, decimal.Zero) if err != nil { - return nil, err + return err } quoteItem, err = funding.CreateItem( cfg.CurrencySettings[i].ExchangeName, @@ -325,30 +352,29 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool qFunds, decimal.Zero) if err != nil { - return nil, err + return err } var pair *funding.SpotPair pair, err = funding.CreatePair(baseItem, quoteItem) if err != nil { - return nil, err + return err } err = funds.AddPair(pair) if err != nil { - return nil, err + return err } } } - bt.Funding = funds var p *portfolio.Portfolio p, err = portfolio.Setup(sizeManager, portfolioRisk, cfg.StatisticSettings.RiskFreeRate) if err != nil { - return nil, err + return err } bt.Strategy, err = strategies.LoadStrategyByName(cfg.StrategySettings.Name, cfg.StrategySettings.SimultaneousSignalProcessing) if err != nil { - return nil, err + return err } bt.MetaData.Strategy = bt.Strategy.Name() bt.Strategy.SetDefaults() @@ -356,7 +382,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool if cfg.StrategySettings.CustomSettings != nil { err = bt.Strategy.SetCustomSettings(cfg.StrategySettings.CustomSettings) if err != nil && !errors.Is(err, base.ErrCustomSettingsUnsupported) { - return nil, err + return err } } stats := &statistics.Statistic{ @@ -364,7 +390,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool StrategyNickname: cfg.Nickname, StrategyDescription: bt.Strategy.Description(), StrategyGoal: cfg.Goal, - ExchangeAssetPairStatistics: make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic), + ExchangeAssetPairStatistics: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic), RiskFreeRate: cfg.StatisticSettings.RiskFreeRate, CandleInterval: cfg.DataSettings.Interval, FundManager: bt.Funding, @@ -384,7 +410,7 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool } trackingPairs, err = trackingcurrencies.CreateUSDTrackingPairs(trackingPairs, bt.exchangeManager) if err != nil { - return nil, err + return err } trackingPairCheck: for i := range trackingPairs { @@ -403,44 +429,55 @@ func NewFromConfig(cfg *config.Config, templatePath, output string, verbose bool Quote: trackingPairs[i].Quote, USDTrackingPair: true, }) + // ensure new tracking pairs are enabled + var exch gctexchange.IBotExchange + exch, err = bt.exchangeManager.GetExchangeByName(trackingPairs[i].Exchange) + if err != nil { + return err + } + exchBase := exch.GetBase() + exchangeAsset := exchBase.CurrencyPairs.Pairs[trackingPairs[i].Asset] // no ok as handled earlier + exchangeAsset.Enabled = exchangeAsset.Enabled.Add(currency.NewPair(trackingPairs[i].Base, trackingPairs[i].Quote)) } } e, err := bt.setupExchangeSettings(cfg) if err != nil { - return nil, err + return err } - bt.Exchange = &e + bt.Exchange = e for i := range e.CurrencySettings { - err = p.SetupCurrencySettingsMap(&e.CurrencySettings[i]) + err = p.SetCurrencySettingsMap(&e.CurrencySettings[i]) if err != nil { - return nil, err + return err } } bt.Portfolio = p - hasFunding := false - fundingItems := funds.GetAllFunding() + fundingItems, err := funds.GetAllFunding() + if err != nil { + return err + } for i := range fundingItems { if fundingItems[i].InitialFunds.IsPositive() { hasFunding = true break } } - if !hasFunding { - return nil, holdings.ErrInitialFundsZero + if !hasFunding && !(cfg.DataSettings.LiveData != nil && cfg.DataSettings.LiveData.RealOrders) { + return holdings.ErrInitialFundsZero } cfg.PrintSetting() - return bt, nil + return nil } -func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange, error) { +func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (*exchange.Exchange, error) { log.Infoln(common.Setup, "Setting exchange settings...") - resp := exchange.Exchange{} + resp := &exchange.Exchange{} for i := range cfg.CurrencySettings { exch, pair, a, err := bt.loadExchangePairAssetBase( cfg.CurrencySettings[i].ExchangeName, @@ -448,29 +485,31 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange cfg.CurrencySettings[i].Quote, cfg.CurrencySettings[i].Asset) if err != nil { - return resp, err + return nil, err } exchangeName := strings.ToLower(exch.GetName()) - bt.Datas.Setup() klineData, err := bt.loadData(cfg, exch, pair, a, cfg.CurrencySettings[i].USDTrackingPair) if err != nil { - return resp, err + return nil, err } + if bt.LiveDataHandler == nil { + err = bt.Funding.AddUSDTrackingData(klineData) + if err != nil && + !errors.Is(err, trackingcurrencies.ErrCurrencyDoesNotContainsUSD) && + !errors.Is(err, funding.ErrUSDTrackingDisabled) { + return nil, err + } - err = bt.Funding.AddUSDTrackingData(klineData) - if err != nil && - !errors.Is(err, trackingcurrencies.ErrCurrencyDoesNotContainsUSD) && - !errors.Is(err, funding.ErrUSDTrackingDisabled) { - return resp, err + if cfg.CurrencySettings[i].USDTrackingPair { + continue + } + + err = bt.DataHolder.SetDataForCurrency(exchangeName, a, pair, klineData) + if err != nil { + return nil, err + } } - - if cfg.CurrencySettings[i].USDTrackingPair { - continue - } - - bt.Datas.SetDataForCurrency(exchangeName, a, pair, klineData) - var makerFee, takerFee decimal.Decimal if cfg.CurrencySettings[i].MakerFee != nil && cfg.CurrencySettings[i].MakerFee.GreaterThan(decimal.Zero) { makerFee = *cfg.CurrencySettings[i].MakerFee @@ -480,7 +519,10 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange } if cfg.CurrencySettings[i].TakerFee == nil || cfg.CurrencySettings[i].MakerFee == nil { var apiMakerFee, apiTakerFee decimal.Decimal - apiMakerFee, apiTakerFee = getFees(context.TODO(), exch, pair) + apiMakerFee, apiTakerFee, err = getFees(context.TODO(), exch, pair) + if err != nil { + log.Errorf(common.Setup, "Could not retrieve fees for %v. %v", exch.GetName(), err) + } if cfg.CurrencySettings[i].MakerFee == nil { makerFee = apiMakerFee cfg.CurrencySettings[i].MakerFee = &makerFee @@ -520,6 +562,7 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange realOrders = cfg.DataSettings.LiveData.RealOrders bt.MetaData.LiveTesting = true bt.MetaData.RealOrders = realOrders + bt.MetaData.ClosePositionsOnStop = cfg.DataSettings.LiveData.ClosePositionsOnStop } buyRule := exchange.MinMax{ @@ -540,11 +583,19 @@ func (bt *BackTest) setupExchangeSettings(cfg *config.Config) (exchange.Exchange if limits != (gctorder.MinMaxLevel{}) { if !cfg.CurrencySettings[i].CanUseExchangeLimits { - log.Warnf(common.Setup, "Exchange %s order execution limits supported but disabled for %s %s, live results may differ", - cfg.CurrencySettings[i].ExchangeName, - pair, - a) - cfg.CurrencySettings[i].ShowExchangeOrderLimitWarning = true + if realOrders { + log.Warnf(common.Setup, "Exchange %s order execution limits enabled for %s %s due to using real orders", + cfg.CurrencySettings[i].ExchangeName, + pair, + a) + cfg.CurrencySettings[i].CanUseExchangeLimits = true + } else { + log.Warnf(common.Setup, "Exchange %s order execution limits supported but disabled for %s %s, live results may differ", + cfg.CurrencySettings[i].ExchangeName, + pair, + a) + cfg.CurrencySettings[i].ShowExchangeOrderLimitWarning = true + } } } var lev exchange.Leverage @@ -587,10 +638,6 @@ func (bt *BackTest) loadExchangePairAssetBase(exch string, base, quote currency. cp = currency.NewPair(base, quote) exchangeBase := e.GetBase() - if exchangeBase.ValidateAPICredentials(exchangeBase.GetDefaultCredentials()) != nil { - log.Warnf(common.Setup, "No credentials set for %v, this is theoretical only", exchangeBase.Name) - } - fPair, err = exchangeBase.FormatExchangeCurrency(cp, ai) if err != nil { return nil, currency.EMPTYPAIR, asset.Empty, err @@ -599,7 +646,13 @@ func (bt *BackTest) loadExchangePairAssetBase(exch string, base, quote currency. } // getFees will return an exchange's fee rate from GCT's wrapper function -func getFees(ctx context.Context, exch gctexchange.IBotExchange, fPair currency.Pair) (makerFee, takerFee decimal.Decimal) { +func getFees(ctx context.Context, exch gctexchange.IBotExchange, fPair currency.Pair) (makerFee, takerFee decimal.Decimal, err error) { + if exch == nil { + return decimal.Zero, decimal.Zero, fmt.Errorf("exchange %w", gctcommon.ErrNilPointer) + } + if fPair.IsEmpty() { + return decimal.Zero, decimal.Zero, currency.ErrCurrencyPairEmpty + } fTakerFee, err := exch.GetFeeByType(ctx, &gctexchange.FeeBuilder{FeeType: gctexchange.OfflineTradeFee, Pair: fPair, @@ -608,7 +661,7 @@ func getFees(ctx context.Context, exch gctexchange.IBotExchange, fPair currency. Amount: 1, }) if err != nil { - log.Errorf(common.Setup, "Could not retrieve taker fee for %v. %v", exch.GetName(), err) + return decimal.Zero, decimal.Zero, err } fMakerFee, err := exch.GetFeeByType(ctx, @@ -620,10 +673,10 @@ func getFees(ctx context.Context, exch gctexchange.IBotExchange, fPair currency. Amount: 1, }) if err != nil { - log.Errorf(common.Setup, "Could not retrieve maker fee for %v. %v", exch.GetName(), err) + return decimal.Zero, decimal.Zero, err } - return decimal.NewFromFloat(fMakerFee), decimal.NewFromFloat(fTakerFee) + return decimal.NewFromFloat(fMakerFee), decimal.NewFromFloat(fTakerFee), nil } // loadData will create kline data from the sources defined in start config files. It can exist from databases, csv or API endpoints @@ -654,7 +707,23 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange, } log.Infof(common.Setup, "Loading data for %v %v %v...\n", exch.GetName(), a, fPair) - resp := &kline.DataFromKline{} + resp := kline.NewDataFromKline() + underlyingPair := currency.EMPTYPAIR + if a.IsFutures() { + // returning the collateral currency along with using the + // fPair base creates a pair that links the futures contract to + // its underlyingPair pair + // eg BTCUSDT-PERP on Binance has a collateral currency of USDT + // taking the BTC base and USDT as quote, allows linking + // BTC-USDT and BTCUSDT-PERP + var curr currency.Code + curr, _, err = exch.GetCollateralCurrencyForContract(a, fPair) + if err != nil { + return resp, err + } + underlyingPair = currency.NewPair(fPair.Base, curr) + } + switch { case cfg.DataSettings.CSVData != nil: if cfg.DataSettings.Interval <= 0 { @@ -671,7 +740,7 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange, if err != nil { return nil, fmt.Errorf("%v. Please check your GoCryptoTrader configuration", err) } - resp.Item.RemoveDuplicates() + resp.Item.RemoveDuplicateCandlesByTime() resp.Item.SortCandlesByTimestamp(false) resp.RangeHolder, err = gctkline.CalculateCandleDateRanges( resp.Item.Candles[0].Time, @@ -714,7 +783,7 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange, return nil, fmt.Errorf("unable to retrieve data from GoCryptoTrader database. Error: %v. Please ensure the database is setup correctly and has data before use", err) } - resp.Item.RemoveDuplicates() + resp.Item.RemoveDuplicateCandlesByTime() resp.Item.SortCandlesByTimestamp(false) resp.RangeHolder, err = gctkline.CalculateCandleDateRanges( cfg.DataSettings.DatabaseData.StartDate, @@ -745,44 +814,25 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange, return resp, err } case cfg.DataSettings.LiveData != nil: - if isUSDTrackingPair { - return nil, errLiveUSDTrackingNotSupported - } - if len(cfg.CurrencySettings) > 1 { - return nil, errors.New("live data simulation only supports one currency") - } - err = loadLiveData(cfg, b) - if err != nil { - return nil, err - } - go bt.loadLiveDataLoop( - resp, - cfg, - exch, - fPair, - a, - dataType) - return resp, nil + bt.exchangeManager.Add(exch) + err = bt.LiveDataHandler.AppendDataSource(&liveDataSourceSetup{ + exchange: exch, + interval: cfg.DataSettings.Interval, + asset: a, + pair: fPair, + underlyingPair: underlyingPair, + dataType: dataType, + dataRequestRetryTolerance: cfg.DataSettings.LiveData.DataRequestRetryTolerance, + dataRequestRetryWaitTime: cfg.DataSettings.LiveData.DataRequestRetryWaitTime, + verboseExchangeRequest: cfg.DataSettings.VerboseExchangeRequests, + }) + return nil, err } if resp == nil { return nil, fmt.Errorf("processing error, response returned nil") } - if a.IsFutures() { - // returning the collateral currency along with using the - // fPair base creates a pair that links the futures contract to - // is underlying pair - // eg BTC-PERP on FTX has a collateral currency of USD - // taking the BTC base and USD as quote, allows linking - // BTC-USD and BTC-PERP - var curr currency.Code - curr, _, err = exch.GetCollateralCurrencyForContract(a, fPair) - if err != nil { - return resp, err - } - resp.Item.UnderlyingPair = currency.NewPair(fPair.Base, curr) - } - + resp.Item.UnderlyingPair = underlyingPair err = b.ValidateKline(fPair, a, resp.Item.Interval) if err != nil { if dataType != common.DataTrade || !strings.EqualFold(err.Error(), "interval not supported") { @@ -794,7 +844,10 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange, if err != nil { return nil, err } - bt.Reports.AddKlineItem(&resp.Item) + err = bt.Reports.SetKlineData(&resp.Item) + if err != nil { + return nil, err + } return resp, nil } @@ -848,41 +901,47 @@ func loadAPIData(cfg *config.Config, exch gctexchange.IBotExchange, fPair curren candles.FillMissingDataWithEmptyEntries(dates) candles.RemoveOutsideRange(cfg.DataSettings.APIData.StartDate, cfg.DataSettings.APIData.EndDate) return &kline.DataFromKline{ + Base: &data.Base{}, Item: *candles, RangeHolder: dates, }, nil } -func loadLiveData(cfg *config.Config, base *gctexchange.Base) error { +func setExchangeCredentials(cfg *config.Config, base *gctexchange.Base) error { if cfg == nil || base == nil || cfg.DataSettings.LiveData == nil { - return common.ErrNilArguments + return gctcommon.ErrNilPointer + } + if !cfg.DataSettings.LiveData.RealOrders { + return nil } if cfg.DataSettings.Interval <= 0 { return errIntervalUnset } + if len(cfg.DataSettings.LiveData.ExchangeCredentials) == 0 { + return errNoCredsNoLive + } + name := strings.ToLower(base.Name) + for i := range cfg.DataSettings.LiveData.ExchangeCredentials { + if !strings.EqualFold(cfg.DataSettings.LiveData.ExchangeCredentials[i].Exchange, name) || + cfg.DataSettings.LiveData.ExchangeCredentials[i].Keys.IsEmpty() { + return fmt.Errorf("%v %w, please review your live, real order config", base.GetName(), gctexchange.ErrCredentialsAreEmpty) + } - if cfg.DataSettings.LiveData.APIKeyOverride != "" { - base.API.SetKey(cfg.DataSettings.LiveData.APIKeyOverride) - } - if cfg.DataSettings.LiveData.APISecretOverride != "" { - base.API.SetSecret(cfg.DataSettings.LiveData.APISecretOverride) - } - if cfg.DataSettings.LiveData.APIClientIDOverride != "" { - base.API.SetClientID(cfg.DataSettings.LiveData.APIClientIDOverride) - } - if cfg.DataSettings.LiveData.API2FAOverride != "" { - base.API.SetPEMKey(cfg.DataSettings.LiveData.API2FAOverride) - } - if cfg.DataSettings.LiveData.APISubAccountOverride != "" { - base.API.SetSubAccount(cfg.DataSettings.LiveData.APISubAccountOverride) + base.SetCredentials( + cfg.DataSettings.LiveData.ExchangeCredentials[i].Keys.Key, + cfg.DataSettings.LiveData.ExchangeCredentials[i].Keys.Secret, + cfg.DataSettings.LiveData.ExchangeCredentials[i].Keys.ClientID, + cfg.DataSettings.LiveData.ExchangeCredentials[i].Keys.SubAccount, + cfg.DataSettings.LiveData.ExchangeCredentials[i].Keys.PEMKey, + cfg.DataSettings.LiveData.ExchangeCredentials[i].Keys.OneTimePassword, + ) + validated := base.AreCredentialsValid(context.TODO()) + base.API.AuthenticatedSupport = validated + if !validated { + return fmt.Errorf("%v %w", base.GetName(), errInvalidCredentials) + } } - validated := base.AreCredentialsValid(context.TODO()) - base.API.AuthenticatedSupport = validated - if !validated && cfg.DataSettings.LiveData.RealOrders { - log.Warn(common.Setup, "Invalid API credentials set, real orders set to false") - cfg.DataSettings.LiveData.RealOrders = false - } return nil } @@ -897,7 +956,11 @@ func NewBacktesterFromConfigs(strategyCfg *config.Config, backtesterCfg *config. if err := strategyCfg.Validate(); err != nil { return nil, err } - bt, err := NewFromConfig(strategyCfg, backtesterCfg.Report.TemplatePath, backtesterCfg.Report.OutputPath, backtesterCfg.Verbose) + bt, err := NewBacktester() + if err != nil { + return nil, err + } + err = bt.SetupFromConfig(strategyCfg, backtesterCfg.Report.TemplatePath, backtesterCfg.Report.OutputPath, backtesterCfg.Verbose) if err != nil { return nil, err } diff --git a/backtester/engine/setup_test.go b/backtester/engine/setup_test.go deleted file mode 100644 index a8dc43b6..00000000 --- a/backtester/engine/setup_test.go +++ /dev/null @@ -1,411 +0,0 @@ -package engine - -import ( - "errors" - "path/filepath" - "strings" - "testing" - "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/portfolio/holdings" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage" - "github.com/thrasher-corp/gocryptotrader/backtester/report" - gctcommon "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/common/convert" - "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/database" - "github.com/thrasher-corp/gocryptotrader/database/drivers" - "github.com/thrasher-corp/gocryptotrader/engine" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" -) - -func TestNewFromConfig(t *testing.T) { - t.Parallel() - _, err := NewFromConfig(nil, "", "", false) - if !errors.Is(err, errNilConfig) { - t.Errorf("received %v, expected %v", err, errNilConfig) - } - - cfg := &config.Config{} - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, base.ErrStrategyNotFound) { - t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound) - } - - cfg.CurrencySettings = []config.CurrencySettings{ - { - ExchangeName: "test", - Base: currency.NewCode("test"), - Quote: currency.NewCode("test"), - }, - { - ExchangeName: testExchange, - Base: currency.BTC, - Quote: currency.NewCode("0624"), - Asset: asset.Futures, - }, - } - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, engine.ErrExchangeNotFound) { - t.Errorf("received: %v, expected: %v", err, engine.ErrExchangeNotFound) - } - cfg.CurrencySettings[0].ExchangeName = testExchange - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, asset.ErrNotSupported) { - t.Errorf("received: %v, expected: %v", err, asset.ErrNotSupported) - } - cfg.CurrencySettings[0].Asset = asset.Spot - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, base.ErrStrategyNotFound) { - t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound) - } - - cfg.StrategySettings = config.StrategySettings{ - Name: dollarcostaverage.Name, - CustomSettings: map[string]interface{}{ - "hello": "moto", - }, - } - cfg.CurrencySettings[0].Base = currency.BTC - cfg.CurrencySettings[0].Quote = currency.USD - cfg.DataSettings.APIData = &config.APIData{ - StartDate: time.Time{}, - EndDate: time.Time{}, - } - - _, err = NewFromConfig(cfg, "", "", false) - if err != nil && !strings.Contains(err.Error(), "unrecognised dataType") { - t.Error(err) - } - cfg.DataSettings.DataType = common.CandleStr - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, errIntervalUnset) { - t.Errorf("received: %v, expected: %v", err, errIntervalUnset) - } - cfg.DataSettings.Interval = gctkline.OneMin - cfg.CurrencySettings[0].MakerFee = &decimal.Zero - cfg.CurrencySettings[0].TakerFee = &decimal.Zero - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, gctcommon.ErrDateUnset) { - t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset) - } - - cfg.DataSettings.APIData.StartDate = time.Now().Add(-time.Minute) - cfg.DataSettings.APIData.EndDate = time.Now() - cfg.DataSettings.APIData.InclusiveEndDate = true - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, holdings.ErrInitialFundsZero) { - t.Errorf("received: %v, expected: %v", err, holdings.ErrInitialFundsZero) - } - - cfg.FundingSettings.UseExchangeLevelFunding = true - cfg.FundingSettings.ExchangeLevelFunding = []config.ExchangeLevelFunding{ - { - ExchangeName: testExchange, - Asset: asset.Spot, - Currency: currency.BTC, - InitialFunds: leet, - TransferFee: leet, - }, - { - ExchangeName: testExchange, - Asset: asset.Futures, - Currency: currency.BTC, - InitialFunds: leet, - TransferFee: leet, - }, - } - _, err = NewFromConfig(cfg, "", "", false) - if !errors.Is(err, nil) { - t.Errorf("received: %v, expected: %v", err, nil) - } -} - -func TestLoadDataAPI(t *testing.T) { - t.Parallel() - bt := BackTest{ - Reports: &report.Data{}, - Statistic: &statistics.Statistic{}, - } - cp := currency.NewPair(currency.BTC, currency.USDT) - cfg := &config.Config{ - CurrencySettings: []config.CurrencySettings{ - { - ExchangeName: "Binance", - Asset: asset.Spot, - Base: cp.Base, - Quote: cp.Quote, - SpotDetails: &config.SpotDetails{ - InitialQuoteFunds: &leet, - }, - BuySide: config.MinMax{}, - SellSide: config.MinMax{}, - MakerFee: &decimal.Zero, - TakerFee: &decimal.Zero, - }, - }, - DataSettings: config.DataSettings{ - DataType: common.CandleStr, - Interval: gctkline.OneMin, - APIData: &config.APIData{ - StartDate: time.Now().Add(-time.Minute * 5), - EndDate: time.Now(), - }}, - StrategySettings: config.StrategySettings{ - Name: dollarcostaverage.Name, - CustomSettings: map[string]interface{}{ - "hello": "moto", - }, - }, - } - em := engine.ExchangeManager{} - exch, err := em.NewExchangeByName("Binance") - if err != nil { - t.Fatal(err) - } - exch.SetDefaults() - b := exch.GetBase() - b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) - b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ - Available: currency.Pairs{cp}, - Enabled: currency.Pairs{cp}, - AssetEnabled: convert.BoolPtr(true), - ConfigFormat: ¤cy.PairFormat{Uppercase: true}, - RequestFormat: ¤cy.PairFormat{Uppercase: true}} - - _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) - if err != nil { - t.Error(err) - } -} - -func TestLoadDataDatabase(t *testing.T) { - t.Parallel() - bt := BackTest{ - Reports: &report.Data{}, - Statistic: &statistics.Statistic{}, - } - cp := currency.NewPair(currency.BTC, currency.USDT) - cfg := &config.Config{ - CurrencySettings: []config.CurrencySettings{ - { - ExchangeName: "Binance", - Asset: asset.Spot, - Base: cp.Base, - Quote: cp.Quote, - SpotDetails: &config.SpotDetails{ - InitialQuoteFunds: &leet, - }, - BuySide: config.MinMax{}, - SellSide: config.MinMax{}, - MakerFee: &decimal.Zero, - TakerFee: &decimal.Zero, - }, - }, - DataSettings: config.DataSettings{ - DataType: common.CandleStr, - Interval: gctkline.OneMin, - DatabaseData: &config.DatabaseData{ - Config: database.Config{ - Enabled: true, - Driver: "sqlite3", - ConnectionDetails: drivers.ConnectionDetails{ - Database: t.TempDir() + "gocryptotrader.db", - }, - }, - StartDate: time.Now().Add(-time.Minute), - EndDate: time.Now(), - InclusiveEndDate: true, - }}, - StrategySettings: config.StrategySettings{ - Name: dollarcostaverage.Name, - CustomSettings: map[string]interface{}{ - "hello": "moto", - }, - }, - } - em := engine.ExchangeManager{} - exch, err := em.NewExchangeByName("Binance") - if err != nil { - t.Fatal(err) - } - exch.SetDefaults() - b := exch.GetBase() - b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) - b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ - Available: currency.Pairs{cp}, - Enabled: currency.Pairs{cp}, - AssetEnabled: convert.BoolPtr(true), - ConfigFormat: ¤cy.PairFormat{Uppercase: true}, - RequestFormat: ¤cy.PairFormat{Uppercase: true}} - bt.databaseManager, err = engine.SetupDatabaseConnectionManager(&cfg.DataSettings.DatabaseData.Config) - if err != nil { - t.Fatal(err) - } - _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) - if err != nil && !strings.Contains(err.Error(), "unable to retrieve data from GoCryptoTrader database") { - t.Error(err) - } -} - -func TestLoadDataCSV(t *testing.T) { - t.Parallel() - bt := BackTest{ - Reports: &report.Data{}, - Statistic: &statistics.Statistic{}, - } - cp := currency.NewPair(currency.BTC, currency.USDT) - cfg := &config.Config{ - CurrencySettings: []config.CurrencySettings{ - { - ExchangeName: "Binance", - Asset: asset.Spot, - Base: cp.Base, - Quote: cp.Quote, - SpotDetails: &config.SpotDetails{ - InitialQuoteFunds: &leet, - }, - BuySide: config.MinMax{}, - SellSide: config.MinMax{}, - MakerFee: &decimal.Zero, - TakerFee: &decimal.Zero, - }, - }, - DataSettings: config.DataSettings{ - DataType: common.CandleStr, - Interval: gctkline.OneMin, - CSVData: &config.CSVData{ - FullPath: "test", - }}, - StrategySettings: config.StrategySettings{ - Name: dollarcostaverage.Name, - CustomSettings: map[string]interface{}{ - "hello": "moto", - }, - }, - } - em := engine.ExchangeManager{} - exch, err := em.NewExchangeByName("Binance") - if err != nil { - t.Fatal(err) - } - exch.SetDefaults() - b := exch.GetBase() - b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) - b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ - Available: currency.Pairs{cp}, - Enabled: currency.Pairs{cp}, - AssetEnabled: convert.BoolPtr(true), - ConfigFormat: ¤cy.PairFormat{Uppercase: true}, - RequestFormat: ¤cy.PairFormat{Uppercase: true}} - _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) - if err != nil && - !strings.Contains(err.Error(), "The system cannot find the file specified.") && - !strings.Contains(err.Error(), "no such file or directory") { - t.Error(err) - } -} - -func TestLoadDataLive(t *testing.T) { - t.Parallel() - bt := BackTest{ - Reports: &report.Data{}, - Statistic: &statistics.Statistic{}, - shutdown: make(chan struct{}), - } - cp := currency.NewPair(currency.BTC, currency.USDT) - cfg := &config.Config{ - CurrencySettings: []config.CurrencySettings{ - { - ExchangeName: "Binance", - Asset: asset.Spot, - Base: cp.Base, - Quote: cp.Quote, - SpotDetails: &config.SpotDetails{ - InitialQuoteFunds: &leet, - }, - BuySide: config.MinMax{}, - SellSide: config.MinMax{}, - MakerFee: &decimal.Zero, - TakerFee: &decimal.Zero, - }, - }, - DataSettings: config.DataSettings{ - DataType: common.CandleStr, - Interval: gctkline.OneMin, - LiveData: &config.LiveData{ - APIKeyOverride: "test", - APISecretOverride: "test", - APIClientIDOverride: "test", - API2FAOverride: "test", - RealOrders: true, - }}, - StrategySettings: config.StrategySettings{ - Name: dollarcostaverage.Name, - CustomSettings: map[string]interface{}{ - "hello": "moto", - }, - }, - } - em := engine.ExchangeManager{} - exch, err := em.NewExchangeByName("Binance") - if err != nil { - t.Fatal(err) - } - exch.SetDefaults() - b := exch.GetBase() - b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) - b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ - Available: currency.Pairs{cp}, - Enabled: currency.Pairs{cp}, - AssetEnabled: convert.BoolPtr(true), - ConfigFormat: ¤cy.PairFormat{Uppercase: true}, - RequestFormat: ¤cy.PairFormat{Uppercase: true}} - _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) - if err != nil { - t.Error(err) - } - bt.Stop() -} - -func TestNewBacktesterFromConfigs(t *testing.T) { - t.Parallel() - _, err := NewBacktesterFromConfigs(nil, nil) - if !errors.Is(err, gctcommon.ErrNilPointer) { - t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) - } - - strat1 := filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat") - cfg, err := config.ReadStrategyConfigFromFile(strat1) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - dc, err := config.GenerateDefaultConfig() - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - - _, err = NewBacktesterFromConfigs(cfg, nil) - if !errors.Is(err, gctcommon.ErrNilPointer) { - t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) - } - - _, err = NewBacktesterFromConfigs(nil, dc) - if !errors.Is(err, gctcommon.ErrNilPointer) { - t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) - } - - bt, err := NewBacktesterFromConfigs(cfg, dc) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - if bt.MetaData.DateLoaded.IsZero() { - t.Errorf("received '%v' expected '%v'", bt.MetaData.DateLoaded, "a date") - } -} diff --git a/backtester/engine/taskmanager.go b/backtester/engine/taskmanager.go new file mode 100644 index 00000000..33674eca --- /dev/null +++ b/backtester/engine/taskmanager.go @@ -0,0 +1,216 @@ +package engine + +import ( + "errors" + "fmt" + + "github.com/gofrs/uuid" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" +) + +var ( + errTaskNotFound = errors.New("task not found") + errTaskAlreadyMonitored = errors.New("task already monitored") + errAlreadyRan = errors.New("task already ran") + errTaskHasNotRan = errors.New("task hasn't ran yet") + errTaskIsRunning = errors.New("task is already running") + errCannotClear = errors.New("cannot clear task") +) + +// NewTaskManager creates a run manager to allow the backtester to manage multiple strategies +func NewTaskManager() *TaskManager { + return &TaskManager{} +} + +// AddTask adds a run to the manager +func (r *TaskManager) AddTask(b *BackTest) error { + if r == nil { + return fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + if b == nil { + return fmt.Errorf("%w BackTest", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + for i := range r.tasks { + if r.tasks[i].Equal(b) { + return fmt.Errorf("%w %s %s", errTaskAlreadyMonitored, b.MetaData.ID, b.MetaData.Strategy) + } + } + + err := b.SetupMetaData() + if err != nil { + return err + } + r.tasks = append(r.tasks, b) + return nil +} + +// List details all strategy tasks +func (r *TaskManager) List() ([]*TaskSummary, error) { + if r == nil { + return nil, fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + resp := make([]*TaskSummary, len(r.tasks)) + for i := range r.tasks { + sum, err := r.tasks[i].GenerateSummary() + if err != nil { + return nil, err + } + resp[i] = sum + } + return resp, nil +} + +// GetSummary returns details about a completed strategy task +func (r *TaskManager) GetSummary(id uuid.UUID) (*TaskSummary, error) { + if r == nil { + return nil, fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + for i := range r.tasks { + if !r.tasks[i].MatchesID(id) { + continue + } + return r.tasks[i].GenerateSummary() + } + return nil, fmt.Errorf("%s %w", id, errTaskNotFound) +} + +// StopTask stops a strategy task if enabled, this will run CloseAllPositions +func (r *TaskManager) StopTask(id uuid.UUID) error { + if r == nil { + return fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + for i := range r.tasks { + switch { + case !r.tasks[i].MatchesID(id): + continue + case r.tasks[i].IsRunning(): + return r.tasks[i].Stop() + case r.tasks[i].HasRan(): + return fmt.Errorf("%w %v", errAlreadyRan, id) + default: + return fmt.Errorf("%w %v", errTaskHasNotRan, id) + } + } + return fmt.Errorf("%s %w", id, errTaskNotFound) +} + +// StopAllTasks stops all running strategies +func (r *TaskManager) StopAllTasks() ([]*TaskSummary, error) { + if r == nil { + return nil, fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + resp := make([]*TaskSummary, 0, len(r.tasks)) + for i := range r.tasks { + if !r.tasks[i].IsRunning() { + continue + } + err := r.tasks[i].Stop() + if err != nil { + return nil, err + } + sum, err := r.tasks[i].GenerateSummary() + if err != nil { + return nil, err + } + resp = append(resp, sum) + } + return resp, nil +} + +// StartTask executes a strategy if found +func (r *TaskManager) StartTask(id uuid.UUID) error { + if r == nil { + return fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + for i := range r.tasks { + switch { + case !r.tasks[i].MatchesID(id): + continue + case r.tasks[i].IsRunning(): + return fmt.Errorf("%w %v", errTaskIsRunning, id) + case r.tasks[i].HasRan(): + return fmt.Errorf("%w %v", errAlreadyRan, id) + default: + return r.tasks[i].ExecuteStrategy(false) + } + } + return fmt.Errorf("%s %w", id, errTaskNotFound) +} + +// StartAllTasks executes all strategies +func (r *TaskManager) StartAllTasks() ([]uuid.UUID, error) { + if r == nil { + return nil, fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + executedRuns := make([]uuid.UUID, 0, len(r.tasks)) + for i := range r.tasks { + if r.tasks[i].HasRan() { + continue + } + executedRuns = append(executedRuns, r.tasks[i].MetaData.ID) + err := r.tasks[i].ExecuteStrategy(false) + if err != nil { + return nil, err + } + } + + return executedRuns, nil +} + +// ClearTask removes a run from memory, but only if it is not running +func (r *TaskManager) ClearTask(id uuid.UUID) error { + if r == nil { + return fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + for i := range r.tasks { + if !r.tasks[i].MatchesID(id) { + continue + } + if r.tasks[i].IsRunning() { + return fmt.Errorf("%w %v, currently running. Stop it first", errCannotClear, r.tasks[i].MetaData.ID) + } + r.tasks = append(r.tasks[:i], r.tasks[i+1:]...) + return nil + } + return fmt.Errorf("%s %w", id, errTaskNotFound) +} + +// ClearAllTasks removes all tasks from memory, but only if they are not running +func (r *TaskManager) ClearAllTasks() (clearedRuns, remainingRuns []*TaskSummary, err error) { + if r == nil { + return nil, nil, fmt.Errorf("%w TaskManager", gctcommon.ErrNilPointer) + } + r.m.Lock() + defer r.m.Unlock() + for i := 0; i < len(r.tasks); i++ { + var run *TaskSummary + run, err = r.tasks[i].GenerateSummary() + if err != nil { + return nil, nil, err + } + if r.tasks[i].IsRunning() { + remainingRuns = append(remainingRuns, run) + } else { + clearedRuns = append(clearedRuns, run) + r.tasks = append(r.tasks[:i], r.tasks[i+1:]...) + i-- + } + } + return clearedRuns, remainingRuns, nil +} diff --git a/backtester/engine/runmanager_test.go b/backtester/engine/taskmanager_test.go similarity index 71% rename from backtester/engine/runmanager_test.go rename to backtester/engine/taskmanager_test.go index 44ebe33f..0d2c9d5f 100644 --- a/backtester/engine/runmanager_test.go +++ b/backtester/engine/taskmanager_test.go @@ -9,48 +9,48 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/binancecashandcarry" gctcommon "github.com/thrasher-corp/gocryptotrader/common" ) func TestSetupRunManager(t *testing.T) { t.Parallel() - rm := SetupRunManager() + rm := NewTaskManager() if rm == nil { - t.Errorf("received '%v' expected '%v'", rm, "&RunManager{}") + t.Errorf("received '%v' expected '%v'", rm, "&TaskManager{}") } } func TestAddRun(t *testing.T) { t.Parallel() - rm := SetupRunManager() - err := rm.AddRun(nil) + rm := NewTaskManager() + err := rm.AddTask(nil) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } bt := &BackTest{} - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if bt.MetaData.ID.IsNil() { t.Errorf("received '%v' expected '%v'", bt.MetaData.ID, "a random ID") } - if len(rm.runs) != 1 { - t.Errorf("received '%v' expected '%v'", len(rm.runs), 1) + if len(rm.tasks) != 1 { + t.Errorf("received '%v' expected '%v'", len(rm.tasks), 1) } - err = rm.AddRun(bt) - if !errors.Is(err, errRunAlreadyMonitored) { - t.Errorf("received '%v' expected '%v'", err, errRunAlreadyMonitored) + err = rm.AddTask(bt) + if !errors.Is(err, errTaskAlreadyMonitored) { + t.Errorf("received '%v' expected '%v'", err, errTaskAlreadyMonitored) } - if len(rm.runs) != 1 { - t.Errorf("received '%v' expected '%v'", len(rm.runs), 1) + if len(rm.tasks) != 1 { + t.Errorf("received '%v' expected '%v'", len(rm.tasks), 1) } rm = nil - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } @@ -58,21 +58,21 @@ func TestAddRun(t *testing.T) { func TestGetSummary(t *testing.T) { t.Parallel() - rm := SetupRunManager() + rm := NewTaskManager() id, err := uuid.NewV4() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } _, err = rm.GetSummary(id) - if !errors.Is(err, errRunNotFound) { - t.Errorf("received '%v' expected '%v'", err, errRunNotFound) + if !errors.Is(err, errTaskNotFound) { + t.Errorf("received '%v' expected '%v'", err, errTaskNotFound) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, Statistic: &statistics.Statistic{}, } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -94,7 +94,7 @@ func TestGetSummary(t *testing.T) { func TestList(t *testing.T) { t.Parallel() - rm := SetupRunManager() + rm := NewTaskManager() list, err := rm.List() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) @@ -104,10 +104,10 @@ func TestList(t *testing.T) { } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, Statistic: &statistics.Statistic{}, } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -129,7 +129,7 @@ func TestList(t *testing.T) { func TestStopRun(t *testing.T) { t.Parallel() - rm := SetupRunManager() + rm := NewTaskManager() list, err := rm.List() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) @@ -142,38 +142,42 @@ func TestStopRun(t *testing.T) { if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - err = rm.StopRun(id) - if !errors.Is(err, errRunNotFound) { - t.Errorf("received '%v' expected '%v'", err, errRunNotFound) + err = rm.StopTask(id) + if !errors.Is(err, errTaskNotFound) { + t.Errorf("received '%v' expected '%v'", err, errTaskNotFound) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, - Statistic: &statistics.Statistic{}, + Strategy: &fakeStrat{}, + Statistic: &fakeStats{}, + Reports: &fakeReport{}, shutdown: make(chan struct{}), } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - err = rm.StopRun(bt.MetaData.ID) - if !errors.Is(err, errRunHasNotRan) { - t.Errorf("received '%v' expected '%v'", err, errRunHasNotRan) + + err = rm.StopTask(bt.MetaData.ID) + if !errors.Is(err, errTaskHasNotRan) { + t.Errorf("received '%v' expected '%v'", err, errTaskHasNotRan) } + bt.m.Lock() bt.MetaData.DateStarted = time.Now() - err = rm.StopRun(bt.MetaData.ID) + bt.m.Unlock() + err = rm.StopTask(bt.MetaData.ID) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - err = rm.StopRun(bt.MetaData.ID) + err = rm.StopTask(bt.MetaData.ID) if !errors.Is(err, errAlreadyRan) { t.Errorf("received '%v' expected '%v'", err, errAlreadyRan) } rm = nil - err = rm.StopRun(id) + err = rm.StopTask(id) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } @@ -181,8 +185,8 @@ func TestStopRun(t *testing.T) { func TestStopAllRuns(t *testing.T) { t.Parallel() - rm := SetupRunManager() - stoppedRuns, err := rm.StopAllRuns() + rm := NewTaskManager() + stoppedRuns, err := rm.StopAllTasks() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -191,16 +195,19 @@ func TestStopAllRuns(t *testing.T) { } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, - Statistic: &statistics.Statistic{}, + Strategy: &binancecashandcarry.Strategy{}, + Statistic: &fakeStats{}, + Reports: &fakeReport{}, shutdown: make(chan struct{}), } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } + bt.m.Lock() bt.MetaData.DateStarted = time.Now() - stoppedRuns, err = rm.StopAllRuns() + bt.m.Unlock() + stoppedRuns, err = rm.StopAllTasks() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -209,7 +216,7 @@ func TestStopAllRuns(t *testing.T) { } rm = nil - _, err = rm.StopAllRuns() + _, err = rm.StopAllTasks() if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } @@ -217,7 +224,7 @@ func TestStopAllRuns(t *testing.T) { func TestStartRun(t *testing.T) { t.Parallel() - rm := SetupRunManager() + rm := NewTaskManager() list, err := rm.List() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) @@ -230,42 +237,44 @@ func TestStartRun(t *testing.T) { if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - err = rm.StartRun(id) - if !errors.Is(err, errRunNotFound) { - t.Errorf("received '%v' expected '%v'", err, errRunNotFound) + err = rm.StartTask(id) + if !errors.Is(err, errTaskNotFound) { + t.Errorf("received '%v' expected '%v'", err, errTaskNotFound) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - err = rm.StartRun(bt.MetaData.ID) + err = rm.StartTask(bt.MetaData.ID) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - err = rm.StartRun(bt.MetaData.ID) - if !errors.Is(err, errRunIsRunning) { - t.Errorf("received '%v' expected '%v'", err, errRunIsRunning) + err = rm.StartTask(bt.MetaData.ID) + if !errors.Is(err, errTaskIsRunning) { + t.Errorf("received '%v' expected '%v'", err, errTaskIsRunning) } - + bt.m.Lock() bt.MetaData.DateEnded = time.Now() bt.MetaData.Closed = true + bt.shutdown = make(chan struct{}) + bt.m.Unlock() - err = rm.StartRun(bt.MetaData.ID) + err = rm.StartTask(bt.MetaData.ID) if !errors.Is(err, errAlreadyRan) { t.Errorf("received '%v' expected '%v'", err, errAlreadyRan) } rm = nil - err = rm.StartRun(id) + err = rm.StartTask(id) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } @@ -273,8 +282,8 @@ func TestStartRun(t *testing.T) { func TestStartAllRuns(t *testing.T) { t.Parallel() - rm := SetupRunManager() - startedRuns, err := rm.StartAllRuns() + rm := NewTaskManager() + startedRuns, err := rm.StartAllTasks() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -283,17 +292,17 @@ func TestStartAllRuns(t *testing.T) { } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - startedRuns, err = rm.StartAllRuns() + startedRuns, err = rm.StartAllTasks() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -302,7 +311,7 @@ func TestStartAllRuns(t *testing.T) { } rm = nil - _, err = rm.StartAllRuns() + _, err = rm.StartAllTasks() if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } @@ -310,37 +319,41 @@ func TestStartAllRuns(t *testing.T) { func TestClearRun(t *testing.T) { t.Parallel() - rm := SetupRunManager() + rm := NewTaskManager() id, err := uuid.NewV4() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - err = rm.ClearRun(id) - if !errors.Is(err, errRunNotFound) { - t.Errorf("received '%v' expected '%v'", err, errRunNotFound) + err = rm.ClearTask(id) + if !errors.Is(err, errTaskNotFound) { + t.Errorf("received '%v' expected '%v'", err, errTaskNotFound) } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } + bt.m.Lock() bt.MetaData.DateStarted = time.Now() - err = rm.ClearRun(bt.MetaData.ID) + bt.m.Unlock() + err = rm.ClearTask(bt.MetaData.ID) if !errors.Is(err, errCannotClear) { t.Errorf("received '%v' expected '%v'", err, errCannotClear) } + bt.m.Lock() bt.MetaData.DateStarted = time.Time{} - err = rm.ClearRun(bt.MetaData.ID) + bt.m.Unlock() + err = rm.ClearTask(bt.MetaData.ID) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -353,7 +366,7 @@ func TestClearRun(t *testing.T) { } rm = nil - err = rm.ClearRun(id) + err = rm.ClearTask(id) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } @@ -361,9 +374,9 @@ func TestClearRun(t *testing.T) { func TestClearAllRuns(t *testing.T) { t.Parallel() - rm := SetupRunManager() + rm := NewTaskManager() - clearedRuns, remainingRuns, err := rm.ClearAllRuns() + clearedRuns, remainingRuns, err := rm.ClearAllTasks() if len(clearedRuns) != 0 { t.Errorf("received '%v' expected '%v'", len(clearedRuns), 0) } @@ -375,19 +388,21 @@ func TestClearAllRuns(t *testing.T) { } bt := &BackTest{ - Strategy: &ftxcashandcarry.Strategy{}, + Strategy: &binancecashandcarry.Strategy{}, EventQueue: &eventholder.Holder{}, - Datas: &data.HandlerPerCurrency{}, + DataHolder: &data.HandlerHolder{}, Statistic: &statistics.Statistic{}, shutdown: make(chan struct{}), } - err = rm.AddRun(bt) + err = rm.AddTask(bt) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } + bt.m.Lock() bt.MetaData.DateStarted = time.Now() - clearedRuns, remainingRuns, err = rm.ClearAllRuns() + bt.m.Unlock() + clearedRuns, remainingRuns, err = rm.ClearAllTasks() if len(clearedRuns) != 0 { t.Errorf("received '%v' expected '%v'", len(clearedRuns), 0) } @@ -398,8 +413,10 @@ func TestClearAllRuns(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, nil) } + bt.m.Lock() bt.MetaData.DateStarted = time.Time{} - clearedRuns, remainingRuns, err = rm.ClearAllRuns() + bt.m.Unlock() + clearedRuns, remainingRuns, err = rm.ClearAllTasks() if len(clearedRuns) != 1 { t.Errorf("received '%v' expected '%v'", len(clearedRuns), 1) } @@ -418,7 +435,7 @@ func TestClearAllRuns(t *testing.T) { } rm = nil - _, _, err = rm.ClearAllRuns() + _, _, err = rm.ClearAllTasks() if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } diff --git a/backtester/eventhandlers/eventholder/eventholder.go b/backtester/eventhandlers/eventholder/eventholder.go index b903eaf4..d6c4c383 100644 --- a/backtester/eventhandlers/eventholder/eventholder.go +++ b/backtester/eventhandlers/eventholder/eventholder.go @@ -2,26 +2,31 @@ package eventholder import ( "github.com/thrasher-corp/gocryptotrader/backtester/common" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" ) // Reset returns struct to defaults -func (e *Holder) Reset() { - e.Queue = nil +func (h *Holder) Reset() error { + if h == nil { + return gctcommon.ErrNilPointer + } + h.Queue = nil + return nil } // AppendEvent adds and event to the queue -func (e *Holder) AppendEvent(i common.EventHandler) { - e.Queue = append(e.Queue, i) +func (h *Holder) AppendEvent(i common.Event) { + h.Queue = append(h.Queue, i) } // NextEvent removes the current event and returns the next event in the queue -func (e *Holder) NextEvent() (i common.EventHandler) { - if len(e.Queue) == 0 { +func (h *Holder) NextEvent() (i common.Event) { + if len(h.Queue) == 0 { return nil } - i = e.Queue[0] - e.Queue = e.Queue[1:] + i = h.Queue[0] + h.Queue = h.Queue[1:] return i } diff --git a/backtester/eventhandlers/eventholder/eventholder_test.go b/backtester/eventhandlers/eventholder/eventholder_test.go index 912b5c86..0759b096 100644 --- a/backtester/eventhandlers/eventholder/eventholder_test.go +++ b/backtester/eventhandlers/eventholder/eventholder_test.go @@ -1,24 +1,35 @@ package eventholder import ( + "errors" "testing" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" ) func TestReset(t *testing.T) { t.Parallel() - e := Holder{Queue: []common.EventHandler{}} - e.Reset() + e := &Holder{Queue: []common.Event{}} + err := e.Reset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if e.Queue != nil { t.Error("expected nil") } + + e = nil + err = e.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } } func TestAppendEvent(t *testing.T) { t.Parallel() - e := Holder{Queue: []common.EventHandler{}} + e := Holder{Queue: []common.Event{}} e.AppendEvent(&order.Order{}) if len(e.Queue) != 1 { t.Error("expected 1") @@ -27,12 +38,12 @@ func TestAppendEvent(t *testing.T) { func TestNextEvent(t *testing.T) { t.Parallel() - e := Holder{Queue: []common.EventHandler{}} + e := Holder{Queue: []common.Event{}} if ev := e.NextEvent(); ev != nil { t.Error("expected not ok") } - e = Holder{Queue: []common.EventHandler{ + e = Holder{Queue: []common.Event{ &order.Order{}, &order.Order{}, &order.Order{}, diff --git a/backtester/eventhandlers/eventholder/eventholder_types.go b/backtester/eventhandlers/eventholder/eventholder_types.go index 6599464f..6afa1886 100644 --- a/backtester/eventhandlers/eventholder/eventholder_types.go +++ b/backtester/eventhandlers/eventholder/eventholder_types.go @@ -6,12 +6,12 @@ import ( // Holder contains the event queue for backtester processing type Holder struct { - Queue []common.EventHandler + Queue []common.Event } // EventHolder interface details what is expected of an event holder to perform type EventHolder interface { - Reset() - AppendEvent(common.EventHandler) - NextEvent() common.EventHandler + Reset() error + AppendEvent(common.Event) + NextEvent() common.Event } diff --git a/backtester/eventhandlers/exchange/exchange.go b/backtester/eventhandlers/exchange/exchange.go index 952a1a52..a0bb7c90 100644 --- a/backtester/eventhandlers/exchange/exchange.go +++ b/backtester/eventhandlers/exchange/exchange.go @@ -2,9 +2,9 @@ package exchange import ( "context" - "errors" "fmt" "strings" + "time" "github.com/gofrs/uuid" "github.com/shopspring/decimal" @@ -14,24 +14,25 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" - "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" ) // Reset returns the exchange to initial settings -func (e *Exchange) Reset() { - *e = Exchange{} +func (e *Exchange) Reset() error { + if e == nil { + return gctcommon.ErrNilPointer + } + e.CurrencySettings = nil + return nil } -// ErrCannotTransact returns when its an issue to do nothing for an event -var ErrCannotTransact = errors.New("cannot transact") - // ExecuteOrder assesses the portfolio manager's order event and if it passes validation // will send an order to the exchange/fake order manager to be stored and raise a fill event -func (e *Exchange) ExecuteOrder(o order.Event, data data.Handler, orderManager *engine.OrderManager, funds funding.IFundReleaser) (fill.Event, error) { +func (e *Exchange) ExecuteOrder(o order.Event, dh data.Handler, om *engine.OrderManager, funds funding.IFundReleaser) (fill.Event, error) { f := &fill.Fill{ Base: o.GetBase(), Direction: o.GetDirection(), @@ -78,33 +79,18 @@ func (e *Exchange) ExecuteOrder(o order.Event, data data.Handler, orderManager * } return f, nil } - // get current orderbook - var ob *orderbook.Base - ob, err = orderbook.Get(f.Exchange, f.CurrencyPair, f.AssetType) - if err != nil { - return f, err - } - // calculate an estimated slippage rate - price, amount, err = slippage.CalculateSlippageByOrderbook(ob, o.GetDirection(), allocatedFunds, f.ExchangeFee) - if err != nil { - return f, err - } - f.Slippage = price.Sub(f.ClosePrice).Div(f.ClosePrice).Mul(decimal.NewFromInt(100)) } else { slippageRate := slippage.EstimateSlippagePercentage(cs.MinimumSlippageRate, cs.MaximumSlippageRate) - if cs.SkipCandleVolumeFitting || o.GetAssetType().IsFutures() { + if cs.SkipCandleVolumeFitting || o.GetAssetType().IsFutures() || o.GetDirection() == gctorder.ClosePosition { f.VolumeAdjustedPrice = f.ClosePrice amount = f.Amount } else { - highStr := data.StreamHigh() - high := highStr[len(highStr)-1] - - lowStr := data.StreamLow() - low := lowStr[len(lowStr)-1] - - volStr := data.StreamVol() - volume := volStr[len(volStr)-1] - adjustedPrice, adjustedAmount = ensureOrderFitsWithinHLV(price, amount, high, low, volume) + var latest data.Event + latest, err = dh.Latest() + if err != nil { + return nil, err + } + adjustedPrice, adjustedAmount = ensureOrderFitsWithinHLV(price, amount, latest.GetHighPrice(), latest.GetLowPrice(), latest.GetVolume()) if !amount.Equal(adjustedAmount) { f.AppendReasonf("Order size shrunk from %v to %v to fit candle", amount, adjustedAmount) amount = adjustedAmount @@ -115,22 +101,6 @@ func (e *Exchange) ExecuteOrder(o order.Event, data data.Handler, orderManager * f.VolumeAdjustedPrice = price } } - if amount.LessThanOrEqual(decimal.Zero) && f.GetAmount().GreaterThan(decimal.Zero) { - switch f.GetDirection() { - case gctorder.Buy, gctorder.Bid: - f.SetDirection(gctorder.CouldNotBuy) - case gctorder.Sell, gctorder.Ask: - f.SetDirection(gctorder.CouldNotSell) - case gctorder.Short: - f.SetDirection(gctorder.CouldNotShort) - case gctorder.Long: - f.SetDirection(gctorder.CouldNotLong) - default: - f.SetDirection(gctorder.DoNothing) - } - f.AppendReasonf("amount set to 0, %s", errDataMayBeIncorrect) - return f, err - } adjustedPrice, err = applySlippageToPrice(f.GetDirection(), price, slippageRate) if err != nil { return f, err @@ -148,14 +118,14 @@ func (e *Exchange) ExecuteOrder(o order.Event, data data.Handler, orderManager * amount = adjustedAmount } - if cs.CanUseExchangeLimits { + if cs.CanUseExchangeLimits || cs.UseRealOrders { // Conforms the amount to the exchange order defined step amount // reducing it when needed adjustedAmount = cs.Limits.ConformToDecimalAmount(amount) - if !adjustedAmount.Equal(amount) { + if !adjustedAmount.Equal(amount) && !adjustedAmount.IsZero() { f.AppendReasonf("Order size shrunk from %v to %v to remain within exchange step amount limits", - adjustedAmount, - amount) + amount, + adjustedAmount) amount = adjustedAmount } } @@ -165,12 +135,14 @@ func (e *Exchange) ExecuteOrder(o order.Event, data data.Handler, orderManager * } fee = calculateExchangeFee(price, amount, cs.TakerFee) - orderID, err := e.placeOrder(context.TODO(), price, amount, fee, cs.UseRealOrders, cs.CanUseExchangeLimits, f, orderManager) + + orderID, err := e.placeOrder(context.TODO(), price, amount, fee, cs.UseRealOrders, cs.CanUseExchangeLimits, f, om) if err != nil { + f.AppendReasonf("could not place order: %v", err) + setCannotPurchaseDirection(f) return f, err } - - ords := orderManager.GetOrdersSnapshot(gctorder.UnknownStatus) + ords := om.GetOrdersSnapshot(gctorder.UnknownStatus) for i := range ords { if ords[i].OrderID != orderID { continue @@ -187,16 +159,15 @@ func (e *Exchange) ExecuteOrder(o order.Event, data data.Handler, orderManager * f.Total = f.PurchasePrice.Mul(f.Amount).Add(f.ExchangeFee) } if !o.IsLiquidating() { - err = allocateFundsPostOrder(f, funds, err, o.GetAmount(), allocatedFunds, amount, adjustedPrice, fee) + err = allocateFundsPostOrder(f, funds, err, o.GetAmount(), allocatedFunds, amount, price, fee) if err != nil { return f, err } } - if f.Order == nil { return nil, fmt.Errorf("placed order %v not found in order manager", orderID) } - + f.AppendReason(summarisePosition(f.GetDirection(), f.Amount, f.Amount.Mul(f.PurchasePrice), f.ExchangeFee, f.Order.Pair, f.UnderlyingPair)) return f, nil } @@ -205,7 +176,7 @@ func allocateFundsPostOrder(f *fill.Fill, funds funding.IFundReleaser, orderErro return fmt.Errorf("%w: fill event", common.ErrNilEvent) } if funds == nil { - return fmt.Errorf("%w: funding", common.ErrNilArguments) + return fmt.Errorf("%w: funding", gctcommon.ErrNilPointer) } switch f.AssetType { @@ -250,7 +221,6 @@ func allocateFundsPostOrder(f *fill.Fill, funds funding.IFundReleaser, orderErro default: return fmt.Errorf("%w asset type %v", common.ErrInvalidDataType, f.GetDirection()) } - f.AppendReason(summarisePosition(f.GetDirection(), f.Amount, f.Amount.Mul(f.PurchasePrice), f.ExchangeFee, f.Order.Pair, currency.EMPTYPAIR)) case asset.Futures: cr, err := funds.CollateralReleaser() if err != nil { @@ -261,17 +231,9 @@ func allocateFundsPostOrder(f *fill.Fill, funds funding.IFundReleaser, orderErro if err != nil { return err } - switch f.GetDirection() { - case gctorder.Short: - f.SetDirection(gctorder.CouldNotShort) - case gctorder.Long: - f.SetDirection(gctorder.CouldNotLong) - default: - return fmt.Errorf("%w asset type %v", common.ErrInvalidDataType, f.GetDirection()) - } + setCannotPurchaseDirection(f) return orderError } - f.AppendReason(summarisePosition(f.GetDirection(), f.Amount, f.Amount.Mul(f.PurchasePrice), f.ExchangeFee, f.Order.Pair, f.UnderlyingPair)) default: return fmt.Errorf("%w asset type %v", common.ErrInvalidDataType, f.AssetType) } @@ -298,6 +260,19 @@ func summarisePosition(direction gctorder.Side, orderAmount, orderTotal, orderFe ) } +func setCannotPurchaseDirection(f fill.Event) { + switch f.GetDirection() { + case gctorder.Buy, gctorder.Bid: + f.SetDirection(gctorder.CouldNotBuy) + case gctorder.Sell, gctorder.Ask: + f.SetDirection(gctorder.CouldNotSell) + case gctorder.Long: + f.SetDirection(gctorder.CouldNotLong) + case gctorder.Short: + f.SetDirection(gctorder.CouldNotShort) + } +} + // verifyOrderWithinLimits conforms the amount to fall into the minimum size and maximum size limit after reduced func verifyOrderWithinLimits(f fill.Event, amount decimal.Decimal, cs *Settings) error { if f == nil { @@ -310,18 +285,12 @@ func verifyOrderWithinLimits(f fill.Event, amount decimal.Decimal, cs *Settings) var minMax MinMax var direction gctorder.Side switch f.GetDirection() { - case gctorder.Buy, gctorder.Bid: + case gctorder.Buy, gctorder.Bid, gctorder.Long: minMax = cs.BuySide direction = gctorder.CouldNotBuy - case gctorder.Sell, gctorder.Ask: - minMax = cs.SellSide + case gctorder.Sell, gctorder.Ask, gctorder.Short: direction = gctorder.CouldNotSell - case gctorder.Long: - minMax = cs.BuySide - direction = gctorder.CouldNotLong - case gctorder.Short: minMax = cs.SellSide - direction = gctorder.CouldNotShort case gctorder.ClosePosition: return nil default: @@ -378,13 +347,15 @@ func (e *Exchange) placeOrder(ctx context.Context, price, amount, fee decimal.De } submit := &gctorder.Submit{ - Price: price.InexactFloat64(), - Amount: amount.InexactFloat64(), - Exchange: f.GetExchange(), - Side: f.GetDirection(), - AssetType: f.GetAssetType(), - Pair: f.Pair(), - Type: gctorder.Market, + Price: price.InexactFloat64(), + Amount: amount.InexactFloat64(), + Exchange: f.GetExchange(), + Side: f.GetDirection(), + AssetType: f.GetAssetType(), + Pair: f.Pair(), + Type: gctorder.Market, + RetrieveFees: true, + RetrieveFeeDelay: time.Millisecond * 500, } var resp *engine.OrderSubmitResponse diff --git a/backtester/eventhandlers/exchange/exchange_test.go b/backtester/eventhandlers/exchange/exchange_test.go index 83edfa0b..3dcc87c5 100644 --- a/backtester/eventhandlers/exchange/exchange_test.go +++ b/backtester/eventhandlers/exchange/exchange_test.go @@ -9,57 +9,86 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "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/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/ftx" + "github.com/thrasher-corp/gocryptotrader/exchanges/binance" + "github.com/thrasher-corp/gocryptotrader/exchanges/currencystate" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) -const testExchange = "ftx" +const testExchange = "binance" type fakeFund struct{} +var leet = decimal.NewFromInt(1337) + func (f *fakeFund) GetPairReader() (funding.IPairReader, error) { - return nil, nil + i, err := funding.CreateItem(testExchange, asset.Spot, currency.BTC, leet, leet) + if err != nil { + return nil, err + } + j, err := funding.CreateItem(testExchange, asset.Spot, currency.USDT, leet, leet) + if err != nil { + return nil, err + } + return funding.CreatePair(i, j) } func (f *fakeFund) GetCollateralReader() (funding.ICollateralReader, error) { - return nil, nil + i, err := funding.CreateItem(testExchange, asset.Futures, currency.BTC, leet, leet) + if err != nil { + return nil, err + } + j, err := funding.CreateItem(testExchange, asset.Futures, currency.USDT, leet, leet) + if err != nil { + return nil, err + } + return funding.CreateCollateral(i, j) } func (f *fakeFund) PairReleaser() (funding.IPairReleaser, error) { - btc, err := funding.CreateItem(testExchange, asset.Spot, currency.BTC, decimal.NewFromInt(9999), decimal.NewFromInt(9999)) + btc, err := funding.CreateItem(testExchange, asset.Spot, currency.BTC, leet, leet) if err != nil { return nil, err } - usd, err := funding.CreateItem(testExchange, asset.Spot, currency.USD, decimal.NewFromInt(9999), decimal.NewFromInt(9999)) + usdt, err := funding.CreateItem(testExchange, asset.Spot, currency.USDT, leet, leet) if err != nil { return nil, err } - p, err := funding.CreatePair(btc, usd) + p, err := funding.CreatePair(btc, usdt) if err != nil { return nil, err } - err = p.Reserve(decimal.NewFromInt(1337), gctorder.Buy) + err = p.Reserve(leet, gctorder.Buy) if err != nil { return nil, err } - err = p.Reserve(decimal.NewFromInt(1337), gctorder.Sell) + err = p.Reserve(leet, gctorder.Sell) if err != nil { return nil, err } return p, nil } func (f *fakeFund) CollateralReleaser() (funding.ICollateralReleaser, error) { - return nil, nil + i, err := funding.CreateItem(testExchange, asset.Futures, currency.BTC, decimal.Zero, decimal.Zero) + if err != nil { + return nil, err + } + j, err := funding.CreateItem(testExchange, asset.Futures, currency.USDT, decimal.Zero, decimal.Zero) + if err != nil { + return nil, err + } + return funding.CreateCollateral(i, j) } func (f *fakeFund) IncreaseAvailable(decimal.Decimal, gctorder.Side) {} @@ -69,12 +98,23 @@ func (f *fakeFund) Release(decimal.Decimal, decimal.Decimal, gctorder.Side) erro func TestReset(t *testing.T) { t.Parallel() - e := Exchange{ - CurrencySettings: []Settings{}, + e := &Exchange{ + CurrencySettings: []Settings{ + {}, + }, } - e.Reset() - if e.CurrencySettings != nil { - t.Error("expected nil") + err := e.Reset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if len(e.CurrencySettings) > 0 { + t.Error("expected no entries") + } + + e = nil + err = e.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } } @@ -85,7 +125,7 @@ func TestSetCurrency(t *testing.T) { if len(e.CurrencySettings) != 0 { t.Error("expected 0") } - f := &ftx.FTX{} + f := &binance.Binance{} f.Name = testExchange cs := &Settings{ Exchange: f, @@ -95,8 +135,8 @@ func TestSetCurrency(t *testing.T) { } e.SetExchangeAssetCurrencySettings(asset.Spot, currency.NewPair(currency.BTC, currency.USDT), cs) result, err := e.GetCurrencySettings(testExchange, asset.Spot, currency.NewPair(currency.BTC, currency.USDT)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !result.UseRealOrders { t.Error("expected true") @@ -148,23 +188,17 @@ func TestPlaceOrder(t *testing.T) { t.Fatal(err) } exch.SetDefaults() - cfg, err := exch.GetDefaultConfig() - if err != nil { - t.Fatal(err) - } - err = exch.Setup(cfg) - if err != nil { - t.Fatal(err) - } + exchB := exch.GetBase() + exchB.States = currencystate.NewCurrencyStates() em.Add(exch) bot.ExchangeManager = em bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false, false, 0) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) } err = bot.OrderManager.Start() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } e := Exchange{} _, err = e.placeOrder(context.Background(), decimal.NewFromInt(1), decimal.NewFromInt(1), decimal.Zero, false, true, nil, nil) @@ -188,13 +222,13 @@ func TestPlaceOrder(t *testing.T) { f.AssetType = asset.Spot f.Direction = gctorder.Buy _, err = e.placeOrder(context.Background(), decimal.NewFromInt(1), decimal.NewFromInt(1), decimal.Zero, false, true, f, bot.OrderManager) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } _, err = e.placeOrder(context.Background(), decimal.NewFromInt(1), decimal.NewFromInt(1), decimal.Zero, true, true, f, bot.OrderManager) - if !errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { - t.Errorf("received: %v but expected: %v", err, exchange.ErrAuthenticationSupportNotEnabled) + if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { + t.Errorf("received: %v but expected: %v", err, exchange.ErrCredentialsAreEmpty) } } @@ -208,23 +242,17 @@ func TestExecuteOrder(t *testing.T) { t.Fatal(err) } exch.SetDefaults() - cfg, err := exch.GetDefaultConfig() - if err != nil { - t.Fatal(err) - } - err = exch.Setup(cfg) - if err != nil { - t.Fatal(err) - } + exchB := exch.GetBase() + exchB.States = currencystate.NewCurrencyStates() em.Add(exch) bot.ExchangeManager = em bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false, false, 0) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = bot.OrderManager.Start() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } p := currency.NewPair(currency.BTC, currency.USDT) @@ -233,7 +261,7 @@ func TestExecuteOrder(t *testing.T) { if err != nil { t.Fatal(err) } - f := &ftx.FTX{} + f := &binance.Binance{} f.Name = testExchange cs := Settings{ Exchange: f, @@ -267,21 +295,25 @@ func TestExecuteOrder(t *testing.T) { Interval: 0, Candles: []gctkline.Candle{ { - Close: 1, - High: 1, - Low: 1, - Volume: 1, + Close: 1, + High: 1, + Low: 1, + Time: time.Now(), }, }, } d := &kline.DataFromKline{ + Base: &data.Base{}, Item: item, } err = d.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - d.Next() _, err = e.ExecuteOrder(o, d, bot.OrderManager, &fakeFund{}) if !errors.Is(err, errNoCurrencySettingsFound) { t.Error(err) @@ -292,8 +324,31 @@ func TestExecuteOrder(t *testing.T) { o.Direction = gctorder.Sell e.CurrencySettings = []Settings{cs} _, err = e.ExecuteOrder(o, d, bot.OrderManager, &fakeFund{}) - if !errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { - t.Errorf("received: %v but expected: %v", err, exchange.ErrAuthenticationSupportNotEnabled) + if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { + t.Errorf("received: %v but expected: %v", err, exchange.ErrCredentialsAreEmpty) + } + + o.LiquidatingPosition = true + _, err = e.ExecuteOrder(o, d, bot.OrderManager, &fakeFund{}) + if !errors.Is(err, nil) { + t.Errorf("received: %v but expected: %v", err, nil) + } + + o.AssetType = asset.Futures + e.CurrencySettings[0].Asset = asset.Futures + _, err = e.ExecuteOrder(o, d, bot.OrderManager, &fakeFund{}) + if !errors.Is(err, nil) { + t.Errorf("received: %v but expected: %v", err, nil) + } + + o.LiquidatingPosition = false + o.Amount = decimal.Zero + o.AssetType = asset.Spot + e.CurrencySettings[0].Asset = asset.Spot + e.CurrencySettings[0].UseRealOrders = false + _, err = e.ExecuteOrder(o, d, bot.OrderManager, &fakeFund{}) + if !errors.Is(err, gctorder.ErrAmountIsInvalid) { + t.Errorf("received: %v but expected: %v", err, gctorder.ErrAmountIsInvalid) } } @@ -307,24 +362,17 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) { t.Fatal(err) } exch.SetDefaults() - cfg, err := exch.GetDefaultConfig() - if err != nil { - t.Fatal(err) - } - err = exch.Setup(cfg) - if err != nil { - t.Fatal(err) - } - + exchB := exch.GetBase() + exchB.States = currencystate.NewCurrencyStates() em.Add(exch) bot.ExchangeManager = em bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false, false, 0) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = bot.OrderManager.Start() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } p := currency.NewPair(currency.BTC, currency.USDT) a := asset.Spot @@ -342,7 +390,7 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) { if err != nil { t.Fatal(err) } - f := &ftx.FTX{} + f := &binance.Binance{} f.Name = testExchange cs := Settings{ Exchange: f, @@ -378,26 +426,31 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) { } d := &kline.DataFromKline{ + Base: &data.Base{}, Item: gctkline.Item{ - Exchange: "", - Pair: currency.EMPTYPAIR, - Asset: asset.Empty, - Interval: 0, + Exchange: testExchange, + Pair: p, + Asset: asset.Spot, + Interval: gctkline.FifteenMin, Candles: []gctkline.Candle{ { Close: 1, High: 1, Low: 1, Volume: 1, + Time: time.Now(), }, }, }, } err = d.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - d.Next() _, err = e.ExecuteOrder(o, d, bot.OrderManager, &fakeFund{}) if !errors.Is(err, errExceededPortfolioLimit) { t.Errorf("received %v expected %v", err, errExceededPortfolioLimit) @@ -465,8 +518,8 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) { e.CurrencySettings = []Settings{cs} _, err = e.ExecuteOrder(o, d, bot.OrderManager, &fakeFund{}) - if !errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { - t.Errorf("received: %v but expected: %v", err, exchange.ErrAuthenticationSupportNotEnabled) + if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { + t.Errorf("received: %v but expected: %v", err, exchange.ErrCredentialsAreEmpty) } } @@ -587,7 +640,7 @@ func TestAllocateFundsPostOrder(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, expectedError) } - expectedError = common.ErrNilArguments + expectedError = gctcommon.ErrNilPointer f := &fill.Fill{ Base: &event.Base{ AssetType: asset.Spot, @@ -605,7 +658,7 @@ func TestAllocateFundsPostOrder(t *testing.T) { if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } - item2, err := funding.CreateItem(testExchange, asset.Spot, currency.USD, decimal.NewFromInt(1337), decimal.Zero) + item2, err := funding.CreateItem(testExchange, asset.Spot, currency.USDT, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } @@ -646,7 +699,7 @@ func TestAllocateFundsPostOrder(t *testing.T) { if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } - item4, err := funding.CreateItem(testExchange, asset.Futures, currency.USD, decimal.NewFromInt(1337), decimal.Zero) + item4, err := funding.CreateItem(testExchange, asset.Futures, currency.USDT, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } diff --git a/backtester/eventhandlers/exchange/exchange_types.go b/backtester/eventhandlers/exchange/exchange_types.go index f1d1be84..76b689fd 100644 --- a/backtester/eventhandlers/exchange/exchange_types.go +++ b/backtester/eventhandlers/exchange/exchange_types.go @@ -16,7 +16,9 @@ import ( ) var ( - errDataMayBeIncorrect = errors.New("data may be incorrect") + // ErrCannotTransact returns when its an issue to do nothing for an event + ErrCannotTransact = errors.New("cannot transact") + errExceededPortfolioLimit = errors.New("exceeded portfolio limit") errNilCurrencySettings = errors.New("received nil currency settings") errInvalidDirection = errors.New("received invalid order direction") @@ -28,7 +30,7 @@ type ExecutionHandler interface { SetExchangeAssetCurrencySettings(asset.Item, currency.Pair, *Settings) GetCurrencySettings(string, asset.Item, currency.Pair) (Settings, error) ExecuteOrder(order.Event, data.Handler, *engine.OrderManager, funding.IFundReleaser) (fill.Event, error) - Reset() + Reset() error } // Exchange contains all the currency settings diff --git a/backtester/eventhandlers/portfolio/compliance/compliance_test.go b/backtester/eventhandlers/portfolio/compliance/compliance_test.go index 8385642e..f352a286 100644 --- a/backtester/eventhandlers/portfolio/compliance/compliance_test.go +++ b/backtester/eventhandlers/portfolio/compliance/compliance_test.go @@ -19,23 +19,19 @@ func TestAddSnapshot(t *testing.T) { } err = m.AddSnapshot(&Snapshot{ - Offset: 0, Timestamp: tt, - Orders: nil, }, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(m.Snapshots) != 1 { t.Error("expected 1") } err = m.AddSnapshot(&Snapshot{ - Offset: 0, Timestamp: tt, - Orders: nil, }, true) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(m.Snapshots) != 1 { t.Error("expected 1") @@ -57,13 +53,13 @@ func TestGetSnapshotAtTime(t *testing.T) { }, }, }, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } var snappySnap Snapshot snappySnap, err = m.GetSnapshotAtTime(tt) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(snappySnap.Orders) == 0 { t.Fatal("expected an order") @@ -90,12 +86,10 @@ func TestGetLatestSnapshot(t *testing.T) { } tt := time.Now() err := m.AddSnapshot(&Snapshot{ - Offset: 0, Timestamp: tt, - Orders: nil, }, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = m.AddSnapshot(&Snapshot{ Offset: 1, diff --git a/backtester/eventhandlers/portfolio/holdings/holdings.go b/backtester/eventhandlers/portfolio/holdings/holdings.go index 95468d78..06defd54 100644 --- a/backtester/eventhandlers/portfolio/holdings/holdings.go +++ b/backtester/eventhandlers/portfolio/holdings/holdings.go @@ -7,22 +7,24 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // Create makes a Holding struct to track total values of strategy holdings over the course of a backtesting run -func Create(ev ClosePriceReader, fundReader funding.IFundReader) (Holding, error) { +func Create(ev ClosePriceReader, fundReader funding.IFundReader) (*Holding, error) { if ev == nil { - return Holding{}, common.ErrNilEvent + return nil, common.ErrNilEvent } - - if ev.GetAssetType().IsFutures() { + a := ev.GetAssetType() + switch { + case a.IsFutures(): funds, err := fundReader.GetCollateralReader() if err != nil { - return Holding{}, err + return nil, err } - return Holding{ + return &Holding{ Offset: ev.GetOffset(), Pair: ev.Pair(), Asset: ev.GetAssetType(), @@ -32,16 +34,16 @@ func Create(ev ClosePriceReader, fundReader funding.IFundReader) (Holding, error QuoteSize: funds.InitialFunds(), TotalInitialValue: funds.InitialFunds(), }, nil - } else if ev.GetAssetType() == asset.Spot { + case a == asset.Spot: funds, err := fundReader.GetPairReader() if err != nil { - return Holding{}, err + return nil, err } if funds.QuoteInitialFunds().LessThan(decimal.Zero) { - return Holding{}, ErrInitialFundsZero + return nil, ErrInitialFundsZero } - return Holding{ + return &Holding{ Offset: ev.GetOffset(), Pair: ev.Pair(), Asset: ev.GetAssetType(), @@ -53,8 +55,9 @@ func Create(ev ClosePriceReader, fundReader funding.IFundReader) (Holding, error BaseSize: funds.BaseInitialFunds(), TotalInitialValue: funds.QuoteInitialFunds().Add(funds.BaseInitialFunds().Mul(ev.GetClosePrice())), }, nil + default: + return nil, fmt.Errorf("%v %w", ev.GetAssetType(), asset.ErrNotSupported) } - return Holding{}, fmt.Errorf("%v %w", ev.GetAssetType(), asset.ErrNotSupported) } // Update calculates holding statistics for the events time @@ -65,11 +68,15 @@ func (h *Holding) Update(e fill.Event, f funding.IFundReader) error { } // UpdateValue calculates the holding's value for a data event's time and price -func (h *Holding) UpdateValue(d common.DataEventHandler) { +func (h *Holding) UpdateValue(d common.Event) error { + if d == nil { + return fmt.Errorf("%w event", gctcommon.ErrNilPointer) + } h.Timestamp = d.GetTime() latest := d.GetClosePrice() h.Offset = d.GetOffset() h.scaleValuesToCurrentPrice(latest) + return nil } func (h *Holding) update(e fill.Event, f funding.IFundReader) error { diff --git a/backtester/eventhandlers/portfolio/holdings/holdings_test.go b/backtester/eventhandlers/portfolio/holdings/holdings_test.go index 998bb2d5..97a0b9c4 100644 --- a/backtester/eventhandlers/portfolio/holdings/holdings_test.go +++ b/backtester/eventhandlers/portfolio/holdings/holdings_test.go @@ -11,6 +11,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" @@ -62,15 +63,15 @@ func TestCreate(t *testing.T) { _, err = Create(&fill.Fill{ Base: &event.Base{AssetType: asset.Spot}, }, pair(t)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } _, err = Create(&fill.Fill{ Base: &event.Base{AssetType: asset.Futures}, }, collateral(t)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -79,8 +80,8 @@ func TestUpdate(t *testing.T) { h, err := Create(&fill.Fill{ Base: &event.Base{AssetType: asset.Spot}, }, pair(t)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } t1 := h.Timestamp // nolint:ifshort,nolintlint // false positive and triggers only on Windows err = h.Update(&fill.Fill{ @@ -88,8 +89,8 @@ func TestUpdate(t *testing.T) { Time: time.Now(), }, }, pair(t)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if t1.Equal(h.Timestamp) { t.Errorf("expected '%v' received '%v'", h.Timestamp, t1) @@ -102,14 +103,23 @@ func TestUpdateValue(t *testing.T) { h, err := Create(&fill.Fill{ Base: b, }, pair(t)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } + + err = h.UpdateValue(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) + } + h.BaseSize = decimal.NewFromInt(1) - h.UpdateValue(&kline.Kline{ + err = h.UpdateValue(&kline.Kline{ Base: b, Close: decimal.NewFromInt(1337), }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if !h.BaseValue.Equal(decimal.NewFromInt(1337)) { t.Errorf("expected '%v' received '%v'", h.BaseSize, decimal.NewFromInt(1337)) } @@ -132,8 +142,8 @@ func TestUpdateBuyStats(t *testing.T) { h, err := Create(&fill.Fill{ Base: &event.Base{AssetType: asset.Spot}, }, pair(t)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = h.update(&fill.Fill{ @@ -166,8 +176,8 @@ func TestUpdateBuyStats(t *testing.T) { Fee: 1, }, }, p) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !h.BaseSize.Equal(p.BaseAvailable()) { t.Errorf("expected '%v' received '%v'", 1, h.BaseSize) @@ -221,8 +231,8 @@ func TestUpdateBuyStats(t *testing.T) { Fee: 0.5, }, }, p) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !h.BoughtAmount.Equal(decimal.NewFromFloat(1.5)) { @@ -254,8 +264,8 @@ func TestUpdateSellStats(t *testing.T) { h, err := Create(&fill.Fill{ Base: &event.Base{AssetType: asset.Spot}, }, p) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = h.update(&fill.Fill{ Base: &event.Base{ @@ -286,8 +296,8 @@ func TestUpdateSellStats(t *testing.T) { Fee: 1, }, }, p) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !h.BaseSize.Equal(decimal.NewFromInt(1)) { t.Errorf("expected '%v' received '%v'", 1, h.BaseSize) @@ -344,8 +354,8 @@ func TestUpdateSellStats(t *testing.T) { Fee: 1, }, }, p) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !h.BoughtAmount.Equal(decimal.NewFromInt(1)) { diff --git a/backtester/eventhandlers/portfolio/holdings/holdings_types.go b/backtester/eventhandlers/portfolio/holdings/holdings_types.go index e2da7073..67582aed 100644 --- a/backtester/eventhandlers/portfolio/holdings/holdings_types.go +++ b/backtester/eventhandlers/portfolio/holdings/holdings_types.go @@ -49,6 +49,6 @@ type Holding struct { // ClosePriceReader is used for holdings calculations // without needing to consider event types type ClosePriceReader interface { - common.EventHandler + common.Event GetClosePrice() decimal.Decimal } diff --git a/backtester/eventhandlers/portfolio/portfolio.go b/backtester/eventhandlers/portfolio/portfolio.go index 5ffa26ea..eba246df 100644 --- a/backtester/eventhandlers/portfolio/portfolio.go +++ b/backtester/eventhandlers/portfolio/portfolio.go @@ -8,6 +8,7 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" @@ -16,20 +17,23 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" - "github.com/thrasher-corp/gocryptotrader/log" ) // OnSignal receives the event from the strategy on whether it has signalled to buy, do nothing or sell // on buy/sell, the portfolio manager will size the order and assess the risk of the order // if successful, it will pass on an order.Order to be used by the exchange event handler to place an order based on // the portfolio manager's recommendations -func (p *Portfolio) OnSignal(ev signal.Event, cs *exchange.Settings, funds funding.IFundReserver) (*order.Order, error) { - if ev == nil || cs == nil { - return nil, common.ErrNilArguments +func (p *Portfolio) OnSignal(ev signal.Event, exchangeSettings *exchange.Settings, funds funding.IFundReserver) (*order.Order, error) { + if ev == nil { + return nil, fmt.Errorf("%w signal event", gctcommon.ErrNilPointer) + } + if exchangeSettings == nil { + return nil, fmt.Errorf("%w exchange settings", gctcommon.ErrNilPointer) } if p.sizeManager == nil { return nil, errSizeManagerUnset @@ -40,7 +44,6 @@ func (p *Portfolio) OnSignal(ev signal.Event, cs *exchange.Settings, funds fundi if funds == nil { return nil, funding.ErrFundsNotFound } - o := &order.Order{ Base: ev.GetBase(), Direction: ev.GetDirection(), @@ -52,7 +55,7 @@ func (p *Portfolio) OnSignal(ev signal.Event, cs *exchange.Settings, funds fundi return o, errInvalidDirection } - lookup := p.exchangeAssetPairSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair()] + lookup := p.exchangeAssetPairPortfolioSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair().Base.Item][ev.Pair().Quote.Item] if lookup == nil { return nil, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, @@ -98,11 +101,11 @@ func (p *Portfolio) OnSignal(ev signal.Event, cs *exchange.Settings, funds fundi return nil, errNoHoldings } sizingFunds = positions[len(positions)-1].LatestSize - d := positions[len(positions)-1].OpeningDirection + d := positions[len(positions)-1].LatestDirection switch d { - case gctorder.Short: + case gctorder.Short, gctorder.Sell, gctorder.Ask: side = gctorder.Long - case gctorder.Long: + case gctorder.Long, gctorder.Buy, gctorder.Bid: side = gctorder.Short } } else { @@ -116,7 +119,7 @@ func (p *Portfolio) OnSignal(ev signal.Event, cs *exchange.Settings, funds fundi if sizingFunds.LessThanOrEqual(decimal.Zero) { return cannotPurchase(ev, o) } - sizedOrder, err := p.sizeOrder(ev, cs, o, sizingFunds, funds) + sizedOrder, err := p.sizeOrder(ev, exchangeSettings, o, sizingFunds, funds) if err != nil { return sizedOrder, err } @@ -134,7 +137,7 @@ func cannotPurchase(ev signal.Event, o *order.Order) (*order.Order, error) { return nil, common.ErrNilEvent } if o == nil { - return nil, fmt.Errorf("%w received nil order for %v %v %v", common.ErrNilArguments, ev.GetExchange(), ev.GetAssetType(), ev.Pair()) + return nil, fmt.Errorf("%w received nil order for %v %v %v", gctcommon.ErrNilPointer, ev.GetExchange(), ev.GetAssetType(), ev.Pair()) } o.AppendReason(notEnoughFundsTo + " " + ev.GetDirection().Lower()) switch ev.GetDirection() { @@ -156,7 +159,7 @@ func cannotPurchase(ev signal.Event, o *order.Order) (*order.Order, error) { func (p *Portfolio) evaluateOrder(d common.Directioner, originalOrderSignal, ev *order.Order) (*order.Order, error) { var evaluatedOrder *order.Order - cm, err := p.GetComplianceManager(originalOrderSignal.GetExchange(), originalOrderSignal.GetAssetType(), originalOrderSignal.Pair()) + cm, err := p.getComplianceManager(originalOrderSignal.GetExchange(), originalOrderSignal.GetAssetType(), originalOrderSignal.Pair()) if err != nil { return nil, err } @@ -231,46 +234,34 @@ func (p *Portfolio) OnFill(ev fill.Event, funds funding.IFundReleaser) (fill.Eve if ev == nil { return nil, common.ErrNilEvent } - lookup := p.exchangeAssetPairSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair()] + lookup := p.exchangeAssetPairPortfolioSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair().Base.Item][ev.Pair().Quote.Item] if lookup == nil { return nil, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, ev.GetExchange(), ev.GetAssetType(), ev.Pair()) } - var err error - // Get the holding from the previous iteration, create it if it doesn't yet have a timestamp - h := lookup.GetHoldingsForTime(ev.GetTime().Add(-ev.GetInterval().Duration())) - if !h.Timestamp.IsZero() { - err = h.Update(ev, funds) + h, err := lookup.GetHoldingsForTime(ev.GetTime().Add(-ev.GetInterval().Duration())) + if err != nil { + if !errors.Is(err, errNoHoldings) { + return nil, err + } + h, err = holdings.Create(ev, funds) if err != nil { return nil, err } - } else { - h = lookup.GetLatestHoldings() - if h.Timestamp.IsZero() { - h, err = holdings.Create(ev, funds) - if err != nil { - return nil, err - } - } else { - err = h.Update(ev, funds) - if err != nil { - return nil, err - } - } - } - err = p.setHoldingsForOffset(&h, true) - if errors.Is(err, errNoHoldings) { - err = p.setHoldingsForOffset(&h, false) - } - if err != nil { - log.Error(common.Portfolio, err) } + err = h.Update(ev, funds) + if err != nil { + return nil, err + } + err = p.SetHoldingsForTimestamp(h) + if err != nil { + return nil, err + } err = p.addComplianceSnapshot(ev) if err != nil { - log.Error(common.Portfolio, err) + return nil, err } - return ev, nil } @@ -280,7 +271,7 @@ func (p *Portfolio) addComplianceSnapshot(fillEvent fill.Event) error { if fillEvent == nil { return common.ErrNilEvent } - complianceManager, err := p.GetComplianceManager(fillEvent.GetExchange(), fillEvent.GetAssetType(), fillEvent.Pair()) + complianceManager, err := p.getComplianceManager(fillEvent.GetExchange(), fillEvent.GetAssetType(), fillEvent.Pair()) if err != nil { return err } @@ -306,40 +297,9 @@ func (p *Portfolio) addComplianceSnapshot(fillEvent fill.Event) error { return complianceManager.AddSnapshot(snap, false) } -func (p *Portfolio) setHoldingsForOffset(h *holdings.Holding, overwriteExisting bool) error { - if h.Timestamp.IsZero() { - return errHoldingsNoTimestamp - } - lookup, ok := p.exchangeAssetPairSettings[h.Exchange][h.Asset][h.Pair] - if !ok { - return fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, h.Exchange, h.Asset, h.Pair) - } - - if overwriteExisting && len(lookup.HoldingsSnapshots) == 0 { - return errNoHoldings - } - for i := len(lookup.HoldingsSnapshots) - 1; i >= 0; i-- { - if lookup.HoldingsSnapshots[i].Offset == h.Offset { - if overwriteExisting { - lookup.HoldingsSnapshots[i] = *h - p.exchangeAssetPairSettings[h.Exchange][h.Asset][h.Pair] = lookup - return nil - } - return errHoldingsAlreadySet - } - } - if overwriteExisting { - return fmt.Errorf("%w at %v", errNoHoldings, h.Timestamp) - } - - lookup.HoldingsSnapshots = append(lookup.HoldingsSnapshots, *h) - p.exchangeAssetPairSettings[h.Exchange][h.Asset][h.Pair] = lookup - return nil -} - // GetLatestOrderSnapshotForEvent gets orders related to the event -func (p *Portfolio) GetLatestOrderSnapshotForEvent(e common.EventHandler) (compliance.Snapshot, error) { - eapSettings, ok := p.exchangeAssetPairSettings[e.GetExchange()][e.GetAssetType()][e.Pair()] +func (p *Portfolio) GetLatestOrderSnapshotForEvent(e common.Event) (compliance.Snapshot, error) { + eapSettings, ok := p.exchangeAssetPairPortfolioSettings[e.GetExchange()][e.GetAssetType()][e.Pair().Base.Item][e.Pair().Quote.Item] if !ok { return compliance.Snapshot{}, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, e.GetExchange(), e.GetAssetType(), e.Pair()) } @@ -349,10 +309,12 @@ func (p *Portfolio) GetLatestOrderSnapshotForEvent(e common.EventHandler) (compl // GetLatestOrderSnapshots returns the latest snapshots from all stored pair data func (p *Portfolio) GetLatestOrderSnapshots() ([]compliance.Snapshot, error) { var resp []compliance.Snapshot - for _, exchangeMap := range p.exchangeAssetPairSettings { + for _, exchangeMap := range p.exchangeAssetPairPortfolioSettings { for _, assetMap := range exchangeMap { - for _, pairMap := range assetMap { - resp = append(resp, pairMap.ComplianceManager.GetLatestSnapshot()) + for _, baseMap := range assetMap { + for _, quoteMap := range baseMap { + resp = append(resp, quoteMap.ComplianceManager.GetLatestSnapshot()) + } } } } @@ -362,97 +324,28 @@ func (p *Portfolio) GetLatestOrderSnapshots() ([]compliance.Snapshot, error) { return resp, nil } -// GetComplianceManager returns the order snapshots for a given exchange, asset, pair -func (p *Portfolio) GetComplianceManager(exchangeName string, a asset.Item, cp currency.Pair) (*compliance.Manager, error) { - lookup := p.exchangeAssetPairSettings[exchangeName][a][cp] +// GetLatestComplianceSnapshot returns the latest compliance snapshot for a given exchange, asset, pair +func (p *Portfolio) GetLatestComplianceSnapshot(exchangeName string, a asset.Item, cp currency.Pair) (*compliance.Snapshot, error) { + cm, err := p.getComplianceManager(exchangeName, a, cp) + if err != nil { + return nil, err + } + snap := cm.GetLatestSnapshot() + + return &snap, nil +} + +// getComplianceManager returns the order snapshots for a given exchange, asset, pair +func (p *Portfolio) getComplianceManager(exchangeName string, a asset.Item, cp currency.Pair) (*compliance.Manager, error) { + lookup := p.exchangeAssetPairPortfolioSettings[exchangeName][a][cp.Base.Item][cp.Quote.Item] if lookup == nil { return nil, fmt.Errorf("%w for %v %v %v could not retrieve compliance manager", errNoPortfolioSettings, exchangeName, a, cp) } return &lookup.ComplianceManager, nil } -// UpdateHoldings updates the portfolio holdings for the data event -func (p *Portfolio) UpdateHoldings(e common.DataEventHandler, funds funding.IFundReleaser) error { - if e == nil { - return common.ErrNilEvent - } - if funds == nil { - return funding.ErrFundsNotFound - } - settings, err := p.getSettings(e.GetExchange(), e.GetAssetType(), e.Pair()) - if err != nil { - return fmt.Errorf("%v %v %v %w", e.GetExchange(), e.GetAssetType(), e.Pair(), err) - } - h := settings.GetLatestHoldings() - if h.Timestamp.IsZero() { - h, err = holdings.Create(e, funds) - if err != nil { - return err - } - } - h.UpdateValue(e) - err = p.setHoldingsForOffset(&h, true) - if errors.Is(err, errNoHoldings) { - err = p.setHoldingsForOffset(&h, false) - } - return err -} - -// GetLatestHoldingsForAllCurrencies will return the current holdings for all loaded currencies -// this is useful to assess the position of your entire portfolio in order to help with risk decisions -func (p *Portfolio) GetLatestHoldingsForAllCurrencies() []holdings.Holding { - var resp []holdings.Holding - for _, x := range p.exchangeAssetPairSettings { - for _, y := range x { - for _, z := range y { - holds := z.GetLatestHoldings() - if !holds.Timestamp.IsZero() { - resp = append(resp, holds) - } - } - } - } - return resp -} - -// ViewHoldingAtTimePeriod retrieves a snapshot of holdings at a specific time period, -// returning empty when not found -func (p *Portfolio) ViewHoldingAtTimePeriod(ev common.EventHandler) (*holdings.Holding, error) { - exchangeAssetPairSettings := p.exchangeAssetPairSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair()] - if exchangeAssetPairSettings == nil { - return nil, fmt.Errorf("%w for %v %v %v", errNoHoldings, ev.GetExchange(), ev.GetAssetType(), ev.Pair()) - } - - for i := len(exchangeAssetPairSettings.HoldingsSnapshots) - 1; i >= 0; i-- { - if ev.GetTime().Equal(exchangeAssetPairSettings.HoldingsSnapshots[i].Timestamp) { - return &exchangeAssetPairSettings.HoldingsSnapshots[i], nil - } - } - - return nil, fmt.Errorf("%w for %v %v %v at %v", errNoHoldings, ev.GetExchange(), ev.GetAssetType(), ev.Pair(), ev.GetTime()) -} - -// GetLatestHoldings returns the latest holdings after being sorted by time -func (s *Settings) GetLatestHoldings() holdings.Holding { - if len(s.HoldingsSnapshots) == 0 { - return holdings.Holding{} - } - - return s.HoldingsSnapshots[len(s.HoldingsSnapshots)-1] -} - -// GetHoldingsForTime returns the holdings for a time period, or an empty holding if not found -func (s *Settings) GetHoldingsForTime(t time.Time) holdings.Holding { - for i := len(s.HoldingsSnapshots) - 1; i >= 0; i-- { - if s.HoldingsSnapshots[i].Timestamp.Equal(t) { - return s.HoldingsSnapshots[i] - } - } - return holdings.Holding{} -} - // GetPositions returns all futures positions for an event's exchange, asset, pair -func (p *Portfolio) GetPositions(e common.EventHandler) ([]gctorder.Position, error) { +func (p *Portfolio) GetPositions(e common.Event) ([]gctorder.Position, error) { settings, err := p.getFuturesSettingsFromEvent(e) if err != nil { return nil, err @@ -461,7 +354,7 @@ func (p *Portfolio) GetPositions(e common.EventHandler) ([]gctorder.Position, er } // GetLatestPosition returns all futures positions for an event's exchange, asset, pair -func (p *Portfolio) GetLatestPosition(e common.EventHandler) (*gctorder.Position, error) { +func (p *Portfolio) GetLatestPosition(e common.Event) (*gctorder.Position, error) { settings, err := p.getFuturesSettingsFromEvent(e) if err != nil { return nil, err @@ -475,7 +368,7 @@ func (p *Portfolio) GetLatestPosition(e common.EventHandler) (*gctorder.Position // UpdatePNL will analyse any futures orders that have been placed over the backtesting run // that are not closed and calculate their PNL -func (p *Portfolio) UpdatePNL(e common.EventHandler, closePrice decimal.Decimal) error { +func (p *Portfolio) UpdatePNL(e common.Event, closePrice decimal.Decimal) error { settings, err := p.getFuturesSettingsFromEvent(e) if err != nil { return err @@ -495,7 +388,7 @@ func (p *Portfolio) TrackFuturesOrder(ev fill.Event, fund funding.IFundReleaser) return nil, common.ErrNilEvent } if fund == nil { - return nil, fmt.Errorf("%w missing funding", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing funding", gctcommon.ErrNilPointer) } detail := ev.GetOrder() if detail == nil { @@ -511,7 +404,7 @@ func (p *Portfolio) TrackFuturesOrder(ev fill.Event, fund funding.IFundReleaser) } settings, err := p.getSettings(detail.Exchange, detail.AssetType, detail.Pair) if err != nil { - return nil, fmt.Errorf("%v %v %v %w", detail.Exchange, detail.AssetType, detail.Pair, err) + return nil, fmt.Errorf("%w", err) } err = settings.FuturesTracker.TrackNewOrder(detail) @@ -552,13 +445,13 @@ func (p *Portfolio) TrackFuturesOrder(ev fill.Event, fund funding.IFundReleaser) // GetLatestPNLForEvent takes in an event and returns the latest PNL data // if it exists -func (p *Portfolio) GetLatestPNLForEvent(e common.EventHandler) (*PNLSummary, error) { +func (p *Portfolio) GetLatestPNLForEvent(e common.Event) (*PNLSummary, error) { if e == nil { return nil, common.ErrNilEvent } response := &PNLSummary{ Exchange: e.GetExchange(), - Item: e.GetAssetType(), + Asset: e.GetAssetType(), Pair: e.Pair(), Offset: e.GetOffset(), } @@ -577,15 +470,15 @@ func (p *Portfolio) GetLatestPNLForEvent(e common.EventHandler) (*PNLSummary, er // CheckLiquidationStatus checks funding against position // and liquidates and removes funding if position unable to continue -func (p *Portfolio) CheckLiquidationStatus(ev common.DataEventHandler, collateralReader funding.ICollateralReader, pnl *PNLSummary) error { +func (p *Portfolio) CheckLiquidationStatus(ev data.Event, collateralReader funding.ICollateralReader, pnl *PNLSummary) error { if ev == nil { return common.ErrNilEvent } if collateralReader == nil { - return fmt.Errorf("%w collateral reader missing", common.ErrNilArguments) + return fmt.Errorf("%w collateral reader missing", gctcommon.ErrNilPointer) } if pnl == nil { - return fmt.Errorf("%w pnl summary missing", common.ErrNilArguments) + return fmt.Errorf("%w pnl summary missing", gctcommon.ErrNilPointer) } availableFunds := collateralReader.AvailableFunds() position, err := p.GetLatestPosition(ev) @@ -602,81 +495,87 @@ func (p *Portfolio) CheckLiquidationStatus(ev common.DataEventHandler, collatera } // CreateLiquidationOrdersForExchange creates liquidation orders, for any that exist on the same exchange where a liquidation is occurring -func (p *Portfolio) CreateLiquidationOrdersForExchange(ev common.DataEventHandler, funds funding.IFundingManager) ([]order.Event, error) { +func (p *Portfolio) CreateLiquidationOrdersForExchange(ev data.Event, funds funding.IFundingManager) ([]order.Event, error) { if ev == nil { return nil, common.ErrNilEvent } if funds == nil { - return nil, fmt.Errorf("%w, requires funding manager", common.ErrNilArguments) + return nil, fmt.Errorf("%w, requires funding manager", gctcommon.ErrNilPointer) } var closingOrders []order.Event - assetPairSettings, ok := p.exchangeAssetPairSettings[ev.GetExchange()] + assetPairSettings, ok := p.exchangeAssetPairPortfolioSettings[ev.GetExchange()] if !ok { return nil, config.ErrExchangeNotFound } - for item, pairMap := range assetPairSettings { - for pair, settings := range pairMap { - switch { - case item.IsFutures(): - positions := settings.FuturesTracker.GetPositions() - if len(positions) == 0 { - continue - } - pos := positions[len(positions)-1] - if !pos.LatestSize.IsPositive() { - continue - } - direction := gctorder.Short - if pos.LatestDirection == gctorder.Short { - direction = gctorder.Long - } - closingOrders = append(closingOrders, &order.Order{ - Base: &event.Base{ - Offset: ev.GetOffset(), - Exchange: pos.Exchange, - Time: ev.GetTime(), - Interval: ev.GetInterval(), - CurrencyPair: pos.Pair, - UnderlyingPair: ev.GetUnderlyingPair(), - AssetType: pos.Asset, - Reasons: []string{"LIQUIDATED"}, - }, - Direction: direction, - Status: gctorder.Liquidated, - ClosePrice: ev.GetClosePrice(), - Amount: pos.LatestSize, - AllocatedFunds: pos.LatestSize, - OrderType: gctorder.Market, - LiquidatingPosition: true, - }) - case item == asset.Spot: - allFunds := funds.GetAllFunding() - for i := range allFunds { - if allFunds[i].Asset.IsFutures() { + for item, baseMap := range assetPairSettings { + for b, quoteMap := range baseMap { + for q, settings := range quoteMap { + switch { + case item.IsFutures(): + positions := settings.FuturesTracker.GetPositions() + if len(positions) == 0 { continue } - if allFunds[i].Currency.IsFiatCurrency() || allFunds[i].Currency.IsStableCurrency() { - // close orders for assets - // funding manager will zero for fiat/stable + pos := positions[len(positions)-1] + if !pos.LatestSize.IsPositive() { continue } + direction := gctorder.Short + if pos.LatestDirection == gctorder.Short { + direction = gctorder.Long + } closingOrders = append(closingOrders, &order.Order{ Base: &event.Base{ - Offset: ev.GetOffset(), - Exchange: ev.GetExchange(), - Time: ev.GetTime(), - Interval: ev.GetInterval(), - CurrencyPair: pair, - AssetType: item, - Reasons: []string{"LIQUIDATED"}, + Offset: ev.GetOffset(), + Exchange: pos.Exchange, + Time: ev.GetTime(), + Interval: ev.GetInterval(), + CurrencyPair: pos.Pair, + UnderlyingPair: ev.GetUnderlyingPair(), + AssetType: pos.Asset, + Reasons: []string{"LIQUIDATED"}, }, - Direction: gctorder.Sell, + Direction: direction, Status: gctorder.Liquidated, - Amount: allFunds[i].Available, + ClosePrice: ev.GetClosePrice(), + Amount: pos.LatestSize, + AllocatedFunds: pos.LatestSize, OrderType: gctorder.Market, - AllocatedFunds: allFunds[i].Available, LiquidatingPosition: true, }) + case item == asset.Spot: + allFunds, err := funds.GetAllFunding() + if err != nil { + return nil, err + } + for i := range allFunds { + if allFunds[i].Asset.IsFutures() { + continue + } + if allFunds[i].Currency.IsFiatCurrency() || allFunds[i].Currency.IsStableCurrency() { + // close orders for assets + // funding manager will zero for fiat/stable + continue + } + cp := currency.NewPair(b.Currency(), q.Currency()) + closingOrders = append(closingOrders, &order.Order{ + Base: &event.Base{ + Offset: ev.GetOffset(), + Exchange: ev.GetExchange(), + Time: ev.GetTime(), + Interval: ev.GetInterval(), + CurrencyPair: cp, + AssetType: item, + Reasons: []string{"LIQUIDATED"}, + }, + Direction: gctorder.Sell, + Status: gctorder.Liquidated, + Amount: allFunds[i].Available, + OrderType: gctorder.Market, + AllocatedFunds: allFunds[i].Available, + LiquidatingPosition: true, + }) + } } } } @@ -685,7 +584,7 @@ func (p *Portfolio) CreateLiquidationOrdersForExchange(ev common.DataEventHandle return closingOrders, nil } -func (p *Portfolio) getFuturesSettingsFromEvent(e common.EventHandler) (*Settings, error) { +func (p *Portfolio) getFuturesSettingsFromEvent(e common.Event) (*Settings, error) { if e == nil { return nil, common.ErrNilEvent } @@ -705,56 +604,159 @@ func (p *Portfolio) getFuturesSettingsFromEvent(e common.EventHandler) (*Setting } func (p *Portfolio) getSettings(exch string, item asset.Item, pair currency.Pair) (*Settings, error) { - exchMap, ok := p.exchangeAssetPairSettings[strings.ToLower(exch)] + exch = strings.ToLower(exch) + settings, ok := p.exchangeAssetPairPortfolioSettings[exch][item][pair.Base.Item][pair.Quote.Item] if !ok { - return nil, errExchangeUnset + return nil, fmt.Errorf("%w for %v %v %v", errNoPortfolioSettings, exch, item, pair) } - itemMap, ok := exchMap[item] - if !ok { - return nil, errAssetUnset - } - pairSettings, ok := itemMap[pair] - if !ok { - return nil, errCurrencyPairUnset - } - - return pairSettings, nil + return settings, nil } -// GetLatestPNLs returns all PNL details in one array -func (p *Portfolio) GetLatestPNLs() []PNLSummary { - var result []PNLSummary - for exch, assetPairSettings := range p.exchangeAssetPairSettings { - for ai, pairSettings := range assetPairSettings { - if !ai.IsFutures() { - continue - } - for cp, settings := range pairSettings { - if settings == nil { - continue - } - if settings.FuturesTracker == nil { - continue - } - summary := PNLSummary{ - Exchange: exch, - Item: ai, - Pair: cp, - } - positions := settings.FuturesTracker.GetPositions() - if len(positions) > 0 { - pnlHistory := positions[len(positions)-1].PNLHistory - if len(pnlHistory) > 0 { - summary.Result = pnlHistory[len(pnlHistory)-1] - summary.CollateralCurrency = positions[0].CollateralCurrency - } - } +// SetHoldingsForTimestamp stores a holding snapshot for the holding's timestamp +func (p *Portfolio) SetHoldingsForTimestamp(h *holdings.Holding) error { + if h.Timestamp.IsZero() { + return errHoldingsNoTimestamp + } + lookup, err := p.getSettings(h.Exchange, h.Asset, h.Pair) + if err != nil { + return err + } + lookup.HoldingsSnapshots[h.Timestamp.UnixNano()] = h + return nil +} - result = append(result, summary) +// UpdateHoldings updates the portfolio holdings for the data event +func (p *Portfolio) UpdateHoldings(e data.Event, funds funding.IFundReleaser) error { + if e == nil { + return common.ErrNilEvent + } + if funds == nil { + return funding.ErrFundsNotFound + } + settings, err := p.getSettings(e.GetExchange(), e.GetAssetType(), e.Pair()) + if err != nil { + return fmt.Errorf("%v %v %v %w", e.GetExchange(), e.GetAssetType(), e.Pair(), err) + } + h, err := settings.GetLatestHoldings() + if err != nil { + if !errors.Is(err, errNoHoldings) { + return err + } + h, err = holdings.Create(e, funds) + if err != nil { + return err + } + } + err = h.UpdateValue(e) + if err != nil { + return err + } + return p.SetHoldingsForTimestamp(h) +} + +// GetLatestHoldingsForAllCurrencies will return the current holdings for all loaded currencies +// this is useful to assess the position of your entire portfolio in order to help with risk decisions +func (p *Portfolio) GetLatestHoldingsForAllCurrencies() []holdings.Holding { + var resp []holdings.Holding + for _, exchangeMap := range p.exchangeAssetPairPortfolioSettings { + for _, assetMap := range exchangeMap { + for _, baseMap := range assetMap { + for _, quoteMap := range baseMap { + holds, err := quoteMap.GetLatestHoldings() + if err != nil { + continue + } + resp = append(resp, *holds) + } } } } - return result + return resp +} + +// ViewHoldingAtTimePeriod retrieves a snapshot of holdings at a specific time period, +// returning an error if not found +func (p *Portfolio) ViewHoldingAtTimePeriod(ev common.Event) (*holdings.Holding, error) { + settings, err := p.getSettings(ev.GetExchange(), ev.GetAssetType(), ev.Pair()) + if err != nil { + return nil, err + } + + h, ok := settings.HoldingsSnapshots[ev.GetTime().UnixNano()] + if !ok { + return nil, fmt.Errorf("%w for %v %v %v at %v", errNoHoldings, ev.GetExchange(), ev.GetAssetType(), ev.Pair(), ev.GetTime()) + } + return h, nil +} + +// GetLatestHoldings returns the latest holdings after being sorted by time +func (s *Settings) GetLatestHoldings() (*holdings.Holding, error) { + if len(s.HoldingsSnapshots) == 0 { + return nil, errNoHoldings + } + var latestTime int64 + for k := range s.HoldingsSnapshots { + if k > latestTime { + latestTime = k + } + } + return s.HoldingsSnapshots[latestTime], nil +} + +// GetHoldingsForTime returns the holdings for a time period, or an error holding if not found +func (s *Settings) GetHoldingsForTime(t time.Time) (*holdings.Holding, error) { + h, ok := s.HoldingsSnapshots[t.UnixNano()] + if !ok { + return nil, fmt.Errorf("%w for %v %v %v at %v", errNoHoldings, s.exchangeName, s.assetType, s.pair, t) + } + return h, nil +} + +// SetHoldingsForEvent re-sets offset details at the events time, +// based on current funding levels +func (p *Portfolio) SetHoldingsForEvent(fm funding.IFundReader, e common.Event) error { + if fm == nil { + return fmt.Errorf("%w funding manager", gctcommon.ErrNilPointer) + } + if e == nil { + return common.ErrNilEvent + } + settings, err := p.getSettings(e.GetExchange(), e.GetAssetType(), e.Pair()) + if err != nil { + return err + } + h, err := settings.GetHoldingsForTime(e.GetTime()) + if err != nil { + if !errors.Is(err, errNoHoldings) { + return err + } + h, err = holdings.Create(e, fm) + if err != nil { + return err + } + } + if e.GetAssetType().IsFutures() { + var c funding.ICollateralReader + c, err = fm.GetCollateralReader() + if err != nil { + return err + } + h.BaseSize = c.CurrentHoldings() + h.QuoteSize = c.AvailableFunds() + } else { + var pr funding.IPairReader + pr, err = fm.GetPairReader() + if err != nil { + return err + } + h.BaseSize = pr.BaseAvailable() + h.QuoteSize = pr.QuoteAvailable() + } + err = h.UpdateValue(e) + if err != nil { + return err + } + return p.SetHoldingsForTimestamp(h) } // GetUnrealisedPNL returns a basic struct containing unrealised PNL diff --git a/backtester/eventhandlers/portfolio/portfolio_test.go b/backtester/eventhandlers/portfolio/portfolio_test.go index 657dedb8..0145950b 100644 --- a/backtester/eventhandlers/portfolio/portfolio_test.go +++ b/backtester/eventhandlers/portfolio/portfolio_test.go @@ -2,7 +2,6 @@ package portfolio import ( "errors" - "strings" "testing" "time" @@ -19,24 +18,36 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/ftx" + "github.com/thrasher-corp/gocryptotrader/exchanges/binance" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) -const testExchange = "ftx" +const testExchange = "binance" + +var leet = decimal.NewFromInt(1337) func TestReset(t *testing.T) { t.Parallel() - p := Portfolio{ - exchangeAssetPairSettings: make(map[string]map[asset.Item]map[currency.Pair]*Settings), + p := &Portfolio{ + exchangeAssetPairPortfolioSettings: make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings), } - p.Reset() - if p.exchangeAssetPairSettings != nil { - t.Error("expected nil") + err := p.Reset() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + if p.exchangeAssetPairPortfolioSettings == nil { + t.Error("expected a map") + } + + p = nil + err = p.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) } } @@ -58,8 +69,8 @@ func TestSetup(t *testing.T) { } var p *Portfolio p, err = Setup(&size.Size{}, &risk.Risk{}, decimal.NewFromInt(1)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !p.riskFreeRate.Equal(decimal.NewFromInt(1)) { t.Error("expected 1") @@ -69,31 +80,31 @@ func TestSetup(t *testing.T) { func TestSetupCurrencySettingsMap(t *testing.T) { t.Parallel() p := &Portfolio{} - err := p.SetupCurrencySettingsMap(nil) + err := p.SetCurrencySettingsMap(nil) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - err = p.SetupCurrencySettingsMap(&exchange.Settings{}) + err = p.SetCurrencySettingsMap(&exchange.Settings{}) if !errors.Is(err, errExchangeUnset) { t.Errorf("received: %v, expected: %v", err, errExchangeUnset) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff}) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff}) if !errors.Is(err, errAssetUnset) { t.Errorf("received: %v, expected: %v", err, errAssetUnset) } - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot}) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot}) if !errors.Is(err, errCurrencyPairUnset) { t.Errorf("received: %v, expected: %v", err, errCurrencyPairUnset) } - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -101,39 +112,39 @@ func TestSetHoldings(t *testing.T) { t.Parallel() p := &Portfolio{} - err := p.setHoldingsForOffset(&holdings.Holding{}, false) + err := p.SetHoldingsForTimestamp(&holdings.Holding{}) if !errors.Is(err, errHoldingsNoTimestamp) { t.Errorf("received: %v, expected: %v", err, errHoldingsNoTimestamp) } tt := time.Now() - err = p.setHoldingsForOffset(&holdings.Holding{Timestamp: tt}, false) + err = p.SetHoldingsForTimestamp(&holdings.Holding{Timestamp: tt}) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt}, false) - if err != nil { - t.Error(err) + Pair: currency.NewPair(currency.BTC, currency.USDT), + Timestamp: tt}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt}, true) - if err != nil { - t.Error(err) + Pair: currency.NewPair(currency.BTC, currency.USDT), + Timestamp: tt}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -145,53 +156,44 @@ func TestGetLatestHoldingsForAllCurrencies(t *testing.T) { t.Error("expected 0") } tt := time.Now() - err := p.setHoldingsForOffset(&holdings.Holding{ + err := p.SetHoldingsForTimestamp(&holdings.Holding{ Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt}, true) + Pair: currency.NewPair(currency.BTC, currency.USDT), + Timestamp: tt}) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } h = p.GetLatestHoldingsForAllCurrencies() if len(h) != 0 { t.Errorf("received %v, expected %v", len(h), 0) } - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Offset: 1, Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt}, false) - if err != nil { - t.Error(err) + Pair: currency.NewPair(currency.BTC, currency.USDT), + Timestamp: tt}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } h = p.GetLatestHoldingsForAllCurrencies() if len(h) != 1 { t.Errorf("received %v, expected %v", len(h), 1) } - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Offset: 1, Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt}, false) - if !errors.Is(err, errHoldingsAlreadySet) { - t.Errorf("received: %v, expected: %v", err, errHoldingsAlreadySet) - } - err = p.setHoldingsForOffset(&holdings.Holding{ - Offset: 1, - Exchange: testExchange, - Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt}, true) + Pair: currency.NewPair(currency.BTC, currency.USDT), + Timestamp: tt}) if !errors.Is(err, nil) { t.Errorf("received: %v, expected: %v", err, nil) } @@ -210,38 +212,43 @@ func TestViewHoldingAtTimePeriod(t *testing.T) { Time: tt, Exchange: testExchange, AssetType: asset.Spot, - CurrencyPair: currency.NewPair(currency.BTC, currency.USD), + CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), }, } _, err := p.ViewHoldingAtTimePeriod(s) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) + } + + ff := &binance.Binance{} + ff.Name = testExchange + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + _, err = p.ViewHoldingAtTimePeriod(s) if !errors.Is(err, errNoHoldings) { t.Errorf("received: %v, expected: %v", err, errNoHoldings) } - ff := &ftx.FTX{} - ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) - } - - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Offset: 1, Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt}, false) - if err != nil { - t.Error(err) + Pair: currency.NewPair(currency.BTC, currency.USDT), + Timestamp: tt}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Offset: 2, Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), - Timestamp: tt.Add(time.Hour)}, false) - if err != nil { - t.Error(err) + Pair: currency.NewPair(currency.BTC, currency.USDT), + Timestamp: tt.Add(time.Hour)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } var h *holdings.Holding h, err = p.ViewHoldingAtTimePeriod(s) @@ -282,58 +289,58 @@ func TestUpdate(t *testing.T) { err = p.UpdateHoldings(&kline.Kline{ Base: b, }, pair) - if !errors.Is(err, errExchangeUnset) { - t.Errorf("received '%v' expected '%v'", err, errExchangeUnset) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received '%v' expected '%v'", err, errNoPortfolioSettings) } tt := time.Now() - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Offset: 1, Exchange: testExchange, Asset: asset.Spot, - Pair: currency.NewPair(currency.BTC, currency.USD), + Pair: currency.NewPair(currency.BTC, currency.USDT), Timestamp: tt, - }, false) + }) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } b.Time = tt b.Exchange = testExchange - b.CurrencyPair = currency.NewPair(currency.BTC, currency.USD) + b.CurrencyPair = currency.NewPair(currency.BTC, currency.USDT) b.AssetType = asset.Spot err = p.UpdateHoldings(&kline.Kline{ Base: b, }, pair) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } func TestGetComplianceManager(t *testing.T) { t.Parallel() p := Portfolio{} - _, err := p.GetComplianceManager("", asset.Empty, currency.EMPTYPAIR) + _, err := p.getComplianceManager("", asset.Empty, currency.EMPTYPAIR) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } var cm *compliance.Manager - cm, err = p.GetComplianceManager(testExchange, asset.Spot, currency.NewPair(currency.BTC, currency.USD)) - if err != nil { - t.Error(err) + cm, err = p.getComplianceManager(testExchange, asset.Spot, currency.NewPair(currency.BTC, currency.USDT)) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if cm == nil { t.Error("expected not nil") @@ -355,27 +362,27 @@ func TestAddComplianceSnapshot(t *testing.T) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = p.addComplianceSnapshot(&fill.Fill{ Base: &event.Base{ Exchange: testExchange, - CurrencyPair: currency.NewPair(currency.BTC, currency.USD), + CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), AssetType: asset.Spot, }, Order: &gctorder.Detail{ Exchange: testExchange, - Pair: currency.NewPair(currency.BTC, currency.USD), + Pair: currency.NewPair(currency.BTC, currency.USDT), AssetType: asset.Spot, }, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -390,12 +397,12 @@ func TestOnFill(t *testing.T) { f := &fill.Fill{ Base: &event.Base{ Exchange: testExchange, - CurrencyPair: currency.NewPair(currency.BTC, currency.USD), + CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), AssetType: asset.Spot, }, Order: &gctorder.Detail{ Exchange: testExchange, - Pair: currency.NewPair(currency.BTC, currency.USD), + Pair: currency.NewPair(currency.BTC, currency.USDT), AssetType: asset.Spot, }, } @@ -403,11 +410,11 @@ func TestOnFill(t *testing.T) { if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USDT)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } b, err := funding.CreateItem(testExchange, asset.Spot, currency.BTC, decimal.NewFromInt(1), decimal.Zero) @@ -423,14 +430,20 @@ func TestOnFill(t *testing.T) { t.Fatal(err) } _, err = p.OnFill(f, pair) + if !errors.Is(err, errHoldingsNoTimestamp) { + t.Errorf("received: %v, expected: %v", err, errHoldingsNoTimestamp) + } + + f.Time = time.Now() + _, err = p.OnFill(f, pair) if !errors.Is(err, nil) { t.Errorf("received: %v, expected: %v", err, nil) } f.Direction = gctorder.Buy _, err = p.OnFill(f, pair) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -438,7 +451,7 @@ func TestOnSignal(t *testing.T) { t.Parallel() p := Portfolio{} _, err := p.OnSignal(nil, nil, nil) - if !errors.Is(err, common.ErrNilArguments) { + if !errors.Is(err, gctcommon.ErrNilPointer) { t.Error(err) } b := &event.Base{} @@ -462,33 +475,33 @@ func TestOnSignal(t *testing.T) { if !errors.Is(err, funding.ErrFundsNotFound) { t.Errorf("received: %v, expected: %v", err, funding.ErrFundsNotFound) } - bc, err := funding.CreateItem(testExchange, asset.Spot, currency.BTC, decimal.NewFromInt(1337), decimal.Zero) + bc, err := funding.CreateItem(testExchange, asset.Spot, currency.BTC, leet, decimal.Zero) if err != nil { t.Fatal(err) } - qc, err := funding.CreateItem(testExchange, asset.Spot, currency.USDT, decimal.NewFromInt(1337), decimal.Zero) + qc, err := funding.CreateItem(testExchange, asset.Spot, currency.USDT, leet, decimal.Zero) if err != nil { t.Fatal(err) } - pair, err := funding.CreatePair(bc, qc) + funds, err := funding.CreatePair(bc, qc) if err != nil { t.Fatal(err) } - _, err = p.OnSignal(s, &exchange.Settings{}, pair) + _, err = p.OnSignal(s, &exchange.Settings{}, funds) if !errors.Is(err, errInvalidDirection) { t.Errorf("received: %v, expected: %v", err, errInvalidDirection) } s.Direction = gctorder.Buy - _, err = p.OnSignal(s, &exchange.Settings{}, pair) + _, err = p.OnSignal(s, &exchange.Settings{}, funds) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } b.Exchange = testExchange b.CurrencyPair = currency.NewPair(currency.BTC, currency.USD) @@ -498,7 +511,7 @@ func TestOnSignal(t *testing.T) { Direction: gctorder.Buy, } var resp *order.Order - resp, err = p.OnSignal(s, &exchange.Settings{}, pair) + resp, err = p.OnSignal(s, &exchange.Settings{}, funds) if err != nil { t.Fatal(err) } @@ -507,38 +520,39 @@ func TestOnSignal(t *testing.T) { } s.Direction = gctorder.Sell - _, err = p.OnSignal(s, &exchange.Settings{}, pair) - if err != nil { - t.Error(err) + _, err = p.OnSignal(s, &exchange.Settings{}, funds) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(resp.Reasons) != 4 { t.Error("expected issue") } s.Direction = gctorder.MissingData - _, err = p.OnSignal(s, &exchange.Settings{}, pair) - if err != nil { - t.Error(err) + _, err = p.OnSignal(s, &exchange.Settings{}, funds) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } s.Direction = gctorder.Buy - err = p.setHoldingsForOffset(&holdings.Holding{ + err = p.SetHoldingsForTimestamp(&holdings.Holding{ Exchange: "lol", Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD), Timestamp: time.Now(), - QuoteSize: decimal.NewFromInt(1337)}, false) + QuoteSize: leet, + }) if !errors.Is(err, errNoPortfolioSettings) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } - - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)}) - if err != nil { - t.Error(err) + cs := &exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.BTC, currency.USD)} + err = p.SetCurrencySettingsMap(cs) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - resp, err = p.OnSignal(s, &exchange.Settings{}, pair) - if err != nil { - t.Error(err) + resp, err = p.OnSignal(s, &exchange.Settings{}, funds) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if resp.Direction != gctorder.CouldNotBuy { t.Errorf("expected common.CouldNotBuy, received %v", resp.Direction) @@ -547,26 +561,95 @@ func TestOnSignal(t *testing.T) { s.ClosePrice = decimal.NewFromInt(10) s.Direction = gctorder.Buy s.Amount = decimal.NewFromInt(1) - resp, err = p.OnSignal(s, &exchange.Settings{}, pair) - if err != nil { - t.Error(err) + resp, err = p.OnSignal(s, &exchange.Settings{}, funds) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if resp.Amount.IsZero() { t.Error("expected an amount to be sized") } + + bc, err = funding.CreateItem(testExchange, asset.Futures, currency.BTC, leet, decimal.Zero) + if err != nil { + t.Fatal(err) + } + qc, err = funding.CreateItem(testExchange, asset.Futures, currency.USD, leet, decimal.Zero) + if err != nil { + t.Fatal(err) + } + collateralFunds, err := funding.CreateCollateral(bc, qc) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + s.AssetType = asset.Futures + cs.Asset = asset.Futures + + err = p.SetCurrencySettingsMap(cs) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) + } + s.Direction = gctorder.Long + _, err = p.OnSignal(s, cs, collateralFunds) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) + } + cp := currency.NewPair(currency.BTC, currency.USD) + _, err = p.getSettings(testExchange, asset.Futures, cp) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) + } + exchangeSettings := &Settings{} + exchangeSettings.FuturesTracker, err = gctorder.SetupMultiPositionTracker(&gctorder.MultiPositionTrackerSetup{ + Exchange: testExchange, + Asset: asset.Futures, + Pair: cp, + Underlying: currency.USD, + CollateralCurrency: currency.USD, + OfflineCalculation: true, + }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + err = exchangeSettings.FuturesTracker.TrackNewOrder(&gctorder.Detail{ + Price: 1337, + Amount: 1337, + Exchange: testExchange, + OrderID: "1337", + ClientOrderID: "1337", + Type: gctorder.Market, + Side: gctorder.Long, + Status: gctorder.AnyStatus, + AssetType: asset.Futures, + Date: time.Now(), + Pair: currency.NewPair(currency.BTC, currency.USD), + }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + s.Direction = gctorder.ClosePosition + _, err = p.OnSignal(s, cs, collateralFunds) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) + } } func TestGetLatestHoldings(t *testing.T) { t.Parallel() - cs := Settings{} - h := cs.GetLatestHoldings() - if !h.Timestamp.IsZero() { - t.Error("expected unset holdings") + s := &Settings{ + HoldingsSnapshots: make(map[int64]*holdings.Holding), + } + _, err := s.GetLatestHoldings() + if !errors.Is(err, errNoHoldings) { + t.Errorf("received: %v, expected: %v", err, errNoHoldings) } - tt := time.Now() - cs.HoldingsSnapshots = append(cs.HoldingsSnapshots, holdings.Holding{Timestamp: tt}) - h = cs.GetLatestHoldings() + tt := time.Now() + s.HoldingsSnapshots[tt.UnixNano()] = &holdings.Holding{Timestamp: tt} + + h, err := s.GetLatestHoldings() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if !h.Timestamp.Equal(tt) { t.Errorf("expected %v, received %v", tt, h.Timestamp) } @@ -583,14 +666,14 @@ func TestGetSnapshotAtTime(t *testing.T) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } cp := currency.NewPair(currency.XRP, currency.DOGE) - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: cp}) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: cp}) if !errors.Is(err, nil) { t.Errorf("received: %v, expected: %v", err, nil) } tt := time.Now() - s, ok := p.exchangeAssetPairSettings[testExchange][asset.Spot][cp] + s, ok := p.exchangeAssetPairPortfolioSettings[testExchange][asset.Spot][cp.Base.Item][cp.Quote.Item] if !ok { t.Fatal("couldn't get settings") } @@ -638,13 +721,13 @@ func TestGetLatestSnapshot(t *testing.T) { t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } cp := currency.NewPair(currency.XRP, currency.DOGE) - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.XRP, currency.DOGE)}) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: currency.NewPair(currency.XRP, currency.DOGE)}) if !errors.Is(err, nil) { t.Errorf("received: %v, expected: %v", err, nil) } - s, ok := p.exchangeAssetPairSettings[testExchange][asset.Spot][cp] + s, ok := p.exchangeAssetPairPortfolioSettings[testExchange][asset.Spot][cp.Base.Item][cp.Quote.Item] if !ok { t.Fatal("couldn't get settings") } @@ -706,21 +789,21 @@ func TestCalculatePNL(t *testing.T) { t.Errorf("received: %v, expected: %v", err, gctorder.ErrNotFuturesAsset) } - exch := &ftx.FTX{} + exch := &binance.Binance{} exch.Name = testExchange a := asset.Futures pair, err := currency.NewPairFromStrings("BTC", "1231") if !errors.Is(err, nil) { t.Errorf("received: %v, expected: %v", err, nil) } - err = p.SetupCurrencySettingsMap(&exchange.Settings{ + err = p.SetCurrencySettingsMap(&exchange.Settings{ Exchange: exch, UseRealOrders: false, Pair: pair, Asset: a, }) - if !errors.Is(err, nil) { - t.Errorf("received: %v, expected: %v", err, nil) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) } tt := time.Now().Add(time.Hour) tt0 := time.Now().Add(-time.Hour) @@ -730,8 +813,8 @@ func TestCalculatePNL(t *testing.T) { ev.Time = tt0 err = p.UpdatePNL(ev, decimal.Zero) - if !errors.Is(err, gctorder.ErrPositionNotFound) { - t.Errorf("received: %v, expected: %v", err, gctorder.ErrPositionNotFound) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received: %v, expected: %v", err, errNoPortfolioSettings) } od := &gctorder.Detail{ @@ -744,14 +827,28 @@ func TestCalculatePNL(t *testing.T) { Pair: pair, OrderID: "lol", } - - s, ok := p.exchangeAssetPairSettings[strings.ToLower(exch.Name)][a][pair] - if !ok { - t.Fatal("couldn't get settings") + mpt, err := gctorder.SetupMultiPositionTracker(&gctorder.MultiPositionTrackerSetup{ + Exchange: testExchange, + Asset: ev.AssetType, + Pair: ev.Pair(), + Underlying: currency.USDT, + CollateralCurrency: currency.USDT, + OfflineCalculation: true, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) } - ev.Close = decimal.NewFromInt(1337) + s := &Settings{ + FuturesTracker: mpt, + } + + p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item] = make(map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item][pair.Quote.Item] = s + ev.Close = leet err = s.ComplianceManager.AddSnapshot(&compliance.Snapshot{ - Offset: 0, Timestamp: tt0, Orders: []compliance.SnapshotOrder{ { @@ -806,110 +903,150 @@ func TestCalculatePNL(t *testing.T) { func TestTrackFuturesOrder(t *testing.T) { t.Parallel() p := &Portfolio{} - var expectedError = common.ErrNilEvent _, err := p.TrackFuturesOrder(nil, nil) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received '%v' expected '%v", err, common.ErrNilEvent) } - expectedError = common.ErrNilArguments _, err = p.TrackFuturesOrder(&fill.Fill{}, nil) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v", err, gctcommon.ErrNilPointer) } fundPair := &funding.SpotPair{} - expectedError = gctorder.ErrSubmissionIsNil _, err = p.TrackFuturesOrder(&fill.Fill{}, fundPair) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, gctorder.ErrSubmissionIsNil) { + t.Errorf("received '%v' expected '%v", err, gctorder.ErrSubmissionIsNil) } - expectedError = gctorder.ErrNotFuturesAsset od := &gctorder.Detail{} _, err = p.TrackFuturesOrder(&fill.Fill{ Order: od, }, fundPair) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, gctorder.ErrNotFuturesAsset) { + t.Errorf("received '%v' expected '%v", err, gctorder.ErrNotFuturesAsset) } od.AssetType = asset.Futures - expectedError = funding.ErrNotCollateral _, err = p.TrackFuturesOrder(&fill.Fill{ Order: od, }, fundPair) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) - } - - expectedError = nil - contract, err := funding.CreateItem(od.Exchange, od.AssetType, od.Pair.Base, decimal.NewFromInt(100), decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) - } - collateral, err := funding.CreateItem(od.Exchange, od.AssetType, od.Pair.Quote, decimal.NewFromInt(100), decimal.Zero) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) - } - collat, err := funding.CreateCollateral(contract, collateral) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) - } - expectedError = errExchangeUnset - _, err = p.TrackFuturesOrder(&fill.Fill{ - Order: od, - }, collat) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) - } - - cp := currency.NewPair(currency.XRP, currency.DOGE) - ff := &ftx.FTX{} - ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Futures, Pair: cp}) - if !errors.Is(err, nil) { - t.Errorf("received: %v, expected: %v", err, nil) + if !errors.Is(err, funding.ErrNotCollateral) { + t.Errorf("received '%v' expected '%v", err, funding.ErrNotCollateral) } + cp := currency.NewPair(currency.BTC, currency.USD) od.Pair = cp od.Exchange = testExchange od.Side = gctorder.Short od.AssetType = asset.Futures - od.Amount = 1337 - od.Price = 1337 - od.OrderID = testExchange + od.Amount = 1 + od.Price = 0 + od.OrderID = od.Exchange od.Date = time.Now() - expectedError = nil + contract, err := funding.CreateItem(od.Exchange, od.AssetType, od.Pair.Base, decimal.NewFromInt(9999), decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + collateral, err := funding.CreateItem(od.Exchange, od.AssetType, od.Pair.Quote, decimal.NewFromInt(9999), decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + err = collateral.IncreaseAvailable(decimal.NewFromInt(9999)) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + err = contract.IncreaseAvailable(decimal.NewFromInt(9999)) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + err = collateral.Reserve(leet) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + err = contract.Reserve(leet) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + collat, err := funding.CreateCollateral(contract, collateral) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + _, err = p.TrackFuturesOrder(&fill.Fill{ + Order: od, + }, collat) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received '%v' expected '%v", err, errNoPortfolioSettings) + } + + ff := &binance.Binance{} + ff.Name = od.Exchange + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Futures, Pair: cp}) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) + } _, err = p.TrackFuturesOrder(&fill.Fill{ Order: od, Base: &event.Base{ - Exchange: testExchange, + Exchange: od.Exchange, AssetType: asset.Futures, CurrencyPair: cp, }, }, collat) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received '%v' expected '%v", err, errNoPortfolioSettings) + } + + od.Side = gctorder.Long + _, err = p.TrackFuturesOrder(&fill.Fill{ + Order: od, + Base: &event.Base{ + Exchange: od.Exchange, + AssetType: asset.Futures, + CurrencyPair: cp, + Time: od.Date, + }, + }, collat) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received '%v' expected '%v", err, errNoPortfolioSettings) + } + + _, err = p.TrackFuturesOrder(&fill.Fill{ + Order: od, + Liquidated: true, + Base: &event.Base{ + Exchange: od.Exchange, + AssetType: asset.Futures, + CurrencyPair: cp, + Time: od.Date, + }, + }, collat) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received '%v' expected '%v", err, errNoPortfolioSettings) } } func TestGetHoldingsForTime(t *testing.T) { t.Parallel() - s := &Settings{} - h := s.GetHoldingsForTime(time.Now()) - if !h.Timestamp.IsZero() { - t.Error("expected unset holdings") + s := &Settings{ + HoldingsSnapshots: make(map[int64]*holdings.Holding), + } + _, err := s.GetHoldingsForTime(time.Now()) + if !errors.Is(err, errNoHoldings) { + t.Errorf("received '%v' expected '%v", err, errNoHoldings) } tt := time.Now() - s.HoldingsSnapshots = append(s.HoldingsSnapshots, holdings.Holding{ + s.HoldingsSnapshots[tt.UnixNano()] = &holdings.Holding{ Timestamp: tt, Offset: 1337, - }) - h = s.GetHoldingsForTime(time.Unix(1337, 0)) - if !h.Timestamp.IsZero() { - t.Error("expected unset holdings") + } + _, err = s.GetHoldingsForTime(time.Unix(1337, 0)) + if !errors.Is(err, errNoHoldings) { + t.Errorf("received '%v' expected '%v", err, errNoHoldings) } - h = s.GetHoldingsForTime(tt) + h, err := s.GetHoldingsForTime(tt) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } if h.Timestamp.IsZero() && h.Offset != 1337 { t.Error("expected set holdings") } @@ -926,17 +1063,17 @@ func TestGetPositions(t *testing.T) { ev := &fill.Fill{ Base: &event.Base{ Exchange: testExchange, - CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), + CurrencyPair: currency.NewPair(currency.BTC, currency.USD), AssetType: asset.Futures, }, } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: ev.AssetType, Pair: ev.Pair()}) - if !errors.Is(err, nil) { - t.Errorf("received: %v, expected: %v", err, nil) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: ev.AssetType, Pair: ev.Pair()}) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) } - expectedError = nil + expectedError = errNoPortfolioSettings _, err = p.GetPositions(ev) if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) @@ -954,28 +1091,44 @@ func TestGetLatestPNLForEvent(t *testing.T) { ev := &fill.Fill{ Base: &event.Base{ Exchange: testExchange, - CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), + CurrencyPair: currency.NewPair(currency.BTC, currency.USD), AssetType: asset.Futures, }, } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: ev.AssetType, Pair: ev.Pair()}) - if !errors.Is(err, nil) { - t.Errorf("received: %v, expected: %v", err, nil) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: ev.AssetType, Pair: ev.Pair()}) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) } - expectedError = gctorder.ErrPositionNotFound + expectedError = errNoPortfolioSettings _, err = p.GetLatestPNLForEvent(ev) if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) } - settings, ok := p.exchangeAssetPairSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair()] - if !ok { - t.Fatalf("where did settings go?") + mpt, err := gctorder.SetupMultiPositionTracker(&gctorder.MultiPositionTrackerSetup{ + Exchange: testExchange, + Asset: ev.AssetType, + Pair: ev.Pair(), + Underlying: currency.USDT, + CollateralCurrency: currency.USDT, + OfflineCalculation: true, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) } + s := &Settings{ + FuturesTracker: mpt, + } + + p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item] = make(map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item][ev.Pair().Quote.Item] = s expectedError = nil - err = settings.FuturesTracker.TrackNewOrder(&gctorder.Detail{ + err = s.FuturesTracker.TrackNewOrder(&gctorder.Detail{ Exchange: ev.GetExchange(), AssetType: ev.AssetType, Pair: ev.Pair(), @@ -1000,19 +1153,16 @@ func TestGetLatestPNLForEvent(t *testing.T) { func TestGetFuturesSettingsFromEvent(t *testing.T) { t.Parallel() p := &Portfolio{} - var expectedError = common.ErrNilEvent _, err := p.getFuturesSettingsFromEvent(nil) - if !errors.Is(err, expectedError) { - t.Fatalf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, common.ErrNilEvent) { + t.Fatalf("received '%v' expected '%v'", err, common.ErrNilEvent) } - expectedError = gctorder.ErrNotFuturesAsset b := &event.Base{} - _, err = p.getFuturesSettingsFromEvent(&fill.Fill{ Base: b, }) - if !errors.Is(err, expectedError) { - t.Fatalf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, gctorder.ErrNotFuturesAsset) { + t.Fatalf("received '%v' expected '%v'", err, gctorder.ErrNotFuturesAsset) } b.Exchange = testExchange b.CurrencyPair = currency.NewPair(currency.BTC, currency.USDT) @@ -1020,72 +1170,25 @@ func TestGetFuturesSettingsFromEvent(t *testing.T) { ev := &fill.Fill{ Base: b, } - expectedError = errExchangeUnset _, err = p.getFuturesSettingsFromEvent(ev) - if !errors.Is(err, expectedError) { - t.Fatalf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, errNoPortfolioSettings) { + t.Fatalf("received '%v' expected '%v'", err, errNoPortfolioSettings) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: ev.AssetType, Pair: ev.Pair()}) - if !errors.Is(err, nil) { - t.Errorf("received: %v, expected: %v", err, nil) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: ev.AssetType, Pair: ev.Pair()}) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) } - expectedError = nil - settings, err := p.getFuturesSettingsFromEvent(ev) - if !errors.Is(err, expectedError) { - t.Fatalf("received '%v' expected '%v'", err, expectedError) - } - - expectedError = errUnsetFuturesTracker - settings.FuturesTracker = nil _, err = p.getFuturesSettingsFromEvent(ev) - if !errors.Is(err, expectedError) { - t.Fatalf("received '%v' expected '%v'", err, expectedError) + if !errors.Is(err, errNoPortfolioSettings) { + t.Fatalf("received '%v' expected '%v'", err, errNoPortfolioSettings) } -} -func TestGetLatestPNLs(t *testing.T) { - t.Parallel() - p := &Portfolio{} - latest := p.GetLatestPNLs() - if len(latest) != 0 { - t.Error("expected empty") - } - ev := &fill.Fill{ - Base: &event.Base{ - Exchange: testExchange, - CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), - AssetType: asset.Futures, - }, - } - ff := &ftx.FTX{} - ff.Name = testExchange - err := p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: ev.AssetType, Pair: ev.Pair()}) - if !errors.Is(err, nil) { - t.Errorf("received: %v, expected: %v", err, nil) - } - settings, ok := p.exchangeAssetPairSettings[ev.GetExchange()][ev.GetAssetType()][ev.Pair()] - if !ok { - t.Fatalf("where did settings go?") - } - err = settings.FuturesTracker.TrackNewOrder(&gctorder.Detail{ - Exchange: ev.GetExchange(), - AssetType: ev.AssetType, - Pair: ev.Pair(), - Amount: 1, - Price: 1, - OrderID: "one", - Date: time.Now(), - Side: gctorder.Buy, - }) - if !errors.Is(err, nil) { - t.Fatalf("received '%v' expected '%v'", err, nil) - } - latest = p.GetLatestPNLs() - if len(latest) != 1 { - t.Error("expected 1") + _, err = p.getFuturesSettingsFromEvent(ev) + if !errors.Is(err, errNoPortfolioSettings) { + t.Fatalf("received '%v' expected '%v'", err, errNoPortfolioSettings) } } @@ -1093,13 +1196,13 @@ func TestGetUnrealisedPNL(t *testing.T) { t.Parallel() p := PNLSummary{ Exchange: testExchange, - Item: asset.Futures, + Asset: asset.Futures, Pair: currency.NewPair(currency.BTC, currency.USDT), - CollateralCurrency: currency.USD, + CollateralCurrency: currency.USDT, Offset: 1, Result: gctorder.PNLResult{ Time: time.Now(), - UnrealisedPNL: decimal.NewFromInt(1337), + UnrealisedPNL: leet, RealisedPNLBeforeFees: decimal.NewFromInt(1338), RealisedPNL: decimal.NewFromInt(1339), Price: decimal.NewFromInt(1331), @@ -1125,13 +1228,13 @@ func TestGetRealisedPNL(t *testing.T) { t.Parallel() p := PNLSummary{ Exchange: testExchange, - Item: asset.Futures, + Asset: asset.Futures, Pair: currency.NewPair(currency.BTC, currency.USDT), - CollateralCurrency: currency.USD, + CollateralCurrency: currency.USDT, Offset: 1, Result: gctorder.PNLResult{ Time: time.Now(), - UnrealisedPNL: decimal.NewFromInt(1337), + UnrealisedPNL: leet, RealisedPNLBeforeFees: decimal.NewFromInt(1338), RealisedPNL: decimal.NewFromInt(1339), Price: decimal.NewFromInt(1331), @@ -1157,13 +1260,13 @@ func TestGetExposure(t *testing.T) { t.Parallel() p := PNLSummary{ Exchange: testExchange, - Item: asset.Futures, + Asset: asset.Futures, Pair: currency.NewPair(currency.BTC, currency.USDT), - CollateralCurrency: currency.USD, + CollateralCurrency: currency.USDT, Offset: 1, Result: gctorder.PNLResult{ Time: time.Now(), - UnrealisedPNL: decimal.NewFromInt(1337), + UnrealisedPNL: leet, RealisedPNLBeforeFees: decimal.NewFromInt(1338), RealisedPNL: decimal.NewFromInt(1339), Price: decimal.NewFromInt(1331), @@ -1182,13 +1285,13 @@ func TestGetCollateralCurrency(t *testing.T) { t.Parallel() p := PNLSummary{ Exchange: testExchange, - Item: asset.Futures, + Asset: asset.Futures, Pair: currency.NewPair(currency.BTC, currency.USDT), - CollateralCurrency: currency.USD, + CollateralCurrency: currency.USDT, Offset: 1, Result: gctorder.PNLResult{ Time: time.Now(), - UnrealisedPNL: decimal.NewFromInt(1337), + UnrealisedPNL: leet, RealisedPNLBeforeFees: decimal.NewFromInt(1338), RealisedPNL: decimal.NewFromInt(1339), Price: decimal.NewFromInt(1331), @@ -1208,13 +1311,13 @@ func TestGetDirection(t *testing.T) { t.Parallel() p := PNLSummary{ Exchange: testExchange, - Item: asset.Futures, + Asset: asset.Futures, Pair: currency.NewPair(currency.BTC, currency.USDT), - CollateralCurrency: currency.USD, + CollateralCurrency: currency.USDT, Offset: 1, Result: gctorder.PNLResult{ Time: time.Now(), - UnrealisedPNL: decimal.NewFromInt(1337), + UnrealisedPNL: leet, RealisedPNLBeforeFees: decimal.NewFromInt(1338), RealisedPNL: decimal.NewFromInt(1339), Price: decimal.NewFromInt(1331), @@ -1240,7 +1343,7 @@ func TestCannotPurchase(t *testing.T) { s := &signal.Signal{ Base: &event.Base{}, } - expectedError = common.ErrNilArguments + expectedError = gctcommon.ErrNilPointer _, err = cannotPurchase(s, nil) if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) @@ -1315,7 +1418,7 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) { ev := &kline.Kline{ Base: b, } - expectedError = common.ErrNilArguments + expectedError = gctcommon.ErrNilPointer _, err = p.CreateLiquidationOrdersForExchange(ev, nil) if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) @@ -1328,30 +1431,30 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) { t.Fatalf("received '%v' expected '%v'", err, expectedError) } - ff := &ftx.FTX{} + ff := &binance.Binance{} ff.Name = testExchange - cp := currency.NewPair(currency.BTC, currency.USD) + cp := currency.NewPair(currency.BTC, currency.USDT) expectedError = nil - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Futures, Pair: cp}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Futures, Pair: cp}) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) } - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: cp}) - if err != nil { - t.Error(err) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: ff, Asset: asset.Spot, Pair: cp}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - ev.Exchange = testExchange + ev.Exchange = ff.Name _, err = p.CreateLiquidationOrdersForExchange(ev, funds) if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) } - settings, err := p.getSettings(ff.Name, asset.Futures, cp) - if !errors.Is(err, expectedError) { - t.Fatalf("received '%v' expected '%v'", err, expectedError) + _, err = p.getSettings(ff.Name, asset.Futures, cp) + if !errors.Is(err, errNoPortfolioSettings) { + t.Fatalf("received '%v' expected '%v'", err, errNoPortfolioSettings) } - err = settings.FuturesTracker.TrackNewOrder(&gctorder.Detail{ + od := &gctorder.Detail{ Exchange: ff.Name, AssetType: asset.Futures, Pair: cp, @@ -1360,10 +1463,33 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) { Date: time.Now(), Amount: 1337, Price: 1337, - }) - if !errors.Is(err, expectedError) { - t.Fatalf("received '%v' expected '%v'", err, expectedError) } + + mpt, err := gctorder.SetupMultiPositionTracker(&gctorder.MultiPositionTrackerSetup{ + Exchange: testExchange, + Asset: od.AssetType, + Pair: cp, + Underlying: currency.USDT, + CollateralCurrency: currency.USDT, + OfflineCalculation: true, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + settings := &Settings{ + FuturesTracker: mpt, + } + + err = settings.FuturesTracker.TrackNewOrder(od) + if !errors.Is(err, expectedError) { + t.Errorf("received '%v', expected '%v'", err, expectedError) + } + p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item] = make(map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][ev.Pair().Base.Item][ev.Pair().Quote.Item] = settings + ev.Exchange = ff.Name ev.AssetType = asset.Futures ev.CurrencyPair = cp @@ -1381,7 +1507,7 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) { if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) } - err = item.IncreaseAvailable(decimal.NewFromInt(1337)) + err = item.IncreaseAvailable(leet) if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) } @@ -1389,7 +1515,7 @@ func TestCreateLiquidationOrdersForExchange(t *testing.T) { if !errors.Is(err, expectedError) { t.Fatalf("received '%v' expected '%v'", err, expectedError) } - if len(orders) != 2 { + if len(orders) != 0 { t.Errorf("expected two orders generated, received '%v'", len(orders)) } } @@ -1419,14 +1545,14 @@ func TestCheckLiquidationStatus(t *testing.T) { ev := &kline.Kline{ Base: &event.Base{}, } - expectedError = common.ErrNilArguments + expectedError = gctcommon.ErrNilPointer err = p.CheckLiquidationStatus(ev, nil, nil) if !errors.Is(err, expectedError) { t.Errorf("received '%v', expected '%v'", err, expectedError) } item := asset.Futures - pair := currency.NewPair(currency.BTC, currency.USD) + pair := currency.NewPair(currency.BTC, currency.USDT) expectedError = nil contract, err := funding.CreateItem(testExchange, item, pair.Base, decimal.NewFromInt(100), decimal.Zero) if !errors.Is(err, expectedError) { @@ -1441,7 +1567,7 @@ func TestCheckLiquidationStatus(t *testing.T) { t.Errorf("received '%v' expected '%v", err, expectedError) } - expectedError = common.ErrNilArguments + expectedError = gctcommon.ErrNilPointer err = p.CheckLiquidationStatus(ev, collat, nil) if !errors.Is(err, expectedError) { t.Errorf("received '%v', expected '%v'", err, expectedError) @@ -1454,20 +1580,20 @@ func TestCheckLiquidationStatus(t *testing.T) { t.Errorf("received '%v', expected '%v'", err, expectedError) } - pnl.Item = asset.Futures + pnl.Asset = asset.Futures ev.AssetType = asset.Futures - ev.Exchange = "ftx" + ev.Exchange = testExchange ev.CurrencyPair = pair - exch := &ftx.FTX{} - exch.Name = testExchange + exch := &binance.Binance{} + exch.Name = ev.Exchange expectedError = nil - err = p.SetupCurrencySettingsMap(&exchange.Settings{Exchange: exch, Asset: asset.Futures, Pair: pair}) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v', expected '%v'", err, expectedError) + err = p.SetCurrencySettingsMap(&exchange.Settings{Exchange: exch, Asset: asset.Futures, Pair: pair}) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNotYetImplemented) } - settings, err := p.getSettings(testExchange, ev.AssetType, ev.Pair()) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v', expected '%v'", err, expectedError) + _, err = p.getSettings(ev.Exchange, ev.AssetType, ev.Pair()) + if !errors.Is(err, errNoPortfolioSettings) { + t.Errorf("received '%v', expected '%v'", err, errNoPortfolioSettings) } od := &gctorder.Detail{ Price: 1336, @@ -1479,12 +1605,98 @@ func TestCheckLiquidationStatus(t *testing.T) { Pair: pair, OrderID: "lol", } + mpt, err := gctorder.SetupMultiPositionTracker(&gctorder.MultiPositionTrackerSetup{ + Exchange: testExchange, + Asset: ev.AssetType, + Pair: ev.Pair(), + Underlying: currency.USDT, + CollateralCurrency: currency.USDT, + OfflineCalculation: true, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + settings := &Settings{ + FuturesTracker: mpt, + } + err = settings.FuturesTracker.TrackNewOrder(od) if !errors.Is(err, expectedError) { t.Errorf("received '%v', expected '%v'", err, expectedError) } + p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType] = make(map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item] = make(map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[testExchange][ev.AssetType][pair.Base.Item][pair.Quote.Item] = settings err = p.CheckLiquidationStatus(ev, collat, pnl) if !errors.Is(err, expectedError) { t.Errorf("received '%v', expected '%v'", err, expectedError) } } + +func TestSetHoldingsForEvent(t *testing.T) { + t.Parallel() + p := &Portfolio{} + err := p.SetHoldingsForEvent(nil, nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNilPointer) + } + + item, err := funding.CreateItem(testExchange, asset.Spot, currency.BTC, decimal.Zero, decimal.Zero) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + cp, err := funding.CreatePair(item, item) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + err = p.SetHoldingsForEvent(cp.FundReader(), nil) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received '%v', expected '%v'", err, common.ErrNilEvent) + } + + err = p.SetHoldingsForEvent(cp.FundReader(), nil) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received '%v', expected '%v'", err, common.ErrNilEvent) + } + + tt := time.Now() + ev := &signal.Signal{ + Base: &event.Base{ + Exchange: testExchange, + CurrencyPair: currency.NewPair(currency.BTC, currency.BTC), + AssetType: asset.Spot, + Time: tt, + }, + } + f := &binance.Binance{} + f.SetDefaults() + err = p.SetCurrencySettingsMap(&exchange.Settings{ + Exchange: f, + Pair: ev.Pair(), + Asset: ev.AssetType, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + err = p.SetHoldingsForEvent(cp.FundReader(), ev) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + + err = p.SetHoldingsForTimestamp(&holdings.Holding{ + Item: currency.BTC, + Pair: ev.Pair(), + Asset: ev.AssetType, + Exchange: ev.Exchange, + Timestamp: tt, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + err = p.SetHoldingsForEvent(cp.FundReader(), ev) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } +} diff --git a/backtester/eventhandlers/portfolio/portfolio_types.go b/backtester/eventhandlers/portfolio/portfolio_types.go index 62212cff..64a0761b 100644 --- a/backtester/eventhandlers/portfolio/portfolio_types.go +++ b/backtester/eventhandlers/portfolio/portfolio_types.go @@ -6,6 +6,7 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" @@ -33,37 +34,37 @@ var ( errNoPortfolioSettings = errors.New("no portfolio settings") errNoHoldings = errors.New("no holdings found") errHoldingsNoTimestamp = errors.New("holding with unset timestamp received") - errHoldingsAlreadySet = errors.New("holding already set") errUnsetFuturesTracker = errors.New("portfolio settings futures tracker unset") ) // Portfolio stores all holdings and rules to assess orders, allowing the portfolio manager to // modify, accept or reject strategy signals type Portfolio struct { - riskFreeRate decimal.Decimal - sizeManager SizeHandler - riskManager risk.Handler - exchangeAssetPairSettings map[string]map[asset.Item]map[currency.Pair]*Settings + riskFreeRate decimal.Decimal + sizeManager SizeHandler + riskManager risk.Handler + exchangeAssetPairPortfolioSettings map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings } // Handler contains all functions expected to operate a portfolio manager type Handler interface { OnSignal(signal.Event, *exchange.Settings, funding.IFundReserver) (*order.Order, error) OnFill(fill.Event, funding.IFundReleaser) (fill.Event, error) - GetLatestOrderSnapshotForEvent(common.EventHandler) (compliance.Snapshot, error) + GetLatestOrderSnapshotForEvent(common.Event) (compliance.Snapshot, error) GetLatestOrderSnapshots() ([]compliance.Snapshot, error) - ViewHoldingAtTimePeriod(common.EventHandler) (*holdings.Holding, error) - setHoldingsForOffset(*holdings.Holding, bool) error - UpdateHoldings(common.DataEventHandler, funding.IFundReleaser) error - GetComplianceManager(string, asset.Item, currency.Pair) (*compliance.Manager, error) - GetPositions(common.EventHandler) ([]gctorder.Position, error) + ViewHoldingAtTimePeriod(common.Event) (*holdings.Holding, error) + SetHoldingsForTimestamp(*holdings.Holding) error + UpdateHoldings(data.Event, funding.IFundReleaser) error + GetPositions(common.Event) ([]gctorder.Position, error) TrackFuturesOrder(fill.Event, funding.IFundReleaser) (*PNLSummary, error) - UpdatePNL(common.EventHandler, decimal.Decimal) error - GetLatestPNLForEvent(common.EventHandler) (*PNLSummary, error) - GetLatestPNLs() []PNLSummary - CheckLiquidationStatus(common.DataEventHandler, funding.ICollateralReader, *PNLSummary) error - CreateLiquidationOrdersForExchange(common.DataEventHandler, funding.IFundingManager) ([]order.Event, error) - Reset() + UpdatePNL(common.Event, decimal.Decimal) error + GetLatestPNLForEvent(common.Event) (*PNLSummary, error) + CheckLiquidationStatus(data.Event, funding.ICollateralReader, *PNLSummary) error + CreateLiquidationOrdersForExchange(data.Event, funding.IFundingManager) ([]order.Event, error) + GetLatestHoldingsForAllCurrencies() []holdings.Holding + Reset() error + SetHoldingsForEvent(funding.IFundReader, common.Event) error + GetLatestComplianceSnapshot(string, asset.Item, currency.Pair) (*compliance.Snapshot, error) } // SizeHandler is the interface to help size orders @@ -74,10 +75,14 @@ type SizeHandler interface { // Settings holds all important information for the portfolio manager // to assess purchasing decisions type Settings struct { + exchangeName string + assetType asset.Item + pair currency.Pair + BuySideSizing exchange.MinMax SellSideSizing exchange.MinMax Leverage exchange.Leverage - HoldingsSnapshots []holdings.Holding + HoldingsSnapshots map[int64]*holdings.Holding ComplianceManager compliance.Manager Exchange gctexchange.IBotExchange FuturesTracker *gctorder.MultiPositionTracker @@ -87,7 +92,7 @@ type Settings struct { // exchange details type PNLSummary struct { Exchange string - Item asset.Item + Asset asset.Item Pair currency.Pair CollateralCurrency currency.Code Offset int64 diff --git a/backtester/eventhandlers/portfolio/risk/risk.go b/backtester/eventhandlers/portfolio/risk/risk.go index 36382035..8b8a362c 100644 --- a/backtester/eventhandlers/portfolio/risk/risk.go +++ b/backtester/eventhandlers/portfolio/risk/risk.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" ) @@ -15,7 +16,7 @@ import ( // we are in a position to follow through with an order func (r *Risk) EvaluateOrder(o order.Event, latestHoldings []holdings.Holding, s compliance.Snapshot) (*order.Order, error) { if o == nil || latestHoldings == nil { - return nil, common.ErrNilArguments + return nil, gctcommon.ErrNilPointer } retOrder, ok := o.(*order.Order) if !ok { @@ -23,8 +24,8 @@ func (r *Risk) EvaluateOrder(o order.Event, latestHoldings []holdings.Holding, s } ex := o.GetExchange() a := o.GetAssetType() - p := o.Pair() - lookup, ok := r.CurrencySettings[ex][a][p] + p := o.Pair().Format(currency.EMPTYFORMAT) + lookup, ok := r.CurrencySettings[ex][a][p.Base.Item][p.Quote.Item] if !ok { return nil, fmt.Errorf("%v %v %v %w", ex, a, p, errNoCurrencySettings) } diff --git a/backtester/eventhandlers/portfolio/risk/risk_test.go b/backtester/eventhandlers/portfolio/risk/risk_test.go index 3136fb4b..118a26d5 100644 --- a/backtester/eventhandlers/portfolio/risk/risk_test.go +++ b/backtester/eventhandlers/portfolio/risk/risk_test.go @@ -5,11 +5,11 @@ import ( "testing" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -54,7 +54,7 @@ func TestEvaluateOrder(t *testing.T) { t.Parallel() r := Risk{} _, err := r.EvaluateOrder(nil, nil, compliance.Snapshot{}) - if !errors.Is(err, common.ErrNilArguments) { + if !errors.Is(err, gctcommon.ErrNilPointer) { t.Error(err) } p := currency.NewPair(currency.BTC, currency.USDT) @@ -68,15 +68,16 @@ func TestEvaluateOrder(t *testing.T) { }, } h := []holdings.Holding{} - r.CurrencySettings = make(map[string]map[asset.Item]map[currency.Pair]*CurrencySettings) - r.CurrencySettings[e] = make(map[asset.Item]map[currency.Pair]*CurrencySettings) - r.CurrencySettings[e][a] = make(map[currency.Pair]*CurrencySettings) + r.CurrencySettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencySettings) + r.CurrencySettings[e] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencySettings) + r.CurrencySettings[e][a] = make(map[*currency.Item]map[*currency.Item]*CurrencySettings) + r.CurrencySettings[e][a][p.Base.Item] = make(map[*currency.Item]*CurrencySettings) _, err = r.EvaluateOrder(o, h, compliance.Snapshot{}) if !errors.Is(err, errNoCurrencySettings) { t.Error(err) } - r.CurrencySettings[e][a][p] = &CurrencySettings{ + r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item] = &CurrencySettings{ MaximumOrdersWithLeverageRatio: decimal.NewFromFloat(0.3), MaxLeverageRate: decimal.NewFromFloat(0.3), MaximumHoldingRatio: decimal.NewFromFloat(0.3), @@ -87,15 +88,15 @@ func TestEvaluateOrder(t *testing.T) { BaseSize: decimal.NewFromInt(1), }) _, err = r.EvaluateOrder(o, h, compliance.Snapshot{}) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } h = append(h, holdings.Holding{ Pair: currency.NewPair(currency.DOGE, currency.USDT), }) o.Leverage = decimal.NewFromFloat(1.1) - r.CurrencySettings[e][a][p].MaximumHoldingRatio = decimal.Zero + r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaximumHoldingRatio = decimal.Zero _, err = r.EvaluateOrder(o, h, compliance.Snapshot{}) if !errors.Is(err, errLeverageNotAllowed) { t.Error(err) @@ -107,14 +108,14 @@ func TestEvaluateOrder(t *testing.T) { } r.MaximumLeverage = decimal.NewFromInt(33) - r.CurrencySettings[e][a][p].MaxLeverageRate = decimal.NewFromInt(33) + r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaxLeverageRate = decimal.NewFromInt(33) _, err = r.EvaluateOrder(o, h, compliance.Snapshot{}) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } r.MaximumLeverage = decimal.NewFromInt(33) - r.CurrencySettings[e][a][p].MaxLeverageRate = decimal.NewFromInt(33) + r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaxLeverageRate = decimal.NewFromInt(33) _, err = r.EvaluateOrder(o, h, compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ @@ -130,10 +131,10 @@ func TestEvaluateOrder(t *testing.T) { } h = append(h, holdings.Holding{Pair: p, BaseValue: decimal.NewFromInt(1337)}, holdings.Holding{Pair: p, BaseValue: decimal.NewFromFloat(1337.42)}) - r.CurrencySettings[e][a][p].MaximumHoldingRatio = decimal.NewFromFloat(0.1) + r.CurrencySettings[e][a][p.Base.Item][p.Quote.Item].MaximumHoldingRatio = decimal.NewFromFloat(0.1) _, err = r.EvaluateOrder(o, h, compliance.Snapshot{}) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } h = append(h, holdings.Holding{Pair: currency.NewPair(currency.DOGE, currency.LTC), BaseValue: decimal.NewFromInt(1337)}) diff --git a/backtester/eventhandlers/portfolio/risk/risk_types.go b/backtester/eventhandlers/portfolio/risk/risk_types.go index d3c1d5b9..fe7538c2 100644 --- a/backtester/eventhandlers/portfolio/risk/risk_types.go +++ b/backtester/eventhandlers/portfolio/risk/risk_types.go @@ -24,7 +24,7 @@ type Handler interface { // Risk contains all currency settings in order to evaluate potential orders type Risk struct { - CurrencySettings map[string]map[asset.Item]map[currency.Pair]*CurrencySettings + CurrencySettings map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencySettings CanUseLeverage bool MaximumLeverage decimal.Decimal } diff --git a/backtester/eventhandlers/portfolio/setup.go b/backtester/eventhandlers/portfolio/setup.go index 50e4ab97..09fd2037 100644 --- a/backtester/eventhandlers/portfolio/setup.go +++ b/backtester/eventhandlers/portfolio/setup.go @@ -5,8 +5,9 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/risk" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -32,12 +33,19 @@ func Setup(sh SizeHandler, r risk.Handler, riskFreeRate decimal.Decimal) (*Portf } // Reset returns the portfolio manager to its default state -func (p *Portfolio) Reset() { - p.exchangeAssetPairSettings = nil +func (p *Portfolio) Reset() error { + if p == nil { + return gctcommon.ErrNilPointer + } + p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.riskFreeRate = decimal.Zero + p.sizeManager = nil + p.riskManager = nil + return nil } -// SetupCurrencySettingsMap ensures a map is created and no panics happen -func (p *Portfolio) SetupCurrencySettingsMap(setup *exchange.Settings) error { +// SetCurrencySettingsMap ensures a map is created and no panics happen +func (p *Portfolio) SetCurrencySettingsMap(setup *exchange.Settings) error { if setup == nil { return errNoPortfolioSettings } @@ -50,31 +58,42 @@ func (p *Portfolio) SetupCurrencySettingsMap(setup *exchange.Settings) error { if setup.Pair.IsEmpty() { return errCurrencyPairUnset } - if p.exchangeAssetPairSettings == nil { - p.exchangeAssetPairSettings = make(map[string]map[asset.Item]map[currency.Pair]*Settings) + + if p.exchangeAssetPairPortfolioSettings == nil { + p.exchangeAssetPairPortfolioSettings = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) } name := strings.ToLower(setup.Exchange.GetName()) - if p.exchangeAssetPairSettings[name] == nil { - p.exchangeAssetPairSettings[name] = make(map[asset.Item]map[currency.Pair]*Settings) + m, ok := p.exchangeAssetPairPortfolioSettings[name] + if !ok { + m = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*Settings) + p.exchangeAssetPairPortfolioSettings[name] = m } - if p.exchangeAssetPairSettings[name][setup.Asset] == nil { - p.exchangeAssetPairSettings[name][setup.Asset] = make(map[currency.Pair]*Settings) + m2, ok := m[setup.Asset] + if !ok { + m2 = make(map[*currency.Item]map[*currency.Item]*Settings) + m[setup.Asset] = m2 } - if _, ok := p.exchangeAssetPairSettings[name][setup.Asset][setup.Pair]; ok { - return nil - } - collateralCurrency, _, err := setup.Exchange.GetCollateralCurrencyForContract(setup.Asset, setup.Pair) - if err != nil { - return err + m3, ok := m2[setup.Pair.Base.Item] + if !ok { + m3 = make(map[*currency.Item]*Settings) + m2[setup.Pair.Base.Item] = m3 } + settings := &Settings{ + Exchange: setup.Exchange, + exchangeName: name, + assetType: setup.Asset, + pair: setup.Pair, BuySideSizing: setup.BuySide, SellSideSizing: setup.SellSide, Leverage: setup.Leverage, - Exchange: setup.Exchange, - ComplianceManager: compliance.Manager{}, + HoldingsSnapshots: make(map[int64]*holdings.Holding), } if setup.Asset.IsFutures() { + collateralCurrency, _, err := setup.Exchange.GetCollateralCurrencyForContract(setup.Asset, setup.Pair) + if err != nil { + return err + } futureTrackerSetup := &gctorder.MultiPositionTrackerSetup{ Exchange: name, Asset: setup.Asset, @@ -94,6 +113,6 @@ func (p *Portfolio) SetupCurrencySettingsMap(setup *exchange.Settings) error { } settings.FuturesTracker = tracker } - p.exchangeAssetPairSettings[name][setup.Asset][setup.Pair] = settings + m3[setup.Pair.Quote.Item] = settings return nil } diff --git a/backtester/eventhandlers/portfolio/size/size.go b/backtester/eventhandlers/portfolio/size/size.go index b5c13ef7..b129498b 100644 --- a/backtester/eventhandlers/portfolio/size/size.go +++ b/backtester/eventhandlers/portfolio/size/size.go @@ -8,13 +8,17 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) // SizeOrder is responsible for ensuring that the order size is within config limits func (s *Size) SizeOrder(o order.Event, amountAvailable decimal.Decimal, cs *exchange.Settings) (*order.Order, decimal.Decimal, error) { - if o == nil || cs == nil { - return nil, decimal.Zero, common.ErrNilArguments + if o == nil { + return nil, decimal.Zero, fmt.Errorf("%w order event", gctcommon.ErrNilPointer) + } + if cs == nil { + return nil, decimal.Zero, fmt.Errorf("%w exchange settings", gctcommon.ErrNilPointer) } if amountAvailable.LessThanOrEqual(decimal.Zero) { return nil, decimal.Zero, errNoFunds @@ -37,9 +41,16 @@ func (s *Size) SizeOrder(o order.Event, amountAvailable decimal.Decimal, cs *exc if err != nil { return nil, decimal.Zero, err } + + sizedPrice := o.GetClosePrice() + if fde.GetClosePrice().GreaterThan(o.GetClosePrice()) { + // ensure limits are respected by using the largest price + sizedPrice = fde.GetClosePrice() + } + initialAmount := amountAvailable.Mul(scalingInfo.Weighting).Div(fde.GetClosePrice()) - oNotionalPosition := initialAmount.Mul(o.GetClosePrice()) - sizedAmount, estFee, err := s.calculateAmount(o.GetDirection(), o.GetClosePrice(), oNotionalPosition, cs, o) + oNotionalPosition := initialAmount.Mul(sizedPrice) + sizedAmount, estFee, err := s.calculateAmount(o.GetDirection(), sizedPrice, oNotionalPosition, cs, o) if err != nil { return nil, decimal.Zero, err } @@ -109,13 +120,15 @@ func (s *Size) calculateAmount(direction gctorder.Side, price, amountAvailable d return decimal.Zero, decimal.Zero, fmt.Errorf("%w at %v for %v %v %v, no amount sized", errCannotAllocate, o.GetTime(), o.GetExchange(), o.GetAssetType(), o.Pair()) } - if o.GetAmount().IsPositive() && o.GetAmount().LessThanOrEqual(amount) { - // when an order amount is already set + if o.GetAmount().IsPositive() { + // when an order amount is already set and still affordable // use the pre-set amount and calculate the fee - amount = o.GetAmount() - fee = o.GetAmount().Mul(price).Mul(cs.TakerFee) + if o.GetAmount().Mul(price).Add(o.GetAmount().Mul(price).Mul(cs.TakerFee)).LessThanOrEqual(amountAvailable) { + // TODO: introduce option to fail + cancel original order if this order pricing fails + amount = o.GetAmount() + fee = o.GetAmount().Mul(price).Mul(cs.TakerFee) + } } - return amount, fee, nil } diff --git a/backtester/eventhandlers/portfolio/size/size_test.go b/backtester/eventhandlers/portfolio/size/size_test.go index c41f616f..0e72e58c 100644 --- a/backtester/eventhandlers/portfolio/size/size_test.go +++ b/backtester/eventhandlers/portfolio/size/size_test.go @@ -1,20 +1,19 @@ package size import ( - "context" "errors" "testing" "time" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/ftx" + "github.com/thrasher-corp/gocryptotrader/exchanges/binance" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) @@ -33,8 +32,8 @@ func TestSizingAccuracy(t *testing.T) { feeRate := decimal.NewFromFloat(0.02) buyLimit := decimal.NewFromInt(1) amountWithoutFee, _, err := sizer.calculateBuySize(price, availableFunds, feeRate, buyLimit, globalMinMax) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } totalWithFee := (price.Mul(amountWithoutFee)).Add(globalMinMax.MaximumTotal.Mul(feeRate)) if !totalWithFee.Equal(globalMinMax.MaximumTotal) { @@ -57,8 +56,8 @@ func TestSizingOverMaxSize(t *testing.T) { feeRate := decimal.NewFromFloat(0.02) buyLimit := decimal.NewFromInt(1) amount, _, err := sizer.calculateBuySize(price, availableFunds, feeRate, buyLimit, globalMinMax) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if amount.GreaterThan(globalMinMax.MaximumSize) { t.Error("greater than max") @@ -173,8 +172,8 @@ func TestCalculateSellSize(t *testing.T) { price = decimal.NewFromInt(12) availableFunds = decimal.NewFromInt(1339) amount, fee, err := sizer.calculateSellSize(price, availableFunds, feeRate, sellLimit, globalMinMax) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !amount.Equal(sellLimit) { t.Errorf("received '%v' expected '%v'", amount, sellLimit) @@ -188,16 +187,16 @@ func TestSizeOrder(t *testing.T) { t.Parallel() s := Size{} _, _, err := s.SizeOrder(nil, decimal.Zero, nil) - if !errors.Is(err, common.ErrNilArguments) { + if !errors.Is(err, gctcommon.ErrNilPointer) { t.Error(err) } o := &order.Order{ Base: &event.Base{ Offset: 1, - Exchange: "ftx", + Exchange: "binance", Time: time.Now(), - CurrencyPair: currency.NewPair(currency.BTC, currency.USD), - UnderlyingPair: currency.NewPair(currency.BTC, currency.USD), + CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), + UnderlyingPair: currency.NewPair(currency.BTC, currency.USDT), AssetType: asset.Spot, }, } @@ -221,28 +220,28 @@ func TestSizeOrder(t *testing.T) { s.BuySide.MaximumSize = decimal.NewFromInt(1) s.BuySide.MinimumSize = decimal.NewFromInt(1) _, _, err = s.SizeOrder(o, decimal.NewFromInt(1337), cs) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } o.Amount = decimal.NewFromInt(1) o.Direction = gctorder.Sell _, _, err = s.SizeOrder(o, decimal.NewFromInt(1337), cs) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } s.SellSide.MaximumSize = decimal.NewFromInt(1) s.SellSide.MinimumSize = decimal.NewFromInt(1) _, _, err = s.SizeOrder(o, decimal.NewFromInt(1337), cs) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } o.Direction = gctorder.ClosePosition _, _, err = s.SizeOrder(o, decimal.NewFromInt(1337), cs) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } // spot futures sizing @@ -251,21 +250,18 @@ func TestSizeOrder(t *testing.T) { MatchesOrderAmount: true, ClosePrice: decimal.NewFromInt(1337), } - exch := ftx.FTX{} - err = exch.LoadCollateralWeightings(context.Background()) - if err != nil { - t.Error(err) - } + exch := binance.Binance{} + // TODO adjust when Binance futures wrappers are implemented cs.Exchange = &exch _, _, err = s.SizeOrder(o, decimal.NewFromInt(1337), cs) - if err != nil { - t.Error(err) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) } o.ClosePrice = decimal.NewFromInt(1000000000) o.Amount = decimal.NewFromInt(1000000000) _, _, err = s.SizeOrder(o, decimal.NewFromInt(1337), cs) - if !errors.Is(err, errCannotAllocate) { - t.Errorf("received: %v, expected: %v", err, errCannotAllocate) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNotYetImplemented) } } diff --git a/backtester/eventhandlers/statistics/common.go b/backtester/eventhandlers/statistics/common.go index 28208b3d..b925c3e8 100644 --- a/backtester/eventhandlers/statistics/common.go +++ b/backtester/eventhandlers/statistics/common.go @@ -7,6 +7,7 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" gctmath "github.com/thrasher-corp/gocryptotrader/common/math" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/log" @@ -19,7 +20,7 @@ func fSIL(str string, limit int) string { } // CalculateBiggestEventDrawdown calculates the biggest drawdown using a slice of DataEvents -func CalculateBiggestEventDrawdown(closePrices []common.DataEventHandler) (Swing, error) { +func CalculateBiggestEventDrawdown(closePrices []data.Event) (Swing, error) { if len(closePrices) == 0 { return Swing{}, fmt.Errorf("%w to calculate drawdowns", errReceivedNoData) } @@ -249,7 +250,7 @@ func CalculateRatios(benchmarkRates, returnsPerCandle []decimal.Decimal, riskFre } arithmeticCalmar, err = gctmath.DecimalCalmarRatio(maxDrawdown.Highest.Value, maxDrawdown.Lowest.Value, arithmeticReturnsPerCandle, riskFreeRateForPeriod) if err != nil { - return nil, nil, err + log.Warnf(common.Statistics, "%s funding arithmetic calmar ratio %v", logMessage, err) } arithmeticStats = &Ratios{} @@ -284,7 +285,7 @@ func CalculateRatios(benchmarkRates, returnsPerCandle []decimal.Decimal, riskFre } geomCalmar, err = gctmath.DecimalCalmarRatio(maxDrawdown.Highest.Value, maxDrawdown.Lowest.Value, geometricReturnsPerCandle, riskFreeRateForPeriod) if err != nil { - return nil, nil, err + log.Warnf(common.Statistics, "%s funding geometric calmar ratio %v", logMessage, err) } geometricStats = &Ratios{} if !arithmeticSharpe.IsZero() { diff --git a/backtester/eventhandlers/statistics/currencystatistics.go b/backtester/eventhandlers/statistics/currencystatistics.go index 45b46d95..45bfb6c7 100644 --- a/backtester/eventhandlers/statistics/currencystatistics.go +++ b/backtester/eventhandlers/statistics/currencystatistics.go @@ -1,10 +1,11 @@ package statistics import ( + "errors" "fmt" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" gctcommon "github.com/thrasher-corp/gocryptotrader/common" gctmath "github.com/thrasher-corp/gocryptotrader/common/math" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -20,22 +21,20 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e firstPrice := first.ClosePrice last := c.Events[len(c.Events)-1] + if last.ComplianceSnapshot == nil { + return errMissingSnapshots + } lastPrice := last.ClosePrice - for i := range last.Transactions.Orders { - switch last.Transactions.Orders[i].Order.Side { - case gctorder.Buy, gctorder.Bid: + for i := range last.ComplianceSnapshot.Orders { + if last.ComplianceSnapshot.Orders[i].Order.Side.IsLong() { c.BuyOrders++ - case gctorder.Sell, gctorder.Ask: + } else { c.SellOrders++ - case gctorder.Long: - c.LongOrders++ - case gctorder.Short: - c.ShortOrders++ } } for i := range c.Events { price := c.Events[i].ClosePrice - if price.LessThan(c.LowestClosePrice.Value) || !c.LowestClosePrice.Set { + if (price.LessThan(c.LowestClosePrice.Value) || !c.LowestClosePrice.Set) && !price.IsZero() { c.LowestClosePrice.Value = price c.LowestClosePrice.Time = c.Events[i].Time c.LowestClosePrice.Set = true @@ -51,7 +50,7 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e if !firstPrice.IsZero() { c.MarketMovement = lastPrice.Sub(firstPrice).Div(firstPrice).Mul(oneHundred) } - if first.Holdings.TotalValue.GreaterThan(decimal.Zero) { + if !first.Holdings.TotalValue.IsZero() { c.StrategyMovement = last.Holdings.TotalValue.Sub(first.Holdings.TotalValue).Div(first.Holdings.TotalValue).Mul(oneHundred) } c.analysePNLGrowth() @@ -62,7 +61,7 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e returnsPerCandle := make([]decimal.Decimal, len(c.Events)) benchmarkRates := make([]decimal.Decimal, len(c.Events)) - allDataEvents := make([]common.DataEventHandler, len(c.Events)) + allDataEvents := make([]data.Event, len(c.Events)) for i := range c.Events { returnsPerCandle[i] = c.Events[i].Holdings.ChangeInTotalValuePercent allDataEvents[i] = c.Events[i].DataEvent @@ -109,7 +108,7 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e decimal.NewFromFloat(intervalsPerYear), decimal.NewFromInt(int64(len(c.Events))), ) - if err != nil { + if err != nil && !errors.Is(err, gctmath.ErrPowerDifferenceTooSmall) { errs = append(errs, err) } c.CompoundAnnualGrowthRate = cagr diff --git a/backtester/eventhandlers/statistics/currencystatistics_test.go b/backtester/eventhandlers/statistics/currencystatistics_test.go index 9970dea5..d6ad2e62 100644 --- a/backtester/eventhandlers/statistics/currencystatistics_test.go +++ b/backtester/eventhandlers/statistics/currencystatistics_test.go @@ -45,7 +45,7 @@ func TestCalculateResults(t *testing.T) { Timestamp: tt1, QuoteInitialFunds: decimal.NewFromInt(1337), }, - Transactions: compliance.Snapshot{ + ComplianceSnapshot: &compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ { ClosePrice: decimal.NewFromInt(1338), @@ -88,7 +88,7 @@ func TestCalculateResults(t *testing.T) { Timestamp: tt2, QuoteInitialFunds: decimal.NewFromInt(1337), }, - Transactions: compliance.Snapshot{ + ComplianceSnapshot: &compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ { ClosePrice: decimal.NewFromInt(1338), @@ -123,8 +123,8 @@ func TestCalculateResults(t *testing.T) { cs.Events = append(cs.Events, ev, ev2) err := cs.CalculateResults(decimal.NewFromFloat(0.03)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !cs.MarketMovement.Equal(decimal.NewFromFloat(-33.15)) { t.Errorf("expected -33.15 received '%v'", cs.MarketMovement) @@ -143,16 +143,16 @@ func TestCalculateResults(t *testing.T) { Base: even2, } err = cs.CalculateResults(decimal.NewFromFloat(0.03)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } cs.Events[1].DataEvent = &kline.Kline{ Base: even2, } err = cs.CalculateResults(decimal.NewFromFloat(0.03)) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -176,7 +176,7 @@ func TestPrintResults(t *testing.T) { Timestamp: tt1, QuoteInitialFunds: decimal.NewFromInt(1337), }, - Transactions: compliance.Snapshot{ + ComplianceSnapshot: &compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ { ClosePrice: decimal.NewFromInt(1338), @@ -215,7 +215,7 @@ func TestPrintResults(t *testing.T) { Timestamp: tt2, QuoteInitialFunds: decimal.NewFromInt(1337), }, - Transactions: compliance.Snapshot{ + ComplianceSnapshot: &compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ { ClosePrice: decimal.NewFromInt(1338), @@ -311,9 +311,8 @@ func TestAnalysePNLGrowth(t *testing.T) { c.Events = append(c.Events, DataAtOffset{PNL: &portfolio.PNLSummary{ Exchange: e, - Item: a, + Asset: a, Pair: p, - Offset: 0, Result: order.PNLResult{ Time: time.Now(), UnrealisedPNL: decimal.NewFromInt(1), @@ -333,9 +332,8 @@ func TestAnalysePNLGrowth(t *testing.T) { c.Events = append(c.Events, DataAtOffset{PNL: &portfolio.PNLSummary{ Exchange: e, - Item: a, + Asset: a, Pair: p, - Offset: 0, Result: order.PNLResult{ Time: time.Now(), UnrealisedPNL: decimal.NewFromFloat(0.5), diff --git a/backtester/eventhandlers/statistics/fundingstatistics.go b/backtester/eventhandlers/statistics/fundingstatistics.go index 5392bd5b..c4474316 100644 --- a/backtester/eventhandlers/statistics/fundingstatistics.go +++ b/backtester/eventhandlers/statistics/fundingstatistics.go @@ -1,12 +1,13 @@ package statistics import ( + "errors" "fmt" "sort" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" gctmath "github.com/thrasher-corp/gocryptotrader/common/math" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -15,33 +16,47 @@ import ( // CalculateFundingStatistics calculates funding statistics for total USD strategy results // along with individual funding item statistics -func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic, riskFreeRate decimal.Decimal, interval gctkline.Interval) (*FundingStatistics, error) { +func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic, riskFreeRate decimal.Decimal, interval gctkline.Interval) (*FundingStatistics, error) { if currStats == nil { - return nil, common.ErrNilArguments + return nil, gctcommon.ErrNilPointer + } + report, err := funds.GenerateReport() + if err != nil { + return nil, err + } + if report == nil { + return nil, errReceivedNoData } - report := funds.GenerateReport() response := &FundingStatistics{ Report: report, } for i := range report.Items { exchangeAssetStats, ok := currStats[report.Items[i].Exchange][report.Items[i].Asset] if !ok { + if report.Items[i].AppendedViaAPI { + // items added via API may not have been processed along with typical events + // are not relevant to calculating statistics + continue + } return nil, fmt.Errorf("%w for %v %v", errNoRelevantStatsFound, report.Items[i].Exchange, report.Items[i].Asset) } var relevantStats []relatedCurrencyPairStatistics - for k, v := range exchangeAssetStats { - if k.Base.Equal(report.Items[i].Currency) { - relevantStats = append(relevantStats, relatedCurrencyPairStatistics{isBaseCurrency: true, stat: v}) - continue - } - if k.Quote.Equal(report.Items[i].Currency) { - relevantStats = append(relevantStats, relatedCurrencyPairStatistics{stat: v}) + for b, baseMap := range exchangeAssetStats { + for q, v := range baseMap { + if b.Currency().Equal(report.Items[i].Currency) { + relevantStats = append(relevantStats, relatedCurrencyPairStatistics{isBaseCurrency: true, stat: v}) + continue + } + if q.Currency().Equal(report.Items[i].Currency) { + relevantStats = append(relevantStats, relatedCurrencyPairStatistics{stat: v}) + } } } - fundingStat, err := CalculateIndividualFundingStatistics(report.DisableUSDTracking, &report.Items[i], relevantStats) + var fundingStat *FundingItemStatistics + fundingStat, err = CalculateIndividualFundingStatistics(report.DisableUSDTracking, &report.Items[i], relevantStats) if err != nil { return nil, err } @@ -79,12 +94,6 @@ func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[str return nil, fmt.Errorf("%w and holding values", errMissingSnapshots) } - if !usdStats.HoldingValues[0].Value.IsZero() { - usdStats.StrategyMovement = usdStats.HoldingValues[len(usdStats.HoldingValues)-1].Value.Sub( - usdStats.HoldingValues[0].Value).Div( - usdStats.HoldingValues[0].Value).Mul( - decimal.NewFromInt(100)) - } usdStats.HoldingValueDifference = report.FinalFunds.Sub(report.InitialFunds).Div(report.InitialFunds).Mul(decimal.NewFromInt(100)) riskFreeRatePerCandle := usdStats.RiskFreeRate.Div(decimal.NewFromFloat(interval.IntervalsPerYear())) @@ -101,8 +110,9 @@ func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[str } benchmarkRates = benchmarkRates[1:] returnsPerCandle = returnsPerCandle[1:] - usdStats.BenchmarkMarketMovement = benchmarkMovement.Sub(usdStats.HoldingValues[0].Value).Div(usdStats.HoldingValues[0].Value).Mul(decimal.NewFromInt(100)) - var err error + if !usdStats.HoldingValues[0].Value.IsZero() { + usdStats.BenchmarkMarketMovement = benchmarkMovement.Sub(usdStats.HoldingValues[0].Value).Div(usdStats.HoldingValues[0].Value).Mul(decimal.NewFromInt(100)) + } usdStats.MaxDrawdown, err = CalculateBiggestValueAtTimeDrawdown(usdStats.HoldingValues, interval) if err != nil { return nil, err @@ -114,8 +124,8 @@ func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[str return nil, err } + var cagr decimal.Decimal for i := range response.Items { - var cagr decimal.Decimal if response.Items[i].ReportItem.InitialFunds.IsZero() { continue } @@ -125,26 +135,25 @@ func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[str decimal.NewFromFloat(interval.IntervalsPerYear()), decimal.NewFromInt(int64(len(usdStats.HoldingValues))), ) - if err != nil { + if err != nil && !errors.Is(err, gctmath.ErrPowerDifferenceTooSmall) { return nil, err } response.Items[i].CompoundAnnualGrowthRate = cagr } if !usdStats.HoldingValues[0].Value.IsZero() { - var cagr decimal.Decimal cagr, err = gctmath.DecimalCompoundAnnualGrowthRate( usdStats.HoldingValues[0].Value, usdStats.HoldingValues[len(usdStats.HoldingValues)-1].Value, decimal.NewFromFloat(interval.IntervalsPerYear()), decimal.NewFromInt(int64(len(usdStats.HoldingValues))), ) - if err != nil { + if err != nil && !errors.Is(err, gctmath.ErrPowerDifferenceTooSmall) { return nil, err } usdStats.CompoundAnnualGrowthRate = cagr } - usdStats.DidStrategyMakeProfit = usdStats.HoldingValues[len(usdStats.HoldingValues)-1].Value.GreaterThan(usdStats.HoldingValues[0].Value) - usdStats.DidStrategyBeatTheMarket = usdStats.StrategyMovement.GreaterThan(usdStats.BenchmarkMarketMovement) + usdStats.DidStrategyMakeProfit = report.FinalFunds.GreaterThan(report.InitialFunds) + usdStats.DidStrategyBeatTheMarket = usdStats.HoldingValueDifference.GreaterThan(usdStats.BenchmarkMarketMovement) response.TotalUSDStatistics = usdStats return response, nil @@ -153,15 +162,15 @@ func CalculateFundingStatistics(funds funding.IFundingManager, currStats map[str // CalculateIndividualFundingStatistics calculates statistics for an individual report item func CalculateIndividualFundingStatistics(disableUSDTracking bool, reportItem *funding.ReportItem, relatedStats []relatedCurrencyPairStatistics) (*FundingItemStatistics, error) { if reportItem == nil { - return nil, fmt.Errorf("%w - nil report item", common.ErrNilArguments) + return nil, fmt.Errorf("%w - nil report item", gctcommon.ErrNilPointer) } + item := &FundingItemStatistics{ ReportItem: reportItem, } - if disableUSDTracking { + if disableUSDTracking || reportItem.AppendedViaAPI { return item, nil } - closePrices := reportItem.Snapshots if len(closePrices) == 0 { return nil, errMissingSnapshots @@ -175,7 +184,7 @@ func CalculateIndividualFundingStatistics(disableUSDTracking bool, reportItem *f Value: closePrices[len(closePrices)-1].USDClosePrice, } for i := range closePrices { - if closePrices[i].USDClosePrice.LessThan(item.LowestClosePrice.Value) || !item.LowestClosePrice.Set { + if (closePrices[i].USDClosePrice.LessThan(item.LowestClosePrice.Value) || !item.LowestClosePrice.Set) && !closePrices[i].USDClosePrice.IsZero() { item.LowestClosePrice.Value = closePrices[i].USDClosePrice item.LowestClosePrice.Time = closePrices[i].Time item.LowestClosePrice.Set = true @@ -220,7 +229,7 @@ func CalculateIndividualFundingStatistics(disableUSDTracking bool, reportItem *f if !reportItem.IsCollateral { for i := range relatedStats { if relatedStats[i].stat == nil { - return nil, fmt.Errorf("%w related stats", common.ErrNilArguments) + return nil, fmt.Errorf("%w related stats", gctcommon.ErrNilPointer) } if relatedStats[i].isBaseCurrency { item.BuyOrders += relatedStats[i].stat.BuyOrders @@ -262,14 +271,16 @@ func CalculateIndividualFundingStatistics(disableUSDTracking bool, reportItem *f if item.ReportItem.USDPairCandle == nil && !reportItem.IsCollateral { return nil, fmt.Errorf("%w usd candles missing", errMissingSnapshots) } - s := item.ReportItem.USDPairCandle.GetStream() + s, err := item.ReportItem.USDPairCandle.GetStream() + if err != nil { + return nil, err + } if len(s) == 0 { return nil, fmt.Errorf("%w stream missing", errMissingSnapshots) } if reportItem.IsCollateral { return item, nil } - var err error item.MaxDrawdown, err = CalculateBiggestEventDrawdown(s) return item, err } diff --git a/backtester/eventhandlers/statistics/fundingstatistics_test.go b/backtester/eventhandlers/statistics/fundingstatistics_test.go index 3b0641a7..1890f0a9 100644 --- a/backtester/eventhandlers/statistics/fundingstatistics_test.go +++ b/backtester/eventhandlers/statistics/fundingstatistics_test.go @@ -5,10 +5,12 @@ import ( "testing" "time" + "github.com/gofrs/uuid" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -18,10 +20,10 @@ import ( func TestCalculateFundingStatistics(t *testing.T) { t.Parallel() _, err := CalculateFundingStatistics(nil, nil, decimal.Zero, gctkline.OneHour) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received %v expected %v", err, common.ErrNilArguments) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received %v expected %v", err, common.ErrNilPointer) } - f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, true) + f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, true, false) if !errors.Is(err, nil) { t.Errorf("received %v expected %v", err, nil) } @@ -44,8 +46,8 @@ func TestCalculateFundingStatistics(t *testing.T) { } _, err = CalculateFundingStatistics(f, nil, decimal.Zero, gctkline.OneHour) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received %v expected %v", err, common.ErrNilArguments) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received %v expected %v", err, common.ErrNilPointer) } usdKline := gctkline.Item{ @@ -63,6 +65,7 @@ func TestCalculateFundingStatistics(t *testing.T) { }, } dfk := &kline.DataFromKline{ + Base: &data.Base{}, Item: usdKline, } err = dfk.Load() @@ -74,13 +77,13 @@ func TestCalculateFundingStatistics(t *testing.T) { t.Errorf("received %v expected %v", err, funding.ErrUSDTrackingDisabled) } - cs := make(map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) + cs := make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) _, err = CalculateFundingStatistics(f, cs, decimal.Zero, gctkline.OneHour) if !errors.Is(err, errNoRelevantStatsFound) { t.Errorf("received %v expected %v", err, errNoRelevantStatsFound) } - f, err = funding.SetupFundingManager(&engine.ExchangeManager{}, true, false) + f, err = funding.SetupFundingManager(&engine.ExchangeManager{}, true, false, false) if !errors.Is(err, nil) { t.Errorf("received %v expected %v", err, nil) } @@ -96,16 +99,24 @@ func TestCalculateFundingStatistics(t *testing.T) { if !errors.Is(err, nil) { t.Errorf("received %v expected %v", err, nil) } - cs["binance"] = make(map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) - cs["binance"][asset.Spot] = make(map[currency.Pair]*CurrencyPairStatistic) - cs["binance"][asset.Spot][currency.NewPair(currency.LTC, currency.USD)] = &CurrencyPairStatistic{} + cs["binance"] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) + cs["binance"][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) + cs["binance"][asset.Spot][currency.LTC.Item] = make(map[*currency.Item]*CurrencyPairStatistic) + cs["binance"][asset.Spot][currency.LTC.Item][currency.USD.Item] = &CurrencyPairStatistic{} _, err = CalculateFundingStatistics(f, cs, decimal.Zero, gctkline.OneHour) if !errors.Is(err, errMissingSnapshots) { t.Errorf("received %v expected %v", err, errMissingSnapshots) } - f.CreateSnapshot(usdKline.Candles[0].Time) - f.CreateSnapshot(usdKline.Candles[1].Time) - cs["binance"][asset.Spot][currency.NewPair(currency.BTC, currency.USDT)] = &CurrencyPairStatistic{} + err = f.CreateSnapshot(usdKline.Candles[0].Time) + if !errors.Is(err, nil) { + t.Errorf("received %v expected %v", err, nil) + } + err = f.CreateSnapshot(usdKline.Candles[1].Time) + if !errors.Is(err, nil) { + t.Errorf("received %v expected %v", err, nil) + } + cs["binance"][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*CurrencyPairStatistic) + cs["binance"][asset.Spot][currency.BTC.Item][currency.USDT.Item] = &CurrencyPairStatistic{} _, err = CalculateFundingStatistics(f, cs, decimal.Zero, gctkline.OneHour) if !errors.Is(err, nil) { @@ -115,8 +126,8 @@ func TestCalculateFundingStatistics(t *testing.T) { func TestCalculateIndividualFundingStatistics(t *testing.T) { _, err := CalculateIndividualFundingStatistics(true, nil, nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received %v expected %v", err, common.ErrNilArguments) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received %v expected %v", err, common.ErrNilPointer) } _, err = CalculateIndividualFundingStatistics(true, &funding.ReportItem{}, nil) @@ -148,8 +159,8 @@ func TestCalculateIndividualFundingStatistics(t *testing.T) { }, } _, err = CalculateIndividualFundingStatistics(false, ri, rs) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received %v expected %v", err, common.ErrNilArguments) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received %v expected %v", err, common.ErrNilPointer) } rs[0].stat = &CurrencyPairStatistic{} @@ -159,10 +170,15 @@ func TestCalculateIndividualFundingStatistics(t *testing.T) { if !errors.Is(err, errMissingSnapshots) { t.Errorf("received %v expected %v", err, errMissingSnapshots) } - + cp := currency.NewPair(currency.BTC, currency.USD) ri.USDPairCandle = &kline.DataFromKline{ + Base: &data.Base{}, Item: gctkline.Item{ - Interval: gctkline.OneHour, + Exchange: testExchange, + Pair: cp, + UnderlyingPair: cp, + Asset: asset.Spot, + Interval: gctkline.OneHour, Candles: []gctkline.Candle{ { Time: time.Now().Add(-time.Hour), @@ -171,6 +187,8 @@ func TestCalculateIndividualFundingStatistics(t *testing.T) { Time: time.Now(), }, }, + SourceJobID: uuid.UUID{}, + ValidationJobID: uuid.UUID{}, }, } err = ri.USDPairCandle.Load() @@ -198,11 +216,11 @@ func TestCalculateIndividualFundingStatistics(t *testing.T) { func TestFundingStatisticsPrintResults(t *testing.T) { f := FundingStatistics{} err := f.PrintResults(false) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received %v expected %v", err, common.ErrNilArguments) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received %v expected %v", err, common.ErrNilPointer) } - funds, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, true) + funds, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, true, false) if !errors.Is(err, nil) { t.Errorf("received %v expected %v", err, nil) } @@ -222,7 +240,10 @@ func TestFundingStatisticsPrintResults(t *testing.T) { if !errors.Is(err, nil) { t.Errorf("received %v expected %v", err, nil) } - f.Report = funds.GenerateReport() + f.Report, err = funds.GenerateReport() + if !errors.Is(err, nil) { + t.Errorf("received %v expected %v", err, nil) + } err = f.PrintResults(false) if !errors.Is(err, nil) { t.Errorf("received %v expected %v", err, nil) @@ -231,8 +252,8 @@ func TestFundingStatisticsPrintResults(t *testing.T) { f.TotalUSDStatistics = &TotalFundingStatistics{} f.Report.DisableUSDTracking = false err = f.PrintResults(false) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received %v expected %v", err, common.ErrNilArguments) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received %v expected %v", err, common.ErrNilPointer) } f.TotalUSDStatistics = &TotalFundingStatistics{ diff --git a/backtester/eventhandlers/statistics/printresults.go b/backtester/eventhandlers/statistics/printresults.go index e9813c88..7952f406 100644 --- a/backtester/eventhandlers/statistics/printresults.go +++ b/backtester/eventhandlers/statistics/printresults.go @@ -3,10 +3,14 @@ package statistics import ( "fmt" "sort" + "time" "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + data2 "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" @@ -66,80 +70,41 @@ func (s *Statistic) PrintTotalResults() { // rather than separated by exchange, asset and currency pair, it's // grouped by time to allow a clearer picture of events func (s *Statistic) PrintAllEventsChronologically() { - var results []eventOutputHolder log.Info(common.Statistics, common.CMDColours.H1+"------------------Events-------------------------------------"+common.CMDColours.Default) var errs gctcommon.Errors - colour := common.CMDColours.Default - for exch, x := range s.ExchangeAssetPairStatistics { - for a, y := range x { - for pair, currencyStatistic := range y { - for i := range currencyStatistic.Events { - switch { - case currencyStatistic.Events[i].FillEvent != nil: - direction := currencyStatistic.Events[i].FillEvent.GetDirection() - if direction == order.CouldNotBuy || - direction == order.CouldNotSell || - direction == order.MissingData || - direction == order.DoNothing || - direction == order.TransferredFunds || - direction == order.UnknownSide { - if direction == order.DoNothing { - colour = common.CMDColours.DarkGrey + var err error + var results []eventOutputHolder + for _, exchangeMap := range s.ExchangeAssetPairStatistics { + for _, assetMap := range exchangeMap { + for _, baseMap := range assetMap { + for _, currencyStatistic := range baseMap { + for i := range currencyStatistic.Events { + var result string + var tt time.Time + switch { + case currencyStatistic.Events[i].FillEvent != nil: + result, err = s.CreateLog(currencyStatistic.Events[i].FillEvent) + if err != nil { + errs = append(errs, err) + continue } - msg := fmt.Sprintf(colour+ - "%v %v%v%v| Price: %v\tDirection: %v", - currencyStatistic.Events[i].FillEvent.GetTime().Format(gctcommon.SimpleTimeFormat), - fSIL(exch, limit12), - fSIL(a.String(), limit10), - fSIL(currencyStatistic.Events[i].FillEvent.Pair().String(), limit14), - currencyStatistic.Events[i].FillEvent.GetClosePrice().Round(8), - currencyStatistic.Events[i].FillEvent.GetDirection()) - msg = addReason(currencyStatistic.Events[i].FillEvent.GetConcatReasons(), msg) - msg += common.CMDColours.Default - results = addEventOutputToTime(results, currencyStatistic.Events[i].FillEvent.GetTime(), msg) - } else { - // successful order! - colour = common.CMDColours.Success - if currencyStatistic.Events[i].FillEvent.IsLiquidated() { - colour = common.CMDColours.Error + tt = currencyStatistic.Events[i].FillEvent.GetTime() + case currencyStatistic.Events[i].SignalEvent != nil: + result, err = s.CreateLog(currencyStatistic.Events[i].SignalEvent) + if err != nil { + errs = append(errs, err) + continue } - msg := fmt.Sprintf(colour+ - "%v %v%v%v| Price: %v\tDirection %v\tOrder placed: Amount: %v\tFee: %v\tTotal: %v", - currencyStatistic.Events[i].FillEvent.GetTime().Format(gctcommon.SimpleTimeFormat), - fSIL(exch, limit12), - fSIL(a.String(), limit10), - fSIL(currencyStatistic.Events[i].FillEvent.Pair().String(), limit14), - currencyStatistic.Events[i].FillEvent.GetPurchasePrice().Round(8), - currencyStatistic.Events[i].FillEvent.GetDirection(), - currencyStatistic.Events[i].FillEvent.GetAmount().Round(8), - currencyStatistic.Events[i].FillEvent.GetExchangeFee(), - currencyStatistic.Events[i].FillEvent.GetTotal().Round(8)) - msg = addReason(currencyStatistic.Events[i].FillEvent.GetConcatReasons(), msg) - msg += common.CMDColours.Default - results = addEventOutputToTime(results, currencyStatistic.Events[i].FillEvent.GetTime(), msg) + tt = currencyStatistic.Events[i].SignalEvent.GetTime() + case currencyStatistic.Events[i].DataEvent != nil: + result, err = s.CreateLog(currencyStatistic.Events[i].DataEvent) + if err != nil { + errs = append(errs, err) + continue + } + tt = currencyStatistic.Events[i].DataEvent.GetTime() } - case currencyStatistic.Events[i].SignalEvent != nil: - msg := fmt.Sprintf("%v %v%v%v| Price: $%v", - currencyStatistic.Events[i].SignalEvent.GetTime().Format(gctcommon.SimpleTimeFormat), - fSIL(exch, limit12), - fSIL(a.String(), limit10), - fSIL(currencyStatistic.Events[i].SignalEvent.Pair().String(), limit14), - currencyStatistic.Events[i].SignalEvent.GetClosePrice().Round(8)) - msg = addReason(currencyStatistic.Events[i].SignalEvent.GetConcatReasons(), msg) - msg += common.CMDColours.Default - results = addEventOutputToTime(results, currencyStatistic.Events[i].SignalEvent.GetTime(), msg) - case currencyStatistic.Events[i].DataEvent != nil: - msg := fmt.Sprintf("%v %v%v%v| Price: $%v", - currencyStatistic.Events[i].DataEvent.GetTime().Format(gctcommon.SimpleTimeFormat), - fSIL(exch, limit12), - fSIL(a.String(), limit10), - fSIL(currencyStatistic.Events[i].DataEvent.Pair().String(), limit14), - currencyStatistic.Events[i].DataEvent.GetClosePrice().Round(8)) - msg = addReason(currencyStatistic.Events[i].DataEvent.GetConcatReasons(), msg) - msg += common.CMDColours.Default - results = addEventOutputToTime(results, currencyStatistic.Events[i].DataEvent.GetTime(), msg) - default: - errs = append(errs, fmt.Errorf(common.CMDColours.Error+"%v%v%v unexpected data received %+v"+common.CMDColours.Default, exch, a, fSIL(pair.String(), limit14), currencyStatistic.Events[i])) + results = addEventOutputToTime(results, tt, result) } } } @@ -164,6 +129,81 @@ func (s *Statistic) PrintAllEventsChronologically() { } } +// CreateLog renders a string log depending on what events are populated +// at a given offset. Can render logs live, or at the end of a backtesting run +func (s *Statistic) CreateLog(data common.Event) (string, error) { + var ( + result string + colour = common.CMDColours.Default + ) + switch ev := data.(type) { + case fill.Event: + direction := ev.GetDirection() + if direction == order.CouldNotBuy || + direction == order.CouldNotSell || + direction == order.CouldNotLong || + direction == order.CouldNotShort || + direction == order.MissingData || + direction == order.DoNothing || + direction == order.TransferredFunds || + direction == order.UnknownSide { + if direction == order.DoNothing { + colour = common.CMDColours.DarkGrey + } + result = fmt.Sprintf(colour+ + "%v %v%v%v| Price: %v\tDirection: %v", + ev.GetTime().Format(gctcommon.SimpleTimeFormat), + fSIL(ev.GetExchange(), limit12), + fSIL(ev.GetAssetType().String(), limit10), + fSIL(ev.Pair().String(), limit14), + ev.GetClosePrice().Round(8), + ev.GetDirection()) + result = addReason(ev.GetConcatReasons(), result) + result += common.CMDColours.Default + } else { + // successful order! + colour = common.CMDColours.Success + if ev.IsLiquidated() { + colour = common.CMDColours.Error + } + result = fmt.Sprintf(colour+ + "%v %v%v%v| Price: %v\tDirection %v\tOrder placed: Amount: %v\tFee: %v\tTotal: %v", + ev.GetTime().Format(gctcommon.SimpleTimeFormat), + fSIL(ev.GetExchange(), limit12), + fSIL(ev.GetAssetType().String(), limit10), + fSIL(ev.Pair().String(), limit14), + ev.GetPurchasePrice().Round(8), + ev.GetDirection(), + ev.GetAmount().Round(8), + ev.GetExchangeFee(), + ev.GetTotal().Round(8)) + result = addReason(ev.GetConcatReasons(), result) + result += common.CMDColours.Default + } + case signal.Event: + result = fmt.Sprintf("%v %v%v%v| Price: $%v", + ev.GetTime().Format(gctcommon.SimpleTimeFormat), + fSIL(ev.GetExchange(), limit12), + fSIL(ev.GetAssetType().String(), limit10), + fSIL(ev.Pair().String(), limit14), + ev.GetClosePrice().Round(8)) + result = addReason(ev.GetConcatReasons(), result) + result += common.CMDColours.Default + case data2.Event: + result = fmt.Sprintf("%v %v%v%v| Price: $%v", + ev.GetTime().Format(gctcommon.SimpleTimeFormat), + fSIL(ev.GetExchange(), limit12), + fSIL(ev.GetAssetType().String(), limit10), + fSIL(ev.Pair().String(), limit14), + ev.GetClosePrice().Round(8)) + result = addReason(ev.GetConcatReasons(), result) + result += common.CMDColours.Default + default: + return "", fmt.Errorf(common.CMDColours.Error+"unexpected data received %T %+v"+common.CMDColours.Default, data, data) + } + return result, nil +} + // PrintResults outputs all calculated statistics to the command line func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency.Pair, usingExchangeLevelFunding bool) { var errs gctcommon.Errors @@ -176,14 +216,14 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency. c.StartingClosePrice.Time = first.Time c.EndingClosePrice.Value = last.DataEvent.GetClosePrice() c.EndingClosePrice.Time = last.Time - c.TotalOrders = c.BuyOrders + c.SellOrders + c.ShortOrders + c.LongOrders + c.TotalOrders = c.BuyOrders + c.SellOrders last.Holdings.TotalValueLost = last.Holdings.TotalValueLostToSlippage.Add(last.Holdings.TotalValueLostToVolumeSizing) sep := fmt.Sprintf("%v %v %v |\t", fSIL(e, limit12), fSIL(a.String(), limit10), fSIL(p.String(), limit14)) currStr := fmt.Sprintf(common.CMDColours.H1+"------------------Stats for %v %v %v------------------------------------------------------"+common.CMDColours.Default, e, a, p) log.Infof(common.CurrencyStatistics, currStr[:70]) if a.IsFutures() { - log.Infof(common.CurrencyStatistics, "%s Long orders: %s", sep, convert.IntToHumanFriendlyString(c.LongOrders, ",")) - log.Infof(common.CurrencyStatistics, "%s Short orders: %s", sep, convert.IntToHumanFriendlyString(c.ShortOrders, ",")) + log.Infof(common.CurrencyStatistics, "%s Long orders: %s", sep, convert.IntToHumanFriendlyString(c.BuyOrders, ",")) + log.Infof(common.CurrencyStatistics, "%s Short orders: %s", sep, convert.IntToHumanFriendlyString(c.SellOrders, ",")) log.Infof(common.CurrencyStatistics, "%s Highest Unrealised PNL: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.HighestUnrealisedPNL.Value, 8, ".", ","), c.HighestUnrealisedPNL.Time) log.Infof(common.CurrencyStatistics, "%s Lowest Unrealised PNL: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.LowestUnrealisedPNL.Value, 8, ".", ","), c.LowestUnrealisedPNL.Time) log.Infof(common.CurrencyStatistics, "%s Highest Realised PNL: %s at %v", sep, convert.DecimalToHumanFriendlyString(c.HighestRealisedPNL.Value, 8, ".", ","), c.HighestRealisedPNL.Time) @@ -205,7 +245,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency. log.Infof(common.CurrencyStatistics, "%s Calculated Drawdown: %s%%", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.DrawdownPercent, 8, ".", ",")) log.Infof(common.CurrencyStatistics, "%s Difference: %s", sep, convert.DecimalToHumanFriendlyString(c.MaxDrawdown.Highest.Value.Sub(c.MaxDrawdown.Lowest.Value), 2, ".", ",")) log.Infof(common.CurrencyStatistics, "%s Drawdown length: %s", sep, convert.IntToHumanFriendlyString(c.MaxDrawdown.IntervalDuration, ",")) - if !usingExchangeLevelFunding { + if !usingExchangeLevelFunding && c.TotalOrders > 1 { log.Info(common.CurrencyStatistics, common.CMDColours.H2+"------------------Ratios------------------------------------------------"+common.CMDColours.Default) log.Info(common.CurrencyStatistics, common.CMDColours.H3+"------------------Rates-------------------------------------------------"+common.CMDColours.Default) log.Infof(common.CurrencyStatistics, "%s Compound Annual Growth Rate: %s", sep, convert.DecimalToHumanFriendlyString(c.CompoundAnnualGrowthRate, 2, ".", ",")) @@ -273,7 +313,7 @@ func (c *CurrencyPairStatistic) PrintResults(e string, a asset.Item, p currency. // PrintResults outputs all calculated funding statistics to the command line func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error { if f.Report == nil { - return fmt.Errorf("%w requires report to be generated", common.ErrNilArguments) + return fmt.Errorf("%w requires report to be generated", gctcommon.ErrNilPointer) } var spotResults, futuresResults []FundingItemStatistics for i := range f.Items { @@ -289,6 +329,9 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error { if len(spotResults) > 0 { log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Spot Item Results------------------"+common.CMDColours.Default) for i := range spotResults { + if spotResults[i].ReportItem.AppendedViaAPI { + continue + } sep := fmt.Sprintf("%v%v%v| ", fSIL(spotResults[i].ReportItem.Exchange, limit12), fSIL(spotResults[i].ReportItem.Asset.String(), limit10), fSIL(spotResults[i].ReportItem.Currency.String(), limit14)) if !spotResults[i].ReportItem.PairedWith.IsEmpty() { log.Infof(common.FundingStatistics, "%s Paired with: %v", sep, spotResults[i].ReportItem.PairedWith) @@ -316,6 +359,9 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error { if len(futuresResults) > 0 { log.Info(common.FundingStatistics, common.CMDColours.H2+"------------------Funding Futures Item Results---------------"+common.CMDColours.Default) for i := range futuresResults { + if futuresResults[i].ReportItem.AppendedViaAPI { + continue + } sep := fmt.Sprintf("%v%v%v| ", fSIL(futuresResults[i].ReportItem.Exchange, limit12), fSIL(futuresResults[i].ReportItem.Asset.String(), limit10), fSIL(futuresResults[i].ReportItem.Currency.String(), limit14)) log.Infof(common.FundingStatistics, "%s Is Collateral: %v", sep, futuresResults[i].IsCollateral) if futuresResults[i].IsCollateral { @@ -346,7 +392,7 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error { log.Infof(common.FundingStatistics, "%s Initial value: $%s", sep, convert.DecimalToHumanFriendlyString(f.Report.InitialFunds, 8, ".", ",")) log.Infof(common.FundingStatistics, "%s Final value: $%s", sep, convert.DecimalToHumanFriendlyString(f.Report.FinalFunds, 8, ".", ",")) log.Infof(common.FundingStatistics, "%s Benchmark Market Movement: %s%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.BenchmarkMarketMovement, 8, ".", ",")) - log.Infof(common.FundingStatistics, "%s Strategy Movement: %s%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.StrategyMovement, 8, ".", ",")) + log.Infof(common.FundingStatistics, "%s Strategy Movement: %s%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.HoldingValueDifference, 8, ".", ",")) log.Infof(common.FundingStatistics, "%s Did strategy make a profit: %v", sep, f.TotalUSDStatistics.DidStrategyMakeProfit) log.Infof(common.FundingStatistics, "%s Did strategy beat the benchmark: %v", sep, f.TotalUSDStatistics.DidStrategyBeatTheMarket) log.Infof(common.FundingStatistics, "%s Highest funds: $%s at %v", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.HighestHoldingValue.Value, 8, ".", ","), f.TotalUSDStatistics.HighestHoldingValue.Time) @@ -357,7 +403,7 @@ func (f *FundingStatistics) PrintResults(wasAnyDataMissing bool) error { log.Infof(common.FundingStatistics, "%s Risk free rate: %s%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.RiskFreeRate.Mul(decimal.NewFromInt(100)), 2, ".", ",")) log.Infof(common.FundingStatistics, "%s Compound Annual Growth Rate: %v%%", sep, convert.DecimalToHumanFriendlyString(f.TotalUSDStatistics.CompoundAnnualGrowthRate, 8, ".", ",")) if f.TotalUSDStatistics.ArithmeticRatios == nil || f.TotalUSDStatistics.GeometricRatios == nil { - return fmt.Errorf("%w missing ratio calculations", common.ErrNilArguments) + return fmt.Errorf("%w missing ratio calculations", gctcommon.ErrNilPointer) } log.Info(common.FundingStatistics, common.CMDColours.H4+"------------------Arithmetic--------------------------------------------"+common.CMDColours.Default) if wasAnyDataMissing { diff --git a/backtester/eventhandlers/statistics/statistics.go b/backtester/eventhandlers/statistics/statistics.go index 5b74cb02..f76311dd 100644 --- a/backtester/eventhandlers/statistics/statistics.go +++ b/backtester/eventhandlers/statistics/statistics.go @@ -5,112 +5,138 @@ import ( "fmt" "time" + "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/log" ) // Reset returns the struct to defaults -func (s *Statistic) Reset() { - *s = Statistic{} +func (s *Statistic) Reset() error { + if s == nil { + return gctcommon.ErrNilPointer + } + s.StrategyName = "" + s.StrategyDescription = "" + s.StrategyNickname = "" + s.StrategyGoal = "" + s.StartDate = time.Time{} + s.EndDate = time.Time{} + s.CandleInterval = 0 + s.RiskFreeRate = decimal.Zero + s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) + s.CurrencyStatistics = nil + s.TotalBuyOrders = 0 + s.TotalLongOrders = 0 + s.TotalShortOrders = 0 + s.TotalSellOrders = 0 + s.TotalOrders = 0 + s.BiggestDrawdown = nil + s.BestStrategyResults = nil + s.BestMarketMovement = nil + s.WasAnyDataMissing = false + s.FundingStatistics = nil + s.FundManager = nil + s.HasCollateral = false + return nil } -// SetupEventForTime sets up the big map for to store important data at each time interval -func (s *Statistic) SetupEventForTime(ev common.DataEventHandler) error { +// SetEventForOffset sets up the big map for to store important data at each time interval +func (s *Statistic) SetEventForOffset(ev common.Event) error { if ev == nil { return common.ErrNilEvent } + if ev.GetBase() == nil { + return fmt.Errorf("%w event base", common.ErrNilEvent) + } ex := ev.GetExchange() a := ev.GetAssetType() p := ev.Pair() - s.setupMap(ex, a) - lookup := s.ExchangeAssetPairStatistics[ex][a][p] - if lookup == nil { + if s.ExchangeAssetPairStatistics == nil { + s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) + } + m, ok := s.ExchangeAssetPairStatistics[ex] + if !ok { + m = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) + s.ExchangeAssetPairStatistics[ex] = m + } + m2, ok := m[a] + if !ok { + m2 = make(map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) + m[a] = m2 + } + m3, ok := m2[p.Base.Item] + if !ok { + m3 = make(map[*currency.Item]*CurrencyPairStatistic) + m2[p.Base.Item] = m3 + } + lookup, ok := m3[p.Quote.Item] + if !ok { lookup = &CurrencyPairStatistic{ Exchange: ev.GetExchange(), Asset: ev.GetAssetType(), Currency: ev.Pair(), UnderlyingPair: ev.GetUnderlyingPair(), } + m3[p.Quote.Item] = lookup } for i := range lookup.Events { - if lookup.Events[i].Offset == ev.GetOffset() { - return ErrAlreadyProcessed + if lookup.Events[i].Offset != ev.GetOffset() { + continue } + return applyEventAtOffset(ev, &lookup.Events[i]) } - lookup.Events = append(lookup.Events, - DataAtOffset{ - DataEvent: ev, - Offset: ev.GetOffset(), - Time: ev.GetTime(), - }, - ) - s.ExchangeAssetPairStatistics[ex][a][p] = lookup + // add to events and then apply the supplied event to it + lookup.Events = append(lookup.Events, DataAtOffset{ + Offset: ev.GetOffset(), + Time: ev.GetTime(), + }) + err := applyEventAtOffset(ev, &lookup.Events[len(lookup.Events)-1]) + if err != nil { + return err + } return nil } -func (s *Statistic) setupMap(ex string, a asset.Item) { - if s.ExchangeAssetPairStatistics == nil { - s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) - } - if s.ExchangeAssetPairStatistics[ex] == nil { - s.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) - } - if s.ExchangeAssetPairStatistics[ex][a] == nil { - s.ExchangeAssetPairStatistics[ex][a] = make(map[currency.Pair]*CurrencyPairStatistic) - } -} - -// SetEventForOffset sets the event for the time period in the event -func (s *Statistic) SetEventForOffset(ev common.EventHandler) error { - if ev == nil { - return common.ErrNilEvent - } - if s.ExchangeAssetPairStatistics == nil { - return errExchangeAssetPairStatsUnset - } - exch := ev.GetExchange() - a := ev.GetAssetType() - p := ev.Pair() - offset := ev.GetOffset() - lookup := s.ExchangeAssetPairStatistics[exch][a][p] - if lookup == nil { - return fmt.Errorf("%w for %v %v %v to set signal event", errCurrencyStatisticsUnset, exch, a, p) - } - for i := len(lookup.Events) - 1; i >= 0; i-- { - if lookup.Events[i].Offset == offset { - return applyEventAtOffset(ev, lookup, i) - } - } - - return fmt.Errorf("%w for event %v %v %v at offset %v", errNoRelevantStatsFound, exch, a, p, ev.GetOffset()) -} - -func applyEventAtOffset(ev common.EventHandler, lookup *CurrencyPairStatistic, i int) error { +func applyEventAtOffset(ev common.Event, data *DataAtOffset) error { switch t := ev.(type) { - case common.DataEventHandler: - lookup.Events[i].DataEvent = t + case kline.Event: + // using kline.Event as signal.Event also matches data.Event + if data.DataEvent != nil && data.DataEvent != ev { + return fmt.Errorf("kline event %w %v %v %v %v", ErrAlreadyProcessed, ev.GetExchange(), ev.GetAssetType(), ev.Pair(), ev.GetOffset()) + } + data.DataEvent = t case signal.Event: - lookup.Events[i].SignalEvent = t + if data.SignalEvent != nil { + return fmt.Errorf("signal event %w %v %v %v %v", ErrAlreadyProcessed, ev.GetExchange(), ev.GetAssetType(), ev.Pair(), ev.GetOffset()) + } + data.SignalEvent = t case order.Event: - lookup.Events[i].OrderEvent = t + if data.OrderEvent != nil { + return fmt.Errorf("order event %w %v %v %v %v", ErrAlreadyProcessed, ev.GetExchange(), ev.GetAssetType(), ev.Pair(), ev.GetOffset()) + } + data.OrderEvent = t case fill.Event: - lookup.Events[i].FillEvent = t + if data.FillEvent != nil { + return fmt.Errorf("fill event %w %v %v %v %v", ErrAlreadyProcessed, ev.GetExchange(), ev.GetAssetType(), ev.Pair(), ev.GetOffset()) + } + data.FillEvent = t default: return fmt.Errorf("unknown event type received: %v", ev) } - lookup.Events[i].Time = ev.GetTime() - lookup.Events[i].ClosePrice = ev.GetClosePrice() - lookup.Events[i].Offset = ev.GetOffset() + data.Time = ev.GetTime() + data.ClosePrice = ev.GetClosePrice() return nil } @@ -120,7 +146,7 @@ func (s *Statistic) AddHoldingsForTime(h *holdings.Holding) error { if s.ExchangeAssetPairStatistics == nil { return errExchangeAssetPairStatsUnset } - lookup := s.ExchangeAssetPairStatistics[h.Exchange][h.Asset][h.Pair] + lookup := s.ExchangeAssetPairStatistics[h.Exchange][h.Asset][h.Pair.Base.Item][h.Pair.Quote.Item] if lookup == nil { return fmt.Errorf("%w for %v %v %v to set holding event", errCurrencyStatisticsUnset, h.Exchange, h.Asset, h.Pair) } @@ -136,14 +162,14 @@ func (s *Statistic) AddHoldingsForTime(h *holdings.Holding) error { // AddPNLForTime stores PNL data for tracking purposes func (s *Statistic) AddPNLForTime(pnl *portfolio.PNLSummary) error { if pnl == nil { - return fmt.Errorf("%w requires PNL", common.ErrNilArguments) + return fmt.Errorf("%w requires PNL", gctcommon.ErrNilPointer) } if s.ExchangeAssetPairStatistics == nil { return errExchangeAssetPairStatsUnset } - lookup := s.ExchangeAssetPairStatistics[pnl.Exchange][pnl.Item][pnl.Pair] + lookup := s.ExchangeAssetPairStatistics[pnl.Exchange][pnl.Asset][pnl.Pair.Base.Item][pnl.Pair.Quote.Item] if lookup == nil { - return fmt.Errorf("%w for %v %v %v to set pnl", errCurrencyStatisticsUnset, pnl.Exchange, pnl.Item, pnl.Pair) + return fmt.Errorf("%w for %v %v %v to set pnl", errCurrencyStatisticsUnset, pnl.Exchange, pnl.Asset, pnl.Pair) } for i := len(lookup.Events) - 1; i >= 0; i-- { if lookup.Events[i].Offset == pnl.Offset { @@ -152,13 +178,16 @@ func (s *Statistic) AddPNLForTime(pnl *portfolio.PNLSummary) error { return nil } } - return fmt.Errorf("%v %v %v %w %v", pnl.Exchange, pnl.Item, pnl.Pair, errNoDataAtOffset, pnl.Offset) + return fmt.Errorf("%v %v %v %w %v", pnl.Exchange, pnl.Asset, pnl.Pair, errNoDataAtOffset, pnl.Offset) } // AddComplianceSnapshotForTime adds the compliance snapshot to the statistics at the time period -func (s *Statistic) AddComplianceSnapshotForTime(c compliance.Snapshot, e fill.Event) error { +func (s *Statistic) AddComplianceSnapshotForTime(c *compliance.Snapshot, e common.Event) error { + if c == nil { + return fmt.Errorf("%w compliance snapshot", common.ErrNilEvent) + } if e == nil { - return common.ErrNilEvent + return fmt.Errorf("%w fill event", common.ErrNilEvent) } if s.ExchangeAssetPairStatistics == nil { return errExchangeAssetPairStatsUnset @@ -166,13 +195,13 @@ func (s *Statistic) AddComplianceSnapshotForTime(c compliance.Snapshot, e fill.E exch := e.GetExchange() a := e.GetAssetType() p := e.Pair() - lookup := s.ExchangeAssetPairStatistics[exch][a][p] + lookup := s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item] if lookup == nil { return fmt.Errorf("%w for %v %v %v to set compliance snapshot", errCurrencyStatisticsUnset, exch, a, p) } for i := len(lookup.Events) - 1; i >= 0; i-- { if lookup.Events[i].Offset == e.GetOffset() { - lookup.Events[i].Transactions = c + lookup.Events[i].ComplianceSnapshot = c return nil } } @@ -189,38 +218,47 @@ func (s *Statistic) CalculateAllResults() error { var err error for exchangeName, exchangeMap := range s.ExchangeAssetPairStatistics { for assetItem, assetMap := range exchangeMap { - for pair, stats := range assetMap { - currCount++ - last := stats.Events[len(stats.Events)-1] - if last.PNL != nil { - s.HasCollateral = true - } - err = stats.CalculateResults(s.RiskFreeRate) - if err != nil { - log.Error(common.Statistics, err) - } - stats.FinalHoldings = last.Holdings - stats.InitialHoldings = stats.Events[0].Holdings - stats.FinalOrders = last.Transactions - s.StartDate = stats.Events[0].Time - s.EndDate = last.Time - stats.PrintResults(exchangeName, assetItem, pair, s.FundManager.IsUsingExchangeLevelFunding()) + for b, baseMap := range assetMap { + for q, stats := range baseMap { + currCount++ + last := stats.Events[len(stats.Events)-1] + if last.PNL != nil { + s.HasCollateral = true + } + err = stats.CalculateResults(s.RiskFreeRate) + if err != nil { + log.Error(common.Statistics, err) + } + stats.FinalHoldings = last.Holdings + stats.InitialHoldings = stats.Events[0].Holdings + if last.ComplianceSnapshot == nil { + return errMissingSnapshots + } + stats.FinalOrders = *last.ComplianceSnapshot + s.StartDate = stats.Events[0].Time + s.EndDate = last.Time + cp := currency.NewPair(b.Currency(), q.Currency()) + stats.PrintResults(exchangeName, assetItem, cp, s.FundManager.IsUsingExchangeLevelFunding()) - finalResults = append(finalResults, FinalResultsHolder{ - Exchange: exchangeName, - Asset: assetItem, - Pair: pair, - MaxDrawdown: stats.MaxDrawdown, - MarketMovement: stats.MarketMovement, - StrategyMovement: stats.StrategyMovement, - }) - s.TotalLongOrders += stats.LongOrders - s.TotalShortOrders += stats.ShortOrders - s.TotalBuyOrders += stats.BuyOrders - s.TotalSellOrders += stats.SellOrders - s.TotalOrders += stats.TotalOrders - if stats.ShowMissingDataWarning { - s.WasAnyDataMissing = true + finalResults = append(finalResults, FinalResultsHolder{ + Exchange: exchangeName, + Asset: assetItem, + Pair: cp, + MaxDrawdown: stats.MaxDrawdown, + MarketMovement: stats.MarketMovement, + StrategyMovement: stats.StrategyMovement, + }) + if assetItem.IsFutures() { + s.TotalLongOrders += stats.BuyOrders + s.TotalShortOrders += stats.SellOrders + } else { + s.TotalBuyOrders += stats.BuyOrders + s.TotalSellOrders += stats.SellOrders + } + s.TotalOrders += stats.TotalOrders + if stats.ShowMissingDataWarning { + s.WasAnyDataMissing = true + } } } } @@ -300,8 +338,10 @@ func (s *Statistic) Serialise() (string, error) { s.CurrencyStatistics = nil for _, exchangeMap := range s.ExchangeAssetPairStatistics { for _, assetMap := range exchangeMap { - for _, stats := range assetMap { - s.CurrencyStatistics = append(s.CurrencyStatistics, stats) + for _, baseMap := range assetMap { + for _, stats := range baseMap { + s.CurrencyStatistics = append(s.CurrencyStatistics, stats) + } } } } diff --git a/backtester/eventhandlers/statistics/statistics_test.go b/backtester/eventhandlers/statistics/statistics_test.go index aadb871f..2b7b8142 100644 --- a/backtester/eventhandlers/statistics/statistics_test.go +++ b/backtester/eventhandlers/statistics/statistics_test.go @@ -7,6 +7,8 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" @@ -34,13 +36,22 @@ var ( func TestReset(t *testing.T) { t.Parallel() - s := Statistic{ + s := &Statistic{ TotalOrders: 1, } - s.Reset() + err := s.Reset() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if s.TotalOrders != 0 { t.Error("expected 0") } + + s = nil + err = s.Reset() + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) + } } func TestAddDataEventForTime(t *testing.T) { @@ -50,11 +61,11 @@ func TestAddDataEventForTime(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) s := Statistic{} - err := s.SetupEventForTime(nil) + err := s.SetEventForOffset(nil) if !errors.Is(err, common.ErrNilEvent) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt, @@ -68,13 +79,13 @@ func TestAddDataEventForTime(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if s.ExchangeAssetPairStatistics == nil { t.Error("expected not nil") } - if len(s.ExchangeAssetPairStatistics[exch][a][p].Events) != 1 { + if len(s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item].Events) != 1 { t.Error("expected 1 event") } } @@ -91,24 +102,23 @@ func TestAddSignalEventForTime(t *testing.T) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } err = s.SetEventForOffset(&signal.Signal{}) - if !errors.Is(err, errExchangeAssetPairStatsUnset) { - t.Errorf("received: %v, expected: %v", err, errExchangeAssetPairStatsUnset) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } - s.setupMap(exch, a) - s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) + s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) b := &event.Base{} err = s.SetEventForOffset(&signal.Signal{ Base: b, }) - if !errors.Is(err, errCurrencyStatisticsUnset) { - t.Errorf("received: %v, expected: %v", err, errCurrencyStatisticsUnset) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } b.Exchange = exch b.Time = tt b.Interval = gctkline.OneDay b.CurrencyPair = p b.AssetType = a - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: b, Open: eleet, Close: eleet, @@ -116,16 +126,16 @@ func TestAddSignalEventForTime(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&signal.Signal{ Base: b, ClosePrice: eleet, Direction: gctorder.Buy, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -141,24 +151,18 @@ func TestAddExchangeEventForTime(t *testing.T) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } err = s.SetEventForOffset(&order.Order{}) - if !errors.Is(err, errExchangeAssetPairStatsUnset) { - t.Errorf("received: %v, expected: %v", err, errExchangeAssetPairStatsUnset) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } - s.setupMap(exch, a) - s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) + s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) b := &event.Base{} - err = s.SetEventForOffset(&order.Order{ - Base: b, - }) - if !errors.Is(err, errCurrencyStatisticsUnset) { - t.Errorf("received: %v, expected: %v", err, errCurrencyStatisticsUnset) - } + b.Exchange = exch b.Time = tt b.Interval = gctkline.OneDay b.CurrencyPair = p b.AssetType = a - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: b, Open: eleet, Close: eleet, @@ -166,8 +170,8 @@ func TestAddExchangeEventForTime(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&order.Order{ Base: b, @@ -179,8 +183,8 @@ func TestAddExchangeEventForTime(t *testing.T) { OrderType: gctorder.Stop, Leverage: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -196,17 +200,16 @@ func TestAddFillEventForTime(t *testing.T) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } err = s.SetEventForOffset(&fill.Fill{}) - if err != nil && err.Error() != "exchangeAssetPairStatistics not setup" { - t.Error(err) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } - s.setupMap(exch, a) - s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) + s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) b := &event.Base{} err = s.SetEventForOffset(&fill.Fill{ Base: b, }) - if !errors.Is(err, errCurrencyStatisticsUnset) { - t.Errorf("received: %v, expected: %v", err, errCurrencyStatisticsUnset) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } b.Exchange = exch @@ -215,7 +218,7 @@ func TestAddFillEventForTime(t *testing.T) { b.CurrencyPair = p b.AssetType = a - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: b, Open: eleet, Close: eleet, @@ -223,8 +226,8 @@ func TestAddFillEventForTime(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&fill.Fill{ Base: b, @@ -236,8 +239,8 @@ func TestAddFillEventForTime(t *testing.T) { ExchangeFee: eleet, Slippage: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -252,13 +255,13 @@ func TestAddHoldingsForTime(t *testing.T) { if !errors.Is(err, errExchangeAssetPairStatsUnset) { t.Errorf("received: %v, expected: %v", err, errExchangeAssetPairStatsUnset) } - s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) + s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) err = s.AddHoldingsForTime(&holdings.Holding{}) if !errors.Is(err, errCurrencyStatisticsUnset) { t.Errorf("received: %v, expected: %v", err, errCurrencyStatisticsUnset) } - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt, @@ -272,8 +275,8 @@ func TestAddHoldingsForTime(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.AddHoldingsForTime(&holdings.Holding{ Pair: p, @@ -295,8 +298,8 @@ func TestAddHoldingsForTime(t *testing.T) { TotalValueLostToSlippage: eleet, TotalValueLost: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -308,18 +311,22 @@ func TestAddComplianceSnapshotForTime(t *testing.T) { p := currency.NewPair(currency.BTC, currency.USDT) s := Statistic{} - err := s.AddComplianceSnapshotForTime(compliance.Snapshot{}, nil) + err := s.AddComplianceSnapshotForTime(nil, nil) if !errors.Is(err, common.ErrNilEvent) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } - err = s.AddComplianceSnapshotForTime(compliance.Snapshot{}, &fill.Fill{}) + err = s.AddComplianceSnapshotForTime(nil, &fill.Fill{}) + if !errors.Is(err, common.ErrNilEvent) { + t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) + } + + err = s.AddComplianceSnapshotForTime(&compliance.Snapshot{}, &fill.Fill{}) if !errors.Is(err, errExchangeAssetPairStatsUnset) { t.Errorf("received: %v, expected: %v", err, errExchangeAssetPairStatsUnset) } - s.setupMap(exch, a) - s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic) + s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic) b := &event.Base{} - err = s.AddComplianceSnapshotForTime(compliance.Snapshot{}, &fill.Fill{Base: b}) + err = s.AddComplianceSnapshotForTime(&compliance.Snapshot{}, &fill.Fill{Base: b}) if !errors.Is(err, errCurrencyStatisticsUnset) { t.Errorf("received: %v, expected: %v", err, errCurrencyStatisticsUnset) } @@ -328,7 +335,7 @@ func TestAddComplianceSnapshotForTime(t *testing.T) { b.Interval = gctkline.OneDay b.CurrencyPair = p b.AssetType = a - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: b, Open: eleet, Close: eleet, @@ -336,16 +343,16 @@ func TestAddComplianceSnapshotForTime(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = s.AddComplianceSnapshotForTime(compliance.Snapshot{ + err = s.AddComplianceSnapshotForTime(&compliance.Snapshot{ Timestamp: tt, }, &fill.Fill{ Base: b, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -488,11 +495,11 @@ func TestPrintAllEventsChronologically(t *testing.T) { exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - err := s.SetupEventForTime(nil) + err := s.SetEventForOffset(nil) if !errors.Is(err, common.ErrNilEvent) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt, @@ -506,8 +513,8 @@ func TestPrintAllEventsChronologically(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&fill.Fill{ @@ -526,8 +533,8 @@ func TestPrintAllEventsChronologically(t *testing.T) { ExchangeFee: eleet, Slippage: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&signal.Signal{ @@ -541,8 +548,8 @@ func TestPrintAllEventsChronologically(t *testing.T) { ClosePrice: eleet, Direction: gctorder.Buy, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } s.PrintAllEventsChronologically() @@ -552,8 +559,8 @@ func TestCalculateTheResults(t *testing.T) { t.Parallel() s := Statistic{} err := s.CalculateAllResults() - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received: %v, expected: %v", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) } tt := time.Now().Add(-gctkline.OneDay.Duration() * 7) @@ -562,11 +569,11 @@ func TestCalculateTheResults(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) p2 := currency.NewPair(currency.XRP, currency.DOGE) - err = s.SetupEventForTime(nil) + err = s.SetEventForOffset(nil) if !errors.Is(err, common.ErrNilEvent) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt, @@ -581,8 +588,8 @@ func TestCalculateTheResults(t *testing.T) { High: eleet, Volume: eleet, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&signal.Signal{ Base: &event.Base{ @@ -600,10 +607,10 @@ func TestCalculateTheResults(t *testing.T) { Volume: eleet, Direction: gctorder.Buy, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt, @@ -618,8 +625,8 @@ func TestCalculateTheResults(t *testing.T) { High: eleeb, Volume: eleeb, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&signal.Signal{ @@ -638,11 +645,11 @@ func TestCalculateTheResults(t *testing.T) { Volume: eleet, Direction: gctorder.Buy, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt2, @@ -657,8 +664,8 @@ func TestCalculateTheResults(t *testing.T) { High: eleeb, Volume: eleeb, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } err = s.SetEventForOffset(&signal.Signal{ Base: &event.Base{ @@ -676,11 +683,11 @@ func TestCalculateTheResults(t *testing.T) { Volume: eleeb, Direction: gctorder.Buy, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = s.SetupEventForTime(&kline.Kline{ + err = s.SetEventForOffset(&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt2, @@ -695,10 +702,10 @@ func TestCalculateTheResults(t *testing.T) { High: eleeb, Volume: eleeb, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - err = s.SetEventForOffset(&signal.Signal{ + signal4 := &signal.Signal{ Base: &event.Base{ Exchange: exch, Time: tt2, @@ -713,17 +720,18 @@ func TestCalculateTheResults(t *testing.T) { ClosePrice: eleeb, Volume: eleeb, Direction: gctorder.Buy, - }) - if err != nil { - t.Error(err) + } + err = s.SetEventForOffset(signal4) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - s.ExchangeAssetPairStatistics[exch][a][p].Events[1].Holdings.QuoteInitialFunds = eleet - s.ExchangeAssetPairStatistics[exch][a][p].Events[1].Holdings.TotalValue = eleeet - s.ExchangeAssetPairStatistics[exch][a][p2].Events[1].Holdings.QuoteInitialFunds = eleet - s.ExchangeAssetPairStatistics[exch][a][p2].Events[1].Holdings.TotalValue = eleeet + s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item].Events[1].Holdings.QuoteInitialFunds = eleet + s.ExchangeAssetPairStatistics[exch][a][p.Base.Item][p.Quote.Item].Events[1].Holdings.TotalValue = eleeet + s.ExchangeAssetPairStatistics[exch][a][p2.Base.Item][p2.Quote.Item].Events[1].Holdings.QuoteInitialFunds = eleet + s.ExchangeAssetPairStatistics[exch][a][p2.Base.Item][p2.Quote.Item].Events[1].Holdings.TotalValue = eleeet - funds, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, false) + funds, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, false, false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -770,7 +778,7 @@ func TestCalculateTheResults(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, errMissingSnapshots) } - funds, err = funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) + funds, err = funding.SetupFundingManager(&engine.ExchangeManager{}, false, true, false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -784,6 +792,11 @@ func TestCalculateTheResults(t *testing.T) { } s.FundManager = funds err = s.CalculateAllResults() + if !errors.Is(err, errMissingSnapshots) { + t.Errorf("received '%v' expected '%v'", err, errMissingSnapshots) + } + + err = s.AddComplianceSnapshotForTime(&compliance.Snapshot{Timestamp: tt2}, signal4) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -794,7 +807,7 @@ func TestCalculateBiggestEventDrawdown(t *testing.T) { exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - var events []common.DataEventHandler + var events []data.Event for i := int64(0); i < 100; i++ { tt1 = tt1.Add(gctkline.OneDay.Duration()) even := &event.Base{ @@ -881,7 +894,7 @@ func TestCalculateBiggestEventDrawdown(t *testing.T) { } // bogus scenario - bogusEvent := []common.DataEventHandler{ + bogusEvent := []data.Event{ &kline.Kline{ Base: &event.Base{ Exchange: exch, @@ -911,3 +924,60 @@ func TestCalculateBiggestValueAtTimeDrawdown(t *testing.T) { t.Errorf("received %v expected %v", err, errReceivedNoData) } } + +func TestAddPNLForTime(t *testing.T) { + t.Parallel() + s := &Statistic{} + err := s.AddPNLForTime(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received %v expected %v", err, gctcommon.ErrNilPointer) + } + + sum := &portfolio.PNLSummary{} + err = s.AddPNLForTime(sum) + if !errors.Is(err, errExchangeAssetPairStatsUnset) { + t.Errorf("received %v expected %v", err, errExchangeAssetPairStatsUnset) + } + + tt := time.Now().Add(-gctkline.OneDay.Duration() * 7) + exch := testExchange + a := asset.Spot + p := currency.NewPair(currency.BTC, currency.USDT) + err = s.SetEventForOffset(&kline.Kline{ + Base: &event.Base{ + Exchange: exch, + Time: tt, + Interval: gctkline.OneDay, + CurrencyPair: p, + AssetType: a, + Offset: 1, + }, + Open: eleet, + Close: eleet, + Low: eleet, + High: eleet, + Volume: eleet, + }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + err = s.AddPNLForTime(sum) + if !errors.Is(err, errCurrencyStatisticsUnset) { + t.Errorf("received %v expected %v", err, errCurrencyStatisticsUnset) + } + + sum.Exchange = exch + sum.Asset = a + sum.Pair = p + err = s.AddPNLForTime(sum) + if !errors.Is(err, errNoDataAtOffset) { + t.Errorf("received %v expected %v", err, errNoDataAtOffset) + } + + sum.Offset = 1 + err = s.AddPNLForTime(sum) + if !errors.Is(err, nil) { + t.Errorf("received %v expected %v", err, nil) + } +} diff --git a/backtester/eventhandlers/statistics/statistics_types.go b/backtester/eventhandlers/statistics/statistics_types.go index 1003fbde..1464ee48 100644 --- a/backtester/eventhandlers/statistics/statistics_types.go +++ b/backtester/eventhandlers/statistics/statistics_types.go @@ -6,6 +6,7 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" @@ -33,28 +34,28 @@ var ( // Statistic holds all statistical information for a backtester run, from drawdowns to ratios. // Any currency specific information is handled in currencystatistics type Statistic struct { - StrategyName string `json:"strategy-name"` - StrategyDescription string `json:"strategy-description"` - StrategyNickname string `json:"strategy-nickname"` - StrategyGoal string `json:"strategy-goal"` - StartDate time.Time `json:"start-date"` - EndDate time.Time `json:"end-date"` - CandleInterval gctkline.Interval `json:"candle-interval"` - RiskFreeRate decimal.Decimal `json:"risk-free-rate"` - ExchangeAssetPairStatistics map[string]map[asset.Item]map[currency.Pair]*CurrencyPairStatistic `json:"-"` - CurrencyStatistics []*CurrencyPairStatistic `json:"currency-statistics"` - TotalBuyOrders int64 `json:"total-buy-orders"` - TotalLongOrders int64 `json:"total-long-orders"` - TotalShortOrders int64 `json:"total-short-orders"` - TotalSellOrders int64 `json:"total-sell-orders"` - TotalOrders int64 `json:"total-orders"` - BiggestDrawdown *FinalResultsHolder `json:"biggest-drawdown,omitempty"` - BestStrategyResults *FinalResultsHolder `json:"best-start-results,omitempty"` - BestMarketMovement *FinalResultsHolder `json:"best-market-movement,omitempty"` - WasAnyDataMissing bool `json:"was-any-data-missing"` - FundingStatistics *FundingStatistics `json:"funding-statistics"` - FundManager funding.IFundingManager `json:"-"` - HasCollateral bool `json:"has-collateral"` + StrategyName string `json:"strategy-name"` + StrategyDescription string `json:"strategy-description"` + StrategyNickname string `json:"strategy-nickname"` + StrategyGoal string `json:"strategy-goal"` + StartDate time.Time `json:"start-date"` + EndDate time.Time `json:"end-date"` + CandleInterval gctkline.Interval `json:"candle-interval"` + RiskFreeRate decimal.Decimal `json:"risk-free-rate"` + ExchangeAssetPairStatistics map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*CurrencyPairStatistic `json:"exchange-asset-pair-statistics"` + CurrencyStatistics []*CurrencyPairStatistic `json:"currency-statistics"` + TotalBuyOrders int64 `json:"total-buy-orders"` + TotalLongOrders int64 `json:"total-long-orders"` + TotalShortOrders int64 `json:"total-short-orders"` + TotalSellOrders int64 `json:"total-sell-orders"` + TotalOrders int64 `json:"total-orders"` + BiggestDrawdown *FinalResultsHolder `json:"biggest-drawdown,omitempty"` + BestStrategyResults *FinalResultsHolder `json:"best-start-results,omitempty"` + BestMarketMovement *FinalResultsHolder `json:"best-market-movement,omitempty"` + WasAnyDataMissing bool `json:"was-any-data-missing"` + FundingStatistics *FundingStatistics `json:"funding-statistics"` + FundManager funding.IFundingManager `json:"-"` + HasCollateral bool `json:"has-collateral"` } // FinalResultsHolder holds important stats about a currency's performance @@ -70,14 +71,14 @@ type FinalResultsHolder struct { // Handler interface details what a statistic is expected to do type Handler interface { SetStrategyName(string) - SetupEventForTime(common.DataEventHandler) error - SetEventForOffset(common.EventHandler) error + SetEventForOffset(common.Event) error AddHoldingsForTime(*holdings.Holding) error - AddComplianceSnapshotForTime(compliance.Snapshot, fill.Event) error + AddComplianceSnapshotForTime(*compliance.Snapshot, common.Event) error CalculateAllResults() error - Reset() + Reset() error Serialise() (string, error) AddPNLForTime(*portfolio.PNLSummary) error + CreateLog(common.Event) (string, error) } // Results holds some statistics on results @@ -122,16 +123,16 @@ type CurrencyStats interface { // DataAtOffset is used to hold all event information // at a time interval type DataAtOffset struct { - Offset int64 - ClosePrice decimal.Decimal - Time time.Time - Holdings holdings.Holding - Transactions compliance.Snapshot - DataEvent common.DataEventHandler - SignalEvent signal.Event - OrderEvent order.Event - FillEvent fill.Event - PNL portfolio.IPNL + Offset int64 + ClosePrice decimal.Decimal + Time time.Time + Holdings holdings.Holding + ComplianceSnapshot *compliance.Snapshot + DataEvent data.Event + SignalEvent signal.Event + OrderEvent order.Event + FillEvent fill.Event + PNL portfolio.IPNL } // CurrencyPairStatistic Holds all events and statistics relevant to an exchange, asset type and currency pair @@ -146,8 +147,6 @@ type CurrencyPairStatistic struct { DoesPerformanceBeatTheMarket bool `json:"does-performance-beat-the-market"` BuyOrders int64 `json:"buy-orders"` - LongOrders int64 `json:"long-orders"` - ShortOrders int64 `json:"short-orders"` SellOrders int64 `json:"sell-orders"` TotalOrders int64 `json:"total-orders"` @@ -254,7 +253,6 @@ type TotalFundingStatistics struct { HighestHoldingValue ValueAtTime `json:"highest-holding-value"` LowestHoldingValue ValueAtTime `json:"lowest-holding-value"` BenchmarkMarketMovement decimal.Decimal `json:"benchmark-market-movement"` - StrategyMovement decimal.Decimal `json:"strategy-movement"` RiskFreeRate decimal.Decimal `json:"risk-free-rate"` CompoundAnnualGrowthRate decimal.Decimal `json:"compound-annual-growth-rate"` MaxDrawdown Swing `json:"max-drawdown"` diff --git a/backtester/eventhandlers/strategies/base/base.go b/backtester/eventhandlers/strategies/base/base.go index f71c1996..c693cab3 100644 --- a/backtester/eventhandlers/strategies/base/base.go +++ b/backtester/eventhandlers/strategies/base/base.go @@ -3,21 +3,25 @@ package base import ( "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/data" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" ) // Strategy is base implementation of the Handler interface type Strategy struct { useSimultaneousProcessing bool - usingExchangeLevelFunding bool } // GetBaseData returns the non-interface version of the Handler func (s *Strategy) GetBaseData(d data.Handler) (signal.Signal, error) { if d == nil { - return signal.Signal{}, common.ErrNilArguments + return signal.Signal{}, gctcommon.ErrNilPointer + } + latest, err := d.Latest() + if err != nil { + return signal.Signal{}, err } - latest := d.Latest() if latest == nil { return signal.Signal{}, common.ErrNilEvent } @@ -40,12 +44,10 @@ func (s *Strategy) SetSimultaneousProcessing(b bool) { s.useSimultaneousProcessing = b } -// UsingExchangeLevelFunding returns whether funding is based on currency pairs or individual currencies at the exchange level -func (s *Strategy) UsingExchangeLevelFunding() bool { - return s.usingExchangeLevelFunding -} - -// SetExchangeLevelFunding sets whether funding is based on currency pairs or individual currencies at the exchange level -func (s *Strategy) SetExchangeLevelFunding(b bool) { - s.usingExchangeLevelFunding = b +// CloseAllPositions sends a closing signal to supported +// strategies, allowing them to sell off any positions held +// default use-case is for when a user closes the application when running +// a live strategy +func (s *Strategy) CloseAllPositions([]holdings.Holding, []data.Event) ([]signal.Event, error) { + return nil, gctcommon.ErrFunctionNotSupported } diff --git a/backtester/eventhandlers/strategies/base/base_test.go b/backtester/eventhandlers/strategies/base/base_test.go index 0dd9ca7a..c64fbb56 100644 --- a/backtester/eventhandlers/strategies/base/base_test.go +++ b/backtester/eventhandlers/strategies/base/base_test.go @@ -11,6 +11,7 @@ import ( datakline "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" @@ -20,11 +21,11 @@ func TestGetBase(t *testing.T) { t.Parallel() s := Strategy{} _, err := s.GetBaseData(nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received: %v, expected: %v", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received: %v, expected: %v", err, gctcommon.ErrNilPointer) } - _, err = s.GetBaseData(&datakline.DataFromKline{}) + _, err = s.GetBaseData(datakline.NewDataFromKline()) if !errors.Is(err, common.ErrNilEvent) { t.Errorf("received: %v, expected: %v", err, common.ErrNilEvent) } @@ -32,8 +33,8 @@ func TestGetBase(t *testing.T) { exch := "binance" a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := data.Base{} - d.SetStream([]common.DataEventHandler{&kline.Kline{ + d := &data.Base{} + err = d.SetStream([]data.Event{&kline.Kline{ Base: &event.Base{ Exchange: exch, Time: tt, @@ -47,15 +48,21 @@ func TestGetBase(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } - d.Next() + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } _, err = s.GetBaseData(&datakline.DataFromKline{ Item: gctkline.Item{}, Base: d, RangeHolder: &gctkline.IntervalRangeHolder{}, }) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } } @@ -73,26 +80,11 @@ func TestSetSimultaneousProcessing(t *testing.T) { } } -func TestUsingExchangeLevelFunding(t *testing.T) { +func TestCloseAllPositions(t *testing.T) { t.Parallel() s := &Strategy{} - if s.UsingExchangeLevelFunding() { - t.Error("expected false") - } - s.usingExchangeLevelFunding = true - if !s.UsingExchangeLevelFunding() { - t.Error("expected true") - } -} - -func TestSetExchangeLevelFunding(t *testing.T) { - t.Parallel() - s := &Strategy{} - s.SetExchangeLevelFunding(true) - if !s.UsingExchangeLevelFunding() { - t.Error("expected true") - } - if !s.UsingExchangeLevelFunding() { - t.Error("expected true") + _, err := s.CloseAllPositions(nil, nil) + if !errors.Is(err, gctcommon.ErrFunctionNotSupported) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrFunctionNotSupported) } } diff --git a/backtester/eventhandlers/strategies/base/base_types.go b/backtester/eventhandlers/strategies/base/base_types.go index 3026bad7..716b840f 100644 --- a/backtester/eventhandlers/strategies/base/base_types.go +++ b/backtester/eventhandlers/strategies/base/base_types.go @@ -16,4 +16,6 @@ var ( ErrInvalidCustomSettings = errors.New("invalid custom settings in config") // ErrTooMuchBadData used when there is too much missing data ErrTooMuchBadData = errors.New("backtesting cannot continue as there is too much invalid data. Please review your dataset") + // ErrNoDataToProcess is returned when simultaneous signal processing is enabled, but no events are passed in + ErrNoDataToProcess = errors.New("no kline data to process") ) diff --git a/backtester/eventhandlers/strategies/ftxcashandcarry/README.md b/backtester/eventhandlers/strategies/binancecashandcarry/README.md similarity index 87% rename from backtester/eventhandlers/strategies/ftxcashandcarry/README.md rename to backtester/eventhandlers/strategies/binancecashandcarry/README.md index 366ffd9e..9f74603e 100644 --- a/backtester/eventhandlers/strategies/ftxcashandcarry/README.md +++ b/backtester/eventhandlers/strategies/binancecashandcarry/README.md @@ -1,16 +1,16 @@ -# GoCryptoTrader Backtester: Ftxcashandcarry package +# GoCryptoTrader Backtester: Binancecashandcarry package [![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml) [![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/binancecashandcarry) [![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) -This ftxcashandcarry package is part of the GoCryptoTrader codebase. +This binancecashandcarry package is part of the GoCryptoTrader codebase. ## This is still in active development @@ -18,22 +18,25 @@ You can track ideas, planned features and what's in progress on this Trello boar Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) -## FTX Cash and carry strategy overview +## Binance Cash and carry strategy overview + +## Important +This strategy was initially designed for the exchange FTX. It is currently being ported to Binance. It does not work at present. ### Description Cash and carry is a strategy which takes advantage of the difference in pricing between a long-dated futures contract and a SPOT asset. -By default, this cash and carry strategy will, upon the first data event, purchase BTC-USD SPOT asset from FTX exchange and then, once filled, raise a SHORT for BTC-20210924 FUTURES contract. +By default, this cash and carry strategy will, upon the first data event, purchase BTC-USD SPOT asset from Binance exchange and then, once filled, raise a SHORT for BTC-20210924 FUTURES contract. On the last event, the strategy will close the SHORT position by raising a LONG of the same contract amount, thereby netting the difference in prices ### Requirements -- At this time of writing, this strategy is only compatible with FTX +- At this time of writing, this strategy is only compatible with Binance - This strategy *requires* `Simultaneous Signal Processing` aka [use-simultaneous-signal-processing](/backtester/config/README.md). - This strategy *requires* `Exchange Level Funding` aka [use-exchange-level-funding](/backtester/config/README.md). ### Creating a strategy config - The long-dated futures contract will need to be part of the `currency-settings` of the contract - Funding for purchasing SPOT assets will need to be part of `funding-settings` -- See the [example config](./config/examples/ftx-cash-carry.strat) +- See the [example config](./config/strategyexamples/binance-cash-carry.strat) ### Customisation This strategy does support strategy customisation in the following ways: diff --git a/backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry.go b/backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry.go similarity index 62% rename from backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry.go rename to backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry.go index bdb7cec0..c49f821e 100644 --- a/backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry.go +++ b/backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry.go @@ -1,17 +1,20 @@ -package ftxcashandcarry +package binancecashandcarry import ( "errors" "fmt" "strings" + "time" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -50,13 +53,13 @@ var errNotSetup = errors.New("sent incomplete signals") // in allowing a strategy to only place an order for X currency if Y currency's price is Z func (s *Strategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTransferer, p portfolio.Handler) ([]signal.Event, error) { if len(d) == 0 { - return nil, errNoSignals + return nil, base.ErrNoDataToProcess } if f == nil { - return nil, fmt.Errorf("%w missing funding transferred", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing funding transferred", gctcommon.ErrNilPointer) } if p == nil { - return nil, fmt.Errorf("%w missing portfolio handler", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing portfolio handler", gctcommon.ErrNilPointer) } var response []signal.Event sortedSignals, err := sortSignals(d) @@ -64,24 +67,35 @@ func (s *Strategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTra return nil, err } - for _, v := range sortedSignals { - pos, err := p.GetPositions(v.futureSignal.Latest()) + for i := range sortedSignals { + var latestSpot, latestFuture data.Event + latestSpot, err = sortedSignals[i].spotSignal.Latest() if err != nil { return nil, err } - spotSignal, err := s.GetBaseData(v.spotSignal) + latestFuture, err = sortedSignals[i].futureSignal.Latest() if err != nil { return nil, err } - futuresSignal, err := s.GetBaseData(v.futureSignal) + var pos []order.Position + pos, err = p.GetPositions(latestFuture) + if err != nil { + return nil, err + } + var spotSignal, futuresSignal signal.Signal + spotSignal, err = s.GetBaseData(sortedSignals[i].spotSignal) + if err != nil { + return nil, err + } + futuresSignal, err = s.GetBaseData(sortedSignals[i].futureSignal) if err != nil { return nil, err } spotSignal.SetDirection(order.DoNothing) futuresSignal.SetDirection(order.DoNothing) - fp := v.futureSignal.Latest().GetClosePrice() - sp := v.spotSignal.Latest().GetClosePrice() + fp := latestFuture.GetClosePrice() + sp := latestSpot.GetClosePrice() diffBetweenFuturesSpot := fp.Sub(sp).Div(sp).Mul(decimal.NewFromInt(100)) futuresSignal.AppendReasonf("Futures Spot Difference: %v%%", diffBetweenFuturesSpot) if len(pos) > 0 && pos[len(pos)-1].Status == order.Open { @@ -93,7 +107,13 @@ func (s *Strategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTra response = append(response, &spotSignal, &futuresSignal) continue } - signals, err := s.createSignals(pos, &spotSignal, &futuresSignal, diffBetweenFuturesSpot, v.futureSignal.IsLastEvent()) + var isLastEvent bool + var signals []signal.Event + isLastEvent, err = sortedSignals[i].futureSignal.IsLastEvent() + if err != nil { + return nil, err + } + signals, err = s.createSignals(pos, &spotSignal, &futuresSignal, diffBetweenFuturesSpot, isLastEvent) if err != nil { return nil, err } @@ -102,14 +122,57 @@ func (s *Strategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTra return response, nil } +// CloseAllPositions is this strategy's implementation on how to +// unwind all positions in the event of a closure +func (s *Strategy) CloseAllPositions(holdings []holdings.Holding, prices []data.Event) ([]signal.Event, error) { + var spotSignals, futureSignals []signal.Event + signalTime := time.Now().UTC() + for i := range holdings { + for j := range prices { + if prices[j].GetExchange() != holdings[i].Exchange || + prices[j].GetAssetType() != holdings[i].Asset || + !prices[j].Pair().Equal(holdings[i].Pair) { + continue + } + sig := &signal.Signal{ + Base: &event.Base{ + Offset: holdings[i].Offset + 1, + Exchange: holdings[i].Exchange, + Time: signalTime, + Interval: prices[j].GetInterval(), + CurrencyPair: holdings[i].Pair, + UnderlyingPair: prices[j].GetUnderlyingPair(), + AssetType: holdings[i].Asset, + Reasons: []string{"closing position on close"}, + }, + OpenPrice: prices[j].GetOpenPrice(), + HighPrice: prices[j].GetHighPrice(), + LowPrice: prices[j].GetLowPrice(), + ClosePrice: prices[j].GetClosePrice(), + Volume: prices[j].GetVolume(), + Amount: holdings[i].BaseSize, + Direction: order.ClosePosition, + CollateralCurrency: holdings[i].Pair.Base, + } + if prices[j].GetAssetType().IsFutures() { + futureSignals = append(futureSignals, sig) + } else { + spotSignals = append(spotSignals, sig) + } + } + } + // close out future positions first + return append(futureSignals, spotSignals...), nil +} + // createSignals creates signals based on the relationships between // futures and spot signals func (s *Strategy) createSignals(pos []order.Position, spotSignal, futuresSignal *signal.Signal, diffBetweenFuturesSpot decimal.Decimal, isLastEvent bool) ([]signal.Event, error) { if spotSignal == nil { - return nil, fmt.Errorf("%w missing spot signal", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing spot signal", gctcommon.ErrNilPointer) } if futuresSignal == nil { - return nil, fmt.Errorf("%w missing futures signal", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing futures signal", gctcommon.ErrNilPointer) } var response []signal.Event switch { @@ -159,42 +222,58 @@ func (s *Strategy) createSignals(pos []order.Position, spotSignal, futuresSignal // sortSignals links spot and futures signals in order to create cash // and carry signals -func sortSignals(d []data.Handler) (map[currency.Pair]cashCarrySignals, error) { +func sortSignals(d []data.Handler) ([]cashCarrySignals, error) { if len(d) == 0 { - return nil, errNoSignals + return nil, base.ErrNoDataToProcess } - var response = make(map[currency.Pair]cashCarrySignals, len(d)) + var carryMap = make(map[*currency.Item]map[*currency.Item]cashCarrySignals, len(d)) for i := range d { - l := d[i].Latest() + l, err := d[i].Latest() + if err != nil { + return nil, err + } if !strings.EqualFold(l.GetExchange(), exchangeName) { - return nil, fmt.Errorf("%w, received '%v'", errOnlyFTXSupported, l.GetExchange()) + return nil, fmt.Errorf("%w, received '%v'", errOnlyBinanceSupported, l.GetExchange()) } a := l.GetAssetType() switch { case a == asset.Spot: - entry := response[l.Pair().Format(currency.EMPTYFORMAT)] + b := carryMap[l.Pair().Base.Item] + if b == nil { + carryMap[l.Pair().Base.Item] = make(map[*currency.Item]cashCarrySignals) + } + entry := carryMap[l.Pair().Base.Item][l.Pair().Quote.Item] entry.spotSignal = d[i] - response[l.Pair().Format(currency.EMPTYFORMAT)] = entry + carryMap[l.Pair().Base.Item][l.Pair().Quote.Item] = entry case a.IsFutures(): u := l.GetUnderlyingPair() - entry := response[u.Format(currency.EMPTYFORMAT)] + b := carryMap[u.Base.Item] + if b == nil { + carryMap[u.Base.Item] = make(map[*currency.Item]cashCarrySignals) + } + entry := carryMap[u.Base.Item][u.Quote.Item] entry.futureSignal = d[i] - response[u.Format(currency.EMPTYFORMAT)] = entry + carryMap[u.Base.Item][u.Quote.Item] = entry default: return nil, errFuturesOnly } } + + var resp []cashCarrySignals // validate that each set of signals is matched - for _, v := range response { - if v.futureSignal == nil { - return nil, fmt.Errorf("%w missing future signal", errNotSetup) - } - if v.spotSignal == nil { - return nil, fmt.Errorf("%w missing spot signal", errNotSetup) + for _, b := range carryMap { + for _, v := range b { + if v.futureSignal == nil { + return nil, fmt.Errorf("%w missing future signal", errNotSetup) + } + if v.spotSignal == nil { + return nil, fmt.Errorf("%w missing spot signal", errNotSetup) + } + resp = append(resp, v) } } - return response, nil + return resp, nil } // SetCustomSettings can override default settings diff --git a/backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry_test.go b/backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry_test.go similarity index 72% rename from backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry_test.go rename to backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry_test.go index 21436a71..4ac5badd 100644 --- a/backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry_test.go +++ b/backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry_test.go @@ -1,4 +1,4 @@ -package ftxcashandcarry +package binancecashandcarry import ( "errors" @@ -10,17 +10,21 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/data" datakline "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" eventkline "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) +const testExchange = "binance" + func TestName(t *testing.T) { t.Parallel() d := Strategy{} @@ -49,8 +53,8 @@ func TestSetCustomSettings(t *testing.T) { t.Parallel() s := Strategy{} err := s.SetCustomSettings(nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } float14 := float64(14) mappalopalous := make(map[string]interface{}) @@ -58,8 +62,8 @@ func TestSetCustomSettings(t *testing.T) { mappalopalous[closeShortDistancePercentageString] = float14 err = s.SetCustomSettings(mappalopalous) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } mappalopalous[openShortDistancePercentageString] = "14" @@ -109,11 +113,11 @@ func TestSetDefaults(t *testing.T) { func TestSortSignals(t *testing.T) { t.Parallel() dInsert := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC) - exch := "ftx" + exch := testExchange a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := data.Base{} - d.SetStream([]common.DataEventHandler{&eventkline.Kline{ + d := &data.Base{} + err := d.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Exchange: exch, Time: dInsert, @@ -127,19 +131,25 @@ func TestSortSignals(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) - d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } da := &datakline.DataFromKline{ Item: gctkline.Item{}, Base: d, RangeHolder: &gctkline.IntervalRangeHolder{}, } - _, err := sortSignals([]data.Handler{da}) + _, err = sortSignals([]data.Handler{da}) if !errors.Is(err, errNotSetup) { t.Errorf("received: %v, expected: %v", err, errNotSetup) } - d2 := data.Base{} - d2.SetStream([]common.DataEventHandler{&eventkline.Kline{ + d2 := &data.Base{} + err = d2.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Exchange: exch, Time: dInsert, @@ -154,7 +164,13 @@ func TestSortSignals(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) - d2.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + _, err = d2.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } da2 := &datakline.DataFromKline{ Item: gctkline.Item{}, Base: d2, @@ -169,7 +185,7 @@ func TestSortSignals(t *testing.T) { func TestCreateSignals(t *testing.T) { t.Parallel() s := Strategy{} - var expectedError = common.ErrNilArguments + var expectedError = gctcommon.ErrNilPointer _, err := s.createSignals(nil, nil, nil, decimal.Zero, false) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v", err, expectedError) @@ -282,14 +298,14 @@ func TestCreateSignals(t *testing.T) { } } -// funderino overrides default implementation -type funderino struct { +// fakeFunds overrides default implementation +type fakeFunds struct { funding.FundManager hasBeenLiquidated bool } // HasExchangeBeenLiquidated overrides default implementation -func (f funderino) HasExchangeBeenLiquidated(_ common.EventHandler) bool { +func (f fakeFunds) HasExchangeBeenLiquidated(_ common.Event) bool { return f.hasBeenLiquidated } @@ -299,7 +315,7 @@ type portfolerino struct { } // GetPositions overrides default implementation -func (p portfolerino) GetPositions(common.EventHandler) ([]gctorder.Position, error) { +func (p portfolerino) GetPositions(common.Event) ([]gctorder.Position, error) { return []gctorder.Position{ { Exchange: exchangeName, @@ -314,16 +330,14 @@ func (p portfolerino) GetPositions(common.EventHandler) ([]gctorder.Position, er func TestOnSimultaneousSignals(t *testing.T) { t.Parallel() s := Strategy{} - var expectedError = errNoSignals _, err := s.OnSimultaneousSignals(nil, nil, nil) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, base.ErrNoDataToProcess) { + t.Errorf("received '%v' expected '%v", err, base.ErrNoDataToProcess) } - expectedError = common.ErrNilArguments cp := currency.NewPair(currency.BTC, currency.USD) d := &datakline.DataFromKline{ - Base: data.Base{}, + Base: &data.Base{}, Item: gctkline.Item{ Exchange: exchangeName, Asset: asset.Spot, @@ -332,7 +346,7 @@ func TestOnSimultaneousSignals(t *testing.T) { }, } tt := time.Now() - d.SetStream([]common.DataEventHandler{&eventkline.Kline{ + err = d.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Exchange: exchangeName, Time: tt, @@ -346,27 +360,32 @@ func TestOnSimultaneousSignals(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } - d.Next() signals := []data.Handler{ d, } - f := &funderino{} + f := &fakeFunds{} _, err = s.OnSimultaneousSignals(signals, f, nil) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v", err, gctcommon.ErrNilPointer) } p := &portfolerino{} - expectedError = errNotSetup _, err = s.OnSimultaneousSignals(signals, f, p) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, errNotSetup) { + t.Errorf("received '%v' expected '%v", err, errNotSetup) } - expectedError = nil d2 := &datakline.DataFromKline{ - Base: data.Base{}, + Base: &data.Base{}, Item: gctkline.Item{ Exchange: exchangeName, Asset: asset.Futures, @@ -374,7 +393,7 @@ func TestOnSimultaneousSignals(t *testing.T) { UnderlyingPair: cp, }, } - d2.SetStream([]common.DataEventHandler{&eventkline.Kline{ + err = d2.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Exchange: exchangeName, Time: tt, @@ -389,14 +408,21 @@ func TestOnSimultaneousSignals(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) - d2.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + + _, err = d2.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } signals = []data.Handler{ d, d2, } resp, err := s.OnSimultaneousSignals(signals, f, p) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) } if len(resp) != 2 { t.Errorf("received '%v' expected '%v", len(resp), 2) @@ -404,8 +430,8 @@ func TestOnSimultaneousSignals(t *testing.T) { f.hasBeenLiquidated = true resp, err = s.OnSimultaneousSignals(signals, f, p) - if !errors.Is(err, expectedError) { - t.Errorf("received '%v' expected '%v", err, expectedError) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) } if len(resp) != 2 { t.Fatalf("received '%v' expected '%v", len(resp), 2) @@ -414,3 +440,83 @@ func TestOnSimultaneousSignals(t *testing.T) { t.Errorf("received '%v' expected '%v", resp[0].GetDirection(), gctorder.DoNothing) } } + +func TestCloseAllPositions(t *testing.T) { + t.Parallel() + s := Strategy{} + _, err := s.CloseAllPositions(nil, nil) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + leet := decimal.NewFromInt(1337) + cp := currency.NewPair(currency.BTC, currency.USD) + h := []holdings.Holding{ + { + Offset: 1, + Item: cp.Base, + Pair: cp, + Asset: asset.Spot, + Exchange: testExchange, + }, + { + Offset: 1, + Item: cp.Base, + Pair: cp, + Asset: asset.Futures, + Exchange: testExchange, + }, + } + + p := []data.Event{ + &signal.Signal{ + Base: &event.Base{ + Offset: 1, + Exchange: testExchange, + Time: time.Now(), + Interval: gctkline.OneDay, + CurrencyPair: cp, + UnderlyingPair: cp, + AssetType: asset.Spot, + }, + OpenPrice: leet, + HighPrice: leet, + LowPrice: leet, + ClosePrice: leet, + Volume: leet, + BuyLimit: leet, + SellLimit: leet, + Amount: leet, + Direction: gctorder.Buy, + }, + &signal.Signal{ + Base: &event.Base{ + Offset: 1, + Exchange: testExchange, + Time: time.Now(), + Interval: gctkline.OneDay, + CurrencyPair: cp, + UnderlyingPair: cp, + AssetType: asset.Futures, + }, + OpenPrice: leet, + HighPrice: leet, + LowPrice: leet, + ClosePrice: leet, + Volume: leet, + BuyLimit: leet, + SellLimit: leet, + Amount: leet, + Direction: gctorder.Buy, + }, + } + positionsToClose, err := s.CloseAllPositions(h, p) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } + if len(positionsToClose) != 2 { + t.Errorf("received '%v' expected '%v", len(positionsToClose), 2) + } + if !positionsToClose[0].GetAssetType().IsFutures() { + t.Errorf("received '%v' expected '%v", positionsToClose[0].GetAssetType(), asset.Futures) + } +} diff --git a/backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry_types.go b/backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry_types.go similarity index 72% rename from backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry_types.go rename to backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry_types.go index 7eb0e4bb..3b6bfb8e 100644 --- a/backtester/eventhandlers/strategies/ftxcashandcarry/ftxcashandcarry_types.go +++ b/backtester/eventhandlers/strategies/binancecashandcarry/binancecashandcarry_types.go @@ -1,4 +1,4 @@ -package ftxcashandcarry +package binancecashandcarry import ( "errors" @@ -9,17 +9,16 @@ import ( const ( // Name is the strategy name - Name = "ftx-cash-carry" + Name = "binance-cash-carry" description = `A cash and carry trade (or basis trading) consists in taking advantage of the premium of a futures contract over the spot price. For example if Ethereum Futures are trading well above its Spot price (contango) you could perform an arbitrage and take advantage of this opportunity.` - exchangeName = "ftx" + exchangeName = "binance" openShortDistancePercentageString = "openShortDistancePercentage" closeShortDistancePercentageString = "closeShortDistancePercentage" ) var ( - errFuturesOnly = errors.New("can only work with futures") - errOnlyFTXSupported = errors.New("only FTX supported for this strategy") - errNoSignals = errors.New("no data signals to process") + errFuturesOnly = errors.New("can only work with futures") + errOnlyBinanceSupported = errors.New("only Binance supported for this strategy") ) // Strategy is an implementation of the Handler interface diff --git a/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage.go b/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage.go index 6103fe89..61490345 100644 --- a/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage.go +++ b/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage.go @@ -44,13 +44,21 @@ func (s *Strategy) OnSignal(d data.Handler, _ funding.IFundingTransferer, _ port return nil, err } - if !d.HasDataAtTime(d.Latest().GetTime()) { + latest, err := d.Latest() + if err != nil { + return nil, err + } + hasDataAtTime, err := d.HasDataAtTime(latest.GetTime()) + if err != nil { + return nil, err + } + if !hasDataAtTime { es.SetDirection(order.MissingData) - es.AppendReasonf("missing data at %v, cannot perform any actions", d.Latest().GetTime()) + es.AppendReasonf("missing data at %v, cannot perform any actions", latest.GetTime()) return &es, nil } - es.SetPrice(d.Latest().GetClosePrice()) + es.SetPrice(latest.GetClosePrice()) es.SetDirection(order.Buy) es.AppendReason("DCA purchases on every iteration") return &es, nil diff --git a/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage_test.go b/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage_test.go index 2a9d675e..8688f4de 100644 --- a/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage_test.go +++ b/backtester/eventhandlers/strategies/dollarcostaverage/dollarcostaverage_test.go @@ -54,8 +54,8 @@ func TestOnSignal(t *testing.T) { exch := "binance" a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := data.Base{} - d.SetStream([]common.DataEventHandler{&eventkline.Kline{ + d := &data.Base{} + err = d.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Exchange: exch, Time: dInsert, @@ -69,7 +69,13 @@ func TestOnSignal(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) - d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } da := &kline.DataFromKline{ Item: gctkline.Item{}, Base: d, @@ -77,8 +83,8 @@ func TestOnSignal(t *testing.T) { } var resp signal.Event resp, err = s.OnSignal(da, nil, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if resp.GetDirection() != gctorder.MissingData { t.Error("expected missing data") @@ -101,19 +107,19 @@ func TestOnSignal(t *testing.T) { }, } err = da.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } ranger, err := gctkline.CalculateCandleDateRanges(dStart, dEnd, gctkline.OneDay, 100000) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } da.RangeHolder = ranger da.RangeHolder.SetHasDataFromCandles(da.Item.Candles) resp, err = s.OnSignal(da, nil, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if resp.GetDirection() != gctorder.Buy { t.Errorf("expected buy, received %v", resp.GetDirection()) @@ -132,8 +138,8 @@ func TestOnSignals(t *testing.T) { exch := "binance" a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := data.Base{} - d.SetStream([]common.DataEventHandler{&eventkline.Kline{ + d := &data.Base{} + err = d.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Offset: 1, Exchange: exch, @@ -148,7 +154,13 @@ func TestOnSignals(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) - d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } da := &kline.DataFromKline{ Item: gctkline.Item{}, Base: d, @@ -156,8 +168,8 @@ func TestOnSignals(t *testing.T) { } var resp []signal.Event resp, err = s.OnSimultaneousSignals([]data.Handler{da}, nil, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(resp) != 1 { t.Fatal("expected 1 response") @@ -183,19 +195,19 @@ func TestOnSignals(t *testing.T) { }, } err = da.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } ranger, err := gctkline.CalculateCandleDateRanges(dStart, dEnd, gctkline.OneDay, 100000) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } da.RangeHolder = ranger da.RangeHolder.SetHasDataFromCandles(da.Item.Candles) resp, err = s.OnSimultaneousSignals([]data.Handler{da}, nil, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(resp) != 1 { t.Fatal("expected 1 response") diff --git a/backtester/eventhandlers/strategies/rsi/rsi.go b/backtester/eventhandlers/strategies/rsi/rsi.go index 1dbf76b5..da6e5e87 100644 --- a/backtester/eventhandlers/strategies/rsi/rsi.go +++ b/backtester/eventhandlers/strategies/rsi/rsi.go @@ -55,15 +55,24 @@ func (s *Strategy) OnSignal(d data.Handler, _ funding.IFundingTransferer, _ port if err != nil { return nil, err } - es.SetPrice(d.Latest().GetClosePrice()) - if offset := d.Offset(); offset <= int(s.rsiPeriod.IntPart()) { + latest, err := d.Latest() + if err != nil { + return nil, err + } + + es.SetPrice(latest.GetClosePrice()) + + if offset := latest.GetOffset(); offset <= s.rsiPeriod.IntPart() { es.AppendReason("Not enough data for signal generation") es.SetDirection(order.DoNothing) return &es, nil } - dataRange := d.StreamClose() + dataRange, err := d.StreamClose() + if err != nil { + return nil, err + } var massagedData []float64 massagedData, err = s.massageMissingData(dataRange, es.GetTime()) if err != nil { @@ -71,9 +80,13 @@ func (s *Strategy) OnSignal(d data.Handler, _ funding.IFundingTransferer, _ port } rsi := indicators.RSI(massagedData, int(s.rsiPeriod.IntPart())) latestRSIValue := decimal.NewFromFloat(rsi[len(rsi)-1]) - if !d.HasDataAtTime(d.Latest().GetTime()) { + hasDataAtTime, err := d.HasDataAtTime(latest.GetTime()) + if err != nil { + return nil, err + } + if !hasDataAtTime { es.SetDirection(order.MissingData) - es.AppendReasonf("missing data at %v, cannot perform any actions. RSI %v", d.Latest().GetTime(), latestRSIValue) + es.AppendReasonf("missing data at %v, cannot perform any actions. RSI %v", latest.GetTime(), latestRSIValue) return &es, nil } @@ -103,9 +116,13 @@ func (s *Strategy) OnSimultaneousSignals(d []data.Handler, _ funding.IFundingTra var resp []signal.Event var errs gctcommon.Errors for i := range d { + latest, err := d[i].Latest() + if err != nil { + return nil, err + } sigEvent, err := s.OnSignal(d[i], nil, nil) if err != nil { - errs = append(errs, fmt.Errorf("%v %v %v %w", d[i].Latest().GetExchange(), d[i].Latest().GetAssetType(), d[i].Latest().Pair(), err)) + errs = append(errs, fmt.Errorf("%v %v %v %w", latest.GetExchange(), latest.GetAssetType(), latest.Pair(), err)) } else { resp = append(resp, sigEvent) } diff --git a/backtester/eventhandlers/strategies/rsi/rsi_test.go b/backtester/eventhandlers/strategies/rsi/rsi_test.go index 7f3ee802..91bb90f1 100644 --- a/backtester/eventhandlers/strategies/rsi/rsi_test.go +++ b/backtester/eventhandlers/strategies/rsi/rsi_test.go @@ -40,8 +40,8 @@ func TestSetCustomSettings(t *testing.T) { t.Parallel() s := Strategy{} err := s.SetCustomSettings(nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } float14 := float64(14) mappalopalous := make(map[string]interface{}) @@ -50,8 +50,8 @@ func TestSetCustomSettings(t *testing.T) { mappalopalous[rsiHighKey] = float14 err = s.SetCustomSettings(mappalopalous) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } mappalopalous[rsiPeriodKey] = "14" @@ -95,8 +95,8 @@ func TestOnSignal(t *testing.T) { exch := "binance" a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := data.Base{} - d.SetStream([]common.DataEventHandler{&eventkline.Kline{ + d := &data.Base{} + err = d.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Offset: 3, Exchange: exch, @@ -110,9 +110,14 @@ func TestOnSignal(t *testing.T) { Low: decimal.NewFromInt(1337), High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), - }}, - ) - d.Next() + }}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } da := &kline.DataFromKline{ Item: gctkline.Item{}, Base: d, @@ -126,8 +131,8 @@ func TestOnSignal(t *testing.T) { s.rsiPeriod = decimal.NewFromInt(1) _, err = s.OnSignal(da, nil, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } da.Item = gctkline.Item{ @@ -147,19 +152,19 @@ func TestOnSignal(t *testing.T) { }, } err = da.Load() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } ranger, err := gctkline.CalculateCandleDateRanges(dStart, dEnd, gctkline.OneDay, 100000) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } da.RangeHolder = ranger da.RangeHolder.SetHasDataFromCandles(da.Item.Candles) resp, err = s.OnSignal(da, nil, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if resp.GetDirection() != order.DoNothing { t.Error("expected do nothing") @@ -177,8 +182,8 @@ func TestOnSignals(t *testing.T) { exch := "binance" a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := data.Base{} - d.SetStream([]common.DataEventHandler{&eventkline.Kline{ + d := &data.Base{} + err = d.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Exchange: exch, Time: dInsert, @@ -192,7 +197,14 @@ func TestOnSignals(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) - d.Next() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v", err, nil) + } da := &kline.DataFromKline{ Item: gctkline.Item{}, Base: d, diff --git a/backtester/eventhandlers/strategies/strategies.go b/backtester/eventhandlers/strategies/strategies.go index c0575c31..dfdc5295 100644 --- a/backtester/eventhandlers/strategies/strategies.go +++ b/backtester/eventhandlers/strategies/strategies.go @@ -2,12 +2,13 @@ package strategies import ( "fmt" + "reflect" "strings" "sync" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/binancecashandcarry" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage" - "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/rsi" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/top2bottom2" "github.com/thrasher-corp/gocryptotrader/common" @@ -15,31 +16,64 @@ import ( // LoadStrategyByName returns the strategy by its name func LoadStrategyByName(name string, useSimultaneousProcessing bool) (Handler, error) { - strategies := GetStrategies() + strategies := GetSupportedStrategies() for i := range strategies { - if !strings.EqualFold(name, strategies[i].Name()) { - continue + strategy, err := createNewStrategy(name, useSimultaneousProcessing, strategies[i]) + if err != nil { + return nil, err } - if useSimultaneousProcessing { - if !strategies[i].SupportsSimultaneousProcessing() { - return nil, fmt.Errorf( - "strategy '%v' %w", - name, - base.ErrSimultaneousProcessingNotSupported) - } - strategies[i].SetSimultaneousProcessing(useSimultaneousProcessing) + if strategy != nil { + return strategy, err } - return strategies[i], nil } return nil, fmt.Errorf("strategy '%v' %w", name, base.ErrStrategyNotFound) } -// GetStrategies returns a static list of set strategies +func createNewStrategy(name string, useSimultaneousProcessing bool, h Handler) (Handler, error) { + if h == nil { + return nil, fmt.Errorf("cannot load %v supported strategies contains %w", name, common.ErrNilPointer) + } + if !strings.EqualFold(name, h.Name()) { + return nil, nil + } + // create new instance so strategy is not shared across all tasks + strategyValue := reflect.ValueOf(h) + if strategyValue.IsNil() { + return nil, fmt.Errorf("cannot load %v supported strategies element is a %w", name, common.ErrNilPointer) + } + strategyElement := strategyValue.Elem() + if !strategyElement.IsValid() { + return nil, fmt.Errorf("cannot load %v strategy element is invalid %w", name, common.ErrTypeAssertFailure) + } + strategyType := strategyElement.Type() + if strategyType == nil { + return nil, fmt.Errorf("cannot load %v strategy type is a %w", name, common.ErrNilPointer) + } + newStrategy := reflect.New(strategyType) + if newStrategy.IsNil() { + return nil, fmt.Errorf("cannot load %v new instance of strategy is a %w", name, common.ErrNilPointer) + } + strategyInterface := newStrategy.Interface() + if strategyInterface == nil { + return nil, fmt.Errorf("cannot load %v new instance of strategy is not an interface. %w", name, common.ErrTypeAssertFailure) + } + strategy, ok := strategyInterface.(Handler) + if !ok { + return nil, fmt.Errorf("cannot load %v new instance of strategy is not a Handler interface. %w", name, common.ErrTypeAssertFailure) + } + if useSimultaneousProcessing && !strategy.SupportsSimultaneousProcessing() { + return nil, base.ErrSimultaneousProcessingNotSupported + } + strategy.SetSimultaneousProcessing(useSimultaneousProcessing) + return strategy, nil +} + +// GetSupportedStrategies returns a static list of set strategies // they must be set in here for the backtester to recognise them -func GetStrategies() StrategyHolder { +func GetSupportedStrategies() StrategyHolder { m.Lock() defer m.Unlock() - return strategyHolder + return supportedStrategies } // AddStrategy will add a strategy to the list of strategies @@ -49,22 +83,22 @@ func AddStrategy(strategy Handler) error { } m.Lock() defer m.Unlock() - for i := range strategyHolder { - if strings.EqualFold(strategyHolder[i].Name(), strategy.Name()) { + for i := range supportedStrategies { + if strings.EqualFold(supportedStrategies[i].Name(), strategy.Name()) { return fmt.Errorf("'%v' %w", strategy.Name(), ErrStrategyAlreadyExists) } } - strategyHolder = append(strategyHolder, strategy) + supportedStrategies = append(supportedStrategies, strategy) return nil } var ( m sync.Mutex - strategyHolder = StrategyHolder{ + supportedStrategies = StrategyHolder{ new(dollarcostaverage.Strategy), new(rsi.Strategy), new(top2bottom2.Strategy), - new(ftxcashandcarry.Strategy), + new(binancecashandcarry.Strategy), } ) diff --git a/backtester/eventhandlers/strategies/strategies_test.go b/backtester/eventhandlers/strategies/strategies_test.go index d3b0f6b5..2bd8e622 100644 --- a/backtester/eventhandlers/strategies/strategies_test.go +++ b/backtester/eventhandlers/strategies/strategies_test.go @@ -16,7 +16,7 @@ import ( func TestGetStrategies(t *testing.T) { t.Parallel() - if resp := GetStrategies(); len(resp) < 2 { + if resp := GetSupportedStrategies(); len(resp) < 2 { t.Error("expected at least 2 strategies to be loaded") } } @@ -34,23 +34,23 @@ func TestLoadStrategyByName(t *testing.T) { } resp, err = LoadStrategyByName(dollarcostaverage.Name, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if resp.Name() != dollarcostaverage.Name { t.Error("expected dca") } resp, err = LoadStrategyByName(dollarcostaverage.Name, true) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if !resp.UsingSimultaneousProcessing() { t.Error("expected true") } resp, err = LoadStrategyByName(rsi.Name, false) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if resp.Name() != rsi.Name { t.Error("expected rsi") @@ -61,6 +61,55 @@ func TestLoadStrategyByName(t *testing.T) { } } +func TestAddStrategy(t *testing.T) { + t.Parallel() + err := AddStrategy(nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, common.ErrNilPointer) + } + err = AddStrategy(new(dollarcostaverage.Strategy)) + if !errors.Is(err, ErrStrategyAlreadyExists) { + t.Errorf("received '%v' expected '%v'", err, ErrStrategyAlreadyExists) + } + + err = AddStrategy(new(customStrategy)) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } +} + +func TestCreateNewStrategy(t *testing.T) { + t.Parallel() + + // invalid Handler + resp, err := createNewStrategy(dollarcostaverage.Name, false, nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, common.ErrNilPointer) + } + if resp != nil { + t.Errorf("received '%v' expected '%v'", resp, nil) + } + + // mismatched name + resp, err = createNewStrategy(dollarcostaverage.Name, false, &customStrategy{}) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if resp != nil { + t.Errorf("received '%v' expected '%v'", resp, nil) + } + + // valid + h := new(dollarcostaverage.Strategy) + resp, err = createNewStrategy(dollarcostaverage.Name, false, h) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + if resp == nil { + t.Errorf("received '%v' expected '%v'", resp, h) + } +} + type customStrategy struct { base.Strategy } @@ -89,20 +138,3 @@ func (s *customStrategy) SetCustomSettings(map[string]interface{}) error { // SetDefaults sets default values for overridable custom settings func (s *customStrategy) SetDefaults() {} - -func TestAddStrategy(t *testing.T) { - t.Parallel() - err := AddStrategy(nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilPointer) - } - err = AddStrategy(new(dollarcostaverage.Strategy)) - if !errors.Is(err, ErrStrategyAlreadyExists) { - t.Errorf("received '%v' expected '%v'", err, ErrStrategyAlreadyExists) - } - - err = AddStrategy(new(customStrategy)) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } -} diff --git a/backtester/eventhandlers/strategies/strategies_types.go b/backtester/eventhandlers/strategies/strategies_types.go index 792ba1d1..203b9d35 100644 --- a/backtester/eventhandlers/strategies/strategies_types.go +++ b/backtester/eventhandlers/strategies/strategies_types.go @@ -5,6 +5,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" + "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" "github.com/thrasher-corp/gocryptotrader/backtester/funding" ) @@ -26,4 +27,5 @@ type Handler interface { SetSimultaneousProcessing(bool) SetCustomSettings(map[string]interface{}) error SetDefaults() + CloseAllPositions([]holdings.Holding, []data.Event) ([]signal.Event, error) } diff --git a/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go b/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go index 04870c83..adb49e40 100644 --- a/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go +++ b/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go @@ -102,20 +102,36 @@ func (s *Strategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTra if err != nil { return nil, err } - es.SetPrice(d[i].Latest().GetClosePrice()) - offset := d[i].Offset() + latest, err := d[i].Latest() + if err != nil { + return nil, err + } + es.SetPrice(latest.GetClosePrice()) + offset := latest.GetOffset() - if offset <= int(s.mfiPeriod.IntPart()) { + if offset <= s.mfiPeriod.IntPart() { es.AppendReason("Not enough data for signal generation") es.SetDirection(order.DoNothing) resp = append(resp, &es) continue } - closeData := d[i].StreamClose() - volumeData := d[i].StreamVol() - highData := d[i].StreamHigh() - lowData := d[i].StreamLow() + history, err := d[i].History() + if err != nil { + return nil, err + } + var ( + closeData = make([]decimal.Decimal, len(history)) + volumeData = make([]decimal.Decimal, len(history)) + highData = make([]decimal.Decimal, len(history)) + lowData = make([]decimal.Decimal, len(history)) + ) + for i := range history { + closeData[i] = history[i].GetClosePrice() + volumeData[i] = history[i].GetVolume() + highData[i] = history[i].GetHighPrice() + lowData[i] = history[i].GetLowPrice() + } var massagedCloseData, massagedVolumeData, massagedHighData, massagedLowData []float64 massagedCloseData, err = s.massageMissingData(closeData, es.GetTime()) if err != nil { @@ -135,9 +151,13 @@ func (s *Strategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundingTra } mfi := indicators.MFI(massagedHighData, massagedLowData, massagedCloseData, massagedVolumeData, int(s.mfiPeriod.IntPart())) latestMFI := decimal.NewFromFloat(mfi[len(mfi)-1]) - if !d[i].HasDataAtTime(d[i].Latest().GetTime()) { + hasDataAtTime, err := d[i].HasDataAtTime(latest.GetTime()) + if err != nil { + return nil, err + } + if !hasDataAtTime { es.SetDirection(order.MissingData) - es.AppendReasonf("missing data at %v, cannot perform any actions. MFI %v", d[i].Latest().GetTime(), latestMFI) + es.AppendReasonf("missing data at %v, cannot perform any actions. MFI %v", latest.GetTime(), latestMFI) resp = append(resp, &es) continue } diff --git a/backtester/eventhandlers/strategies/top2bottom2/top2bottom2_test.go b/backtester/eventhandlers/strategies/top2bottom2/top2bottom2_test.go index 989642d1..4f79b662 100644 --- a/backtester/eventhandlers/strategies/top2bottom2/top2bottom2_test.go +++ b/backtester/eventhandlers/strategies/top2bottom2/top2bottom2_test.go @@ -7,7 +7,6 @@ import ( "time" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" @@ -48,8 +47,8 @@ func TestSetCustomSettings(t *testing.T) { t.Parallel() s := Strategy{} err := s.SetCustomSettings(nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } float14 := float64(14) mappalopalous := make(map[string]interface{}) @@ -58,8 +57,8 @@ func TestSetCustomSettings(t *testing.T) { mappalopalous[mfiHighKey] = float14 err = s.SetCustomSettings(mappalopalous) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } mappalopalous[mfiPeriodKey] = "14" @@ -109,8 +108,8 @@ func TestOnSignals(t *testing.T) { exch := "binance" a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) - d := data.Base{} - d.SetStream([]common.DataEventHandler{&eventkline.Kline{ + d := &data.Base{} + err = d.SetStream([]data.Event{&eventkline.Kline{ Base: &event.Base{ Exchange: exch, Time: dInsert, @@ -124,7 +123,13 @@ func TestOnSignals(t *testing.T) { High: decimal.NewFromInt(1337), Volume: decimal.NewFromInt(1337), }}) - d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + _, err = d.Next() + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } da := &kline.DataFromKline{ Item: gctkline.Item{}, Base: d, @@ -163,8 +168,8 @@ func TestSelectTopAndBottomPerformers(t *testing.T) { s := Strategy{} s.SetDefaults() _, err := s.selectTopAndBottomPerformers(nil, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } b := &event.Base{} fundEvents := []mfiFundEvent{ @@ -210,8 +215,8 @@ func TestSelectTopAndBottomPerformers(t *testing.T) { }, } resp, err := s.selectTopAndBottomPerformers(fundEvents, nil) - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(resp) != 5 { t.Error("expected 5 events") diff --git a/backtester/eventtypes/event/event.go b/backtester/eventtypes/event/event.go index 57014878..a2cce633 100644 --- a/backtester/eventtypes/event/event.go +++ b/backtester/eventtypes/event/event.go @@ -27,7 +27,7 @@ func (b *Base) IsEvent() bool { // GetTime returns the time func (b *Base) GetTime() time.Time { - return b.Time + return b.Time.UTC() } // Pair returns the currency pair diff --git a/backtester/eventtypes/fill/fill_types.go b/backtester/eventtypes/fill/fill_types.go index 15a81cd9..ecf2ba18 100644 --- a/backtester/eventtypes/fill/fill_types.go +++ b/backtester/eventtypes/fill/fill_types.go @@ -26,7 +26,7 @@ type Fill struct { // Event holds all functions required to handle a fill event type Event interface { - common.EventHandler + common.Event common.Directioner SetAmount(decimal.Decimal) diff --git a/backtester/eventtypes/kline/kline.go b/backtester/eventtypes/kline/kline.go index d529c421..96571f1c 100644 --- a/backtester/eventtypes/kline/kline.go +++ b/backtester/eventtypes/kline/kline.go @@ -25,7 +25,19 @@ func (k *Kline) GetOpenPrice() decimal.Decimal { return k.Open } +// GetVolume returns the volume of a kline +func (k *Kline) GetVolume() decimal.Decimal { + return k.Volume +} + // GetUnderlyingPair returns the open price of a kline func (k *Kline) GetUnderlyingPair() currency.Pair { return k.UnderlyingPair } + +// IsKline is a function to help distinguish between kline.Event +// and signal.Event as signal.Event implements kline.Event definitions otherwise +// this function is not called +func (k *Kline) IsKline() bool { + return true +} diff --git a/backtester/eventtypes/kline/kline_types.go b/backtester/eventtypes/kline/kline_types.go index 8fd8080d..a82e3862 100644 --- a/backtester/eventtypes/kline/kline_types.go +++ b/backtester/eventtypes/kline/kline_types.go @@ -2,11 +2,12 @@ package kline import ( "github.com/shopspring/decimal" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" ) // Kline holds kline data and an event to be processed as -// a common.DataEventHandler type +// a common.Event type type Kline struct { *event.Base Open decimal.Decimal @@ -16,3 +17,9 @@ type Kline struct { Volume decimal.Decimal ValidationIssues string } + +// Event is a kline data event +type Event interface { + data.Event + IsKline() bool +} diff --git a/backtester/eventtypes/order/order_types.go b/backtester/eventtypes/order/order_types.go index c51729c7..d6f66c3f 100644 --- a/backtester/eventtypes/order/order_types.go +++ b/backtester/eventtypes/order/order_types.go @@ -28,7 +28,7 @@ type Order struct { // Event inherits common event interfaces along with extra functions related to handling orders type Event interface { - common.EventHandler + common.Event common.Directioner GetClosePrice() decimal.Decimal GetBuyLimit() decimal.Decimal diff --git a/backtester/eventtypes/signal/signal.go b/backtester/eventtypes/signal/signal.go index 47797069..de9090de 100644 --- a/backtester/eventtypes/signal/signal.go +++ b/backtester/eventtypes/signal/signal.go @@ -2,6 +2,7 @@ package signal import ( "github.com/shopspring/decimal" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) @@ -51,6 +52,26 @@ func (s *Signal) GetClosePrice() decimal.Decimal { return s.ClosePrice } +// GetHighPrice returns the high price of a signal +func (s *Signal) GetHighPrice() decimal.Decimal { + return s.HighPrice +} + +// GetLowPrice returns the low price of a signal +func (s *Signal) GetLowPrice() decimal.Decimal { + return s.LowPrice +} + +// GetOpenPrice returns the open price of a signal +func (s *Signal) GetOpenPrice() decimal.Decimal { + return s.OpenPrice +} + +// GetVolume returns the volume of a signal +func (s *Signal) GetVolume() decimal.Decimal { + return s.Volume +} + // SetPrice sets the price func (s *Signal) SetPrice(f decimal.Decimal) { s.ClosePrice = f @@ -92,3 +113,18 @@ func (s *Signal) IsNil() bool { func (s *Signal) MatchOrderAmount() bool { return s.MatchesOrderAmount } + +// ToKline is used to convert a signal event +// to a data event for the purpose of closing all positions +// function CloseAllPositions is builds signal data, but +// data event data must still be populated +func (s *Signal) ToKline() kline.Event { + return &kline.Kline{ + Base: s.Base, + Open: s.OpenPrice, + Close: s.ClosePrice, + Low: s.LowPrice, + High: s.HighPrice, + Volume: s.Volume, + } +} diff --git a/backtester/eventtypes/signal/signal_test.go b/backtester/eventtypes/signal/signal_test.go index ca0cc5b6..6914a98d 100644 --- a/backtester/eventtypes/signal/signal_test.go +++ b/backtester/eventtypes/signal/signal_test.go @@ -5,6 +5,7 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/currency" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) @@ -33,7 +34,7 @@ func TestSetPrice(t *testing.T) { } s.SetPrice(decimal.NewFromInt(1337)) if !s.GetClosePrice().Equal(decimal.NewFromInt(1337)) { - t.Error("expected decimal.NewFromInt(1337)") + t.Errorf("received '%v' expected '%v'", s.GetClosePrice(), 1337) } } @@ -154,3 +155,49 @@ func TestMatchOrderAmount(t *testing.T) { t.Error("expected true") } } + +func TestGetHighPrice(t *testing.T) { + t.Parallel() + s := Signal{ + HighPrice: decimal.NewFromInt(1337), + } + if !s.GetHighPrice().Equal(decimal.NewFromInt(1337)) { + t.Errorf("received '%v' expected '%v'", s.GetHighPrice(), 1337) + } +} + +func TestGetLowPrice(t *testing.T) { + t.Parallel() + s := Signal{ + LowPrice: decimal.NewFromInt(1337), + } + if !s.GetLowPrice().Equal(decimal.NewFromInt(1337)) { + t.Errorf("received '%v' expected '%v'", s.GetLowPrice(), 1337) + } +} + +func TestGetOpenPrice(t *testing.T) { + t.Parallel() + s := Signal{ + OpenPrice: decimal.NewFromInt(1337), + } + if !s.GetOpenPrice().Equal(decimal.NewFromInt(1337)) { + t.Errorf("received '%v' expected '%v'", s.GetOpenPrice(), 1337) + } +} + +func TestToKline(t *testing.T) { + t.Parallel() + s := Signal{ + OpenPrice: decimal.NewFromInt(1337), + } + k := s.ToKline() + switch k.(type) { + case kline.Event: + if !k.GetOpenPrice().Equal(decimal.NewFromInt(1337)) { + t.Errorf("received '%v' expected '%v'", k.GetOpenPrice(), 1337) + } + default: + t.Errorf("expected '%v' received '%v'", "kline event", "signal event") + } +} diff --git a/backtester/eventtypes/signal/signal_types.go b/backtester/eventtypes/signal/signal_types.go index 4235dfd0..12c57283 100644 --- a/backtester/eventtypes/signal/signal_types.go +++ b/backtester/eventtypes/signal/signal_types.go @@ -4,6 +4,7 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" + "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) @@ -11,10 +12,14 @@ import ( // Event handler is used for getting trade signal details // Example Amount and Price of current candle tick type Event interface { - common.EventHandler + common.Event common.Directioner - + ToKline() kline.Event GetClosePrice() decimal.Decimal + GetHighPrice() decimal.Decimal + GetOpenPrice() decimal.Decimal + GetLowPrice() decimal.Decimal + GetVolume() decimal.Decimal IsSignal() bool GetSellLimit() decimal.Decimal GetBuyLimit() decimal.Decimal diff --git a/backtester/funding/README.md b/backtester/funding/README.md index c593cbb7..02ccb514 100644 --- a/backtester/funding/README.md +++ b/backtester/funding/README.md @@ -57,7 +57,7 @@ Yes! Though it does use some things to consider. - For example, if an indicator is very strong on one exchange, but not another, you may wish to transfer funds to the strongest exchange to act upon - It comes with the assumption that a transfer is actually possible in the candle timeframe your strategy runs on. - For example, a 1 minute candle strategy likely would not be able to process a transfer of funds and have another exchange use it in that timeframe. So any positive results from such a strategy may not be reflected in real-world scenarios -- You can only transfer to the same currency eg BTC from Binance to FTX, no conversions +- You can only transfer to the same currency eg BTC from Binance to Kraken, no conversions - You set the transfer fee in your config ### Do I need to add funding settings to my config if Exchange Level Funding is disabled? @@ -65,28 +65,29 @@ No. The already existing `CurrencySettings` will populate the funding manager wi #### Strategy Settings -| Key | Description | Example | -| --- | ------- | --- | -| Name | The strategy to use | `rsi` | -| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | -| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | - -#### Funding Settings - -| Key | Description | Example | -| --- | ------- | --- | -| UseExchangeLevelFunding | This allows shared exchange funds to be used in your strategy. Requires `UsesSimultaneousProcessing` to be set to `true` to use | `false` | -| ExchangeLevelFunding | This is a list of funding definitions if `UseExchangeLevelFunding` is set to true | See below table | +| Key | Description | Example | +|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| name | The strategy to use | `rsi` | +| use-simultaneous-signal-processing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | +| disable-usd-tracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` | +| custom-settings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | #### Funding Config Settings -| Key | Description | Example | -| --- | ------- | ----- | -| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | -| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports| `spot` | -| Currency | The currency to set funds | `BTC` | -| InitialFunds | The initial funding for the currency | `1337` | -| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | +| Key | Description | Example | +|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| use-exchange-level-funding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` | +| exchange-level-funding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` | + +##### Funding Item Config Settings + +| Key | Description | Example | +|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| +| exchange-name | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | +| asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | +| currency | The currency to set funds | `BTC` | +| initial-funds | The initial funding for the currency | `1337` | +| transfer-fee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/backtester/funding/funding.go b/backtester/funding/funding.go index 65bed25a..411eaa7f 100644 --- a/backtester/funding/funding.go +++ b/backtester/funding/funding.go @@ -2,7 +2,6 @@ package funding import ( "context" - "errors" "fmt" "sort" "strings" @@ -10,38 +9,23 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/funding/trackingcurrencies" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "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" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" -) - -var ( - // ErrFundsNotFound used when funds are requested but the funding is not found in the manager - ErrFundsNotFound = errors.New("funding not found") - // ErrAlreadyExists used when a matching item or pair is already in the funding manager - ErrAlreadyExists = errors.New("funding already exists") - // ErrUSDTrackingDisabled used when attempting to track USD values when disabled - ErrUSDTrackingDisabled = errors.New("USD tracking disabled") - - errCannotAllocate = errors.New("cannot allocate funds") - errZeroAmountReceived = errors.New("amount received less than or equal to zero") - errNegativeAmountReceived = errors.New("received negative decimal") - errNotEnoughFunds = errors.New("not enough funds") - errCannotTransferToSameFunds = errors.New("cannot send funds to self") - errTransferMustBeSameCurrency = errors.New("cannot transfer to different currency") - errCannotMatchTrackingToItem = errors.New("cannot match tracking data to funding items") - errNotFutures = errors.New("item linking collateral currencies must be a futures asset") - errExchangeManagerRequired = errors.New("exchange manager required") + "github.com/thrasher-corp/gocryptotrader/log" ) // SetupFundingManager creates the funding holder. It carries knowledge about levels of funding // across all execution handlers and enables fund transfers -func SetupFundingManager(exchManager *engine.ExchangeManager, usingExchangeLevelFunding, disableUSDTracking bool) (*FundManager, error) { +func SetupFundingManager(exchManager *engine.ExchangeManager, usingExchangeLevelFunding, disableUSDTracking, verbose bool) (*FundManager, error) { if exchManager == nil { return nil, errExchangeManagerRequired } @@ -49,6 +33,7 @@ func SetupFundingManager(exchManager *engine.ExchangeManager, usingExchangeLevel usingExchangeLevelFunding: usingExchangeLevelFunding, disableUSDTracking: disableUSDTracking, exchangeManager: exchManager, + verbose: verbose, }, nil } @@ -85,10 +70,10 @@ func CreateItem(exch string, a asset.Item, ci currency.Code, initialFunds, trans // for collateral purposes func (f *FundManager) LinkCollateralCurrency(item *Item, code currency.Code) error { if item == nil { - return fmt.Errorf("%w missing item", common.ErrNilArguments) + return fmt.Errorf("%w missing item", gctcommon.ErrNilPointer) } if code.IsEmpty() { - return fmt.Errorf("%w unset currency", common.ErrNilArguments) + return fmt.Errorf("%w unset currency", gctcommon.ErrNilPointer) } if !item.asset.IsFutures() { return errNotFutures @@ -120,23 +105,30 @@ func (f *FundManager) LinkCollateralCurrency(item *Item, code currency.Code) err // CreateSnapshot creates a Snapshot for an event's point in time // as funding.snapshots is a map, it allows for the last event // in the chronological list to establish the canon at X time -func (f *FundManager) CreateSnapshot(t time.Time) { +func (f *FundManager) CreateSnapshot(t time.Time) error { + if t.IsZero() { + return gctcommon.ErrDateUnset + } for i := range f.items { if f.items[i].snapshot == nil { f.items[i].snapshot = make(map[int64]ItemSnapshot) } - - iss := ItemSnapshot{ - Available: f.items[i].available, - Time: t, + iss, ok := f.items[i].snapshot[t.UnixNano()] + if !ok { + iss = ItemSnapshot{ + Time: t, + } } - + iss.Available = f.items[i].available if !f.disableUSDTracking { - var usdClosePrice decimal.Decimal if f.items[i].trackingCandles == nil { continue } - usdCandles := f.items[i].trackingCandles.GetStream() + var usdClosePrice decimal.Decimal + usdCandles, err := f.items[i].trackingCandles.GetStream() + if err != nil { + return err + } for j := range usdCandles { if usdCandles[j].GetTime().Equal(t) { usdClosePrice = usdCandles[j].GetClosePrice() @@ -149,13 +141,14 @@ func (f *FundManager) CreateSnapshot(t time.Time) { f.items[i].snapshot[t.UnixNano()] = iss } + return nil } // AddUSDTrackingData adds USD tracking data to a funding item // only in the event that it is not USD and there is data func (f *FundManager) AddUSDTrackingData(k *kline.DataFromKline) error { if f == nil || f.items == nil { - return common.ErrNilArguments + return gctcommon.ErrNilPointer } if f.disableUSDTracking { return ErrUSDTrackingDisabled @@ -169,7 +162,7 @@ func (f *FundManager) AddUSDTrackingData(k *kline.DataFromKline) error { } if f.items[i].asset.IsFutures() && k.Item.Asset.IsFutures() { if f.items[i].isCollateral { - err := f.setUSDCandles(k, i) + err := f.setUSDCandles(k, f.items[i]) if err != nil { return err } @@ -183,8 +176,7 @@ func (f *FundManager) AddUSDTrackingData(k *kline.DataFromKline) error { if strings.EqualFold(f.items[i].exchange, k.Item.Exchange) && f.items[i].asset == k.Item.Asset { if f.items[i].currency.Equal(k.Item.Pair.Base) { - if f.items[i].trackingCandles == nil && - trackingcurrencies.CurrencyIsUSDTracked(k.Item.Pair.Quote) { + if trackingcurrencies.CurrencyIsUSDTracked(k.Item.Pair.Quote) { f.items[i].trackingCandles = k if f.items[i].pairedWith != nil { basePairedWith = f.items[i].pairedWith.currency @@ -196,11 +188,9 @@ func (f *FundManager) AddUSDTrackingData(k *kline.DataFromKline) error { if f.items[i].pairedWith != nil && !f.items[i].currency.Equal(basePairedWith) { continue } - if f.items[i].trackingCandles == nil { - err := f.setUSDCandles(k, i) - if err != nil { - return err - } + err := f.setUSDCandles(k, f.items[i]) + if err != nil { + return err } quoteSet = true } @@ -216,29 +206,31 @@ func (f *FundManager) AddUSDTrackingData(k *kline.DataFromKline) error { // usd stablecoins do not always match in value, // this is a simplified implementation that can allow // USD tracking for many currencies across many exchanges -func (f *FundManager) setUSDCandles(k *kline.DataFromKline, i int) error { +func (f *FundManager) setUSDCandles(k *kline.DataFromKline, i *Item) error { usdCandles := gctkline.Item{ Exchange: k.Item.Exchange, - Pair: currency.Pair{Delimiter: k.Item.Pair.Delimiter, Base: f.items[i].currency, Quote: currency.USD}, + Pair: currency.Pair{Delimiter: k.Item.Pair.Delimiter, Base: i.currency, Quote: currency.USD}, Asset: k.Item.Asset, Interval: k.Item.Interval, Candles: make([]gctkline.Candle, len(k.Item.Candles)), } - for j := range usdCandles.Candles { - usdCandles.Candles[j] = gctkline.Candle{ - Time: k.Item.Candles[j].Time, + for x := range usdCandles.Candles { + usdCandles.Candles[x] = gctkline.Candle{ + Time: k.Item.Candles[x].Time, Open: 1, High: 1, Low: 1, Close: 1, } } - cpy := *k - cpy.Item = usdCandles - if err := cpy.Load(); err != nil { + usdData := &kline.DataFromKline{ + Base: &data.Base{}, + Item: usdCandles, + } + if err := usdData.Load(); err != nil { return err } - f.items[i].trackingCandles = &cpy + i.trackingCandles = usdData return nil } @@ -248,10 +240,10 @@ func (f *FundManager) setUSDCandles(k *kline.DataFromKline, i int) error { // USDT level funding func CreatePair(base, quote *Item) (*SpotPair, error) { if base == nil { - return nil, fmt.Errorf("base %w", common.ErrNilArguments) + return nil, fmt.Errorf("base %w", gctcommon.ErrNilPointer) } if quote == nil { - return nil, fmt.Errorf("quote %w", common.ErrNilArguments) + return nil, fmt.Errorf("quote %w", gctcommon.ErrNilPointer) } // copy to prevent the off chance of sending in the same base OR quote // to create a new pair with a new base OR quote @@ -268,11 +260,12 @@ func CreatePair(base, quote *Item) (*SpotPair, error) { // USDT level funding func CreateCollateral(contract, collateral *Item) (*CollateralPair, error) { if contract == nil { - return nil, fmt.Errorf("base %w", common.ErrNilArguments) + return nil, fmt.Errorf("base %w", gctcommon.ErrNilPointer) } if collateral == nil { - return nil, fmt.Errorf("quote %w", common.ErrNilArguments) + return nil, fmt.Errorf("quote %w", gctcommon.ErrNilPointer) } + collateral.isCollateral = true // copy to prevent the off chance of sending in the same base OR quote // to create a new pair with a new base OR quote bCopy := *contract @@ -283,8 +276,12 @@ func CreateCollateral(contract, collateral *Item) (*CollateralPair, error) { } // Reset clears all settings -func (f *FundManager) Reset() { +func (f *FundManager) Reset() error { + if f == nil { + return gctcommon.ErrNilPointer + } *f = FundManager{} + return nil } // USDTrackingDisabled clears all settings @@ -293,7 +290,7 @@ func (f *FundManager) USDTrackingDisabled() bool { } // GenerateReport builds report data for result HTML report -func (f *FundManager) GenerateReport() *Report { +func (f *FundManager) GenerateReport() (*Report, error) { report := Report{ UsingExchangeLevelFunding: f.usingExchangeLevelFunding, DisableUSDTracking: f.disableUSDTracking, @@ -301,22 +298,37 @@ func (f *FundManager) GenerateReport() *Report { items := make([]ReportItem, len(f.items)) for x := range f.items { item := ReportItem{ - Exchange: f.items[x].exchange, - Asset: f.items[x].asset, - Currency: f.items[x].currency, - InitialFunds: f.items[x].initialFunds, - TransferFee: f.items[x].transferFee, - FinalFunds: f.items[x].available, - IsCollateral: f.items[x].isCollateral, + Exchange: f.items[x].exchange, + Asset: f.items[x].asset, + Currency: f.items[x].currency, + InitialFunds: f.items[x].initialFunds, + TransferFee: f.items[x].transferFee, + FinalFunds: f.items[x].available, + IsCollateral: f.items[x].isCollateral, + AppendedViaAPI: f.items[x].appendedViaAPI, } if !f.disableUSDTracking && f.items[x].trackingCandles != nil { - usdStream := f.items[x].trackingCandles.GetStream() - item.USDInitialFunds = f.items[x].initialFunds.Mul(usdStream[0].GetClosePrice()) - item.USDFinalFunds = f.items[x].available.Mul(usdStream[len(usdStream)-1].GetClosePrice()) - item.USDInitialCostForOne = usdStream[0].GetClosePrice() - item.USDFinalCostForOne = usdStream[len(usdStream)-1].GetClosePrice() + usdStream, err := f.items[x].trackingCandles.GetStream() + if err != nil { + return nil, err + } + last, err := usdStream.Last() + if err != nil { + log.Errorf(common.FundManager, "USD tracking data is nil for %v %v %v, please ensure data is present", f.items[x].exchange, f.items[x].asset, f.items[x].currency) + } + first, err := usdStream.First() + if err != nil { + log.Errorf(common.FundManager, "USD tracking data is nil for %v %v %v, please ensure data is present", f.items[x].exchange, f.items[x].asset, f.items[x].currency) + } + if !item.IsCollateral { + item.USDInitialFunds = f.items[x].initialFunds.Mul(first.GetClosePrice()) + item.USDFinalFunds = f.items[x].available.Mul(last.GetClosePrice()) + } + + item.USDInitialCostForOne = first.GetClosePrice() + item.USDFinalCostForOne = last.GetClosePrice() item.USDPairCandle = f.items[x].trackingCandles } @@ -331,14 +343,15 @@ func (f *FundManager) GenerateReport() *Report { continue } for y := range report.USDTotalsOverTime { - if report.USDTotalsOverTime[y].Time.Equal(snapshot.Time) { - report.USDTotalsOverTime[y].USDValue = report.USDTotalsOverTime[y].USDValue.Add(snapshot.USDValue) - report.USDTotalsOverTime[y].Breakdown = append(report.USDTotalsOverTime[y].Breakdown, CurrencyContribution{ - Currency: f.items[x].currency, - USDContribution: snapshot.USDValue, - }) - continue snaps + if !report.USDTotalsOverTime[y].Time.Equal(snapshot.Time) { + continue } + report.USDTotalsOverTime[y].USDValue = report.USDTotalsOverTime[y].USDValue.Add(snapshot.USDValue) + report.USDTotalsOverTime[y].Breakdown = append(report.USDTotalsOverTime[y].Breakdown, CurrencyContribution{ + Currency: f.items[x].currency, + USDContribution: snapshot.USDValue, + }) + continue snaps } report.USDTotalsOverTime = append(report.USDTotalsOverTime, ItemSnapshot{ Time: snapshot.Time, @@ -378,13 +391,13 @@ func (f *FundManager) GenerateReport() *Report { } report.Items = items - return &report + return &report, nil } // Transfer allows transferring funds from one pretend exchange to another func (f *FundManager) Transfer(amount decimal.Decimal, sender, receiver *Item, inclusiveFee bool) error { if sender == nil || receiver == nil { - return common.ErrNilArguments + return gctcommon.ErrNilPointer } if amount.LessThanOrEqual(decimal.Zero) { return errZeroAmountReceived @@ -464,7 +477,7 @@ func (f *FundManager) IsUsingExchangeLevelFunding() bool { } // GetFundingForEvent This will construct a funding based on a backtesting event -func (f *FundManager) GetFundingForEvent(ev common.EventHandler) (IFundingPair, error) { +func (f *FundManager) GetFundingForEvent(ev common.Event) (IFundingPair, error) { return f.getFundingForEAP(ev.GetExchange(), ev.GetAssetType(), ev.Pair()) } @@ -502,20 +515,10 @@ func (f *FundManager) getFundingForEAP(exch string, a asset.Item, p currency.Pai return nil, fmt.Errorf("%v %v %v %w", exch, a, p, ErrFundsNotFound) } -// GetFundingForEAC This will construct a funding based on the exchange, asset, currency code -func (f *FundManager) getFundingForEAC(exch string, a asset.Item, c currency.Code) (*Item, error) { - for i := range f.items { - if f.items[i].BasicEqual(exch, a, c, currency.EMPTYCODE) { - return f.items[i], nil - } - } - return nil, ErrFundsNotFound -} - // Liquidate will remove all funding for all items belonging to an exchange -func (f *FundManager) Liquidate(ev common.EventHandler) { +func (f *FundManager) Liquidate(ev common.Event) error { if ev == nil { - return + return fmt.Errorf("%w event", gctcommon.ErrNilPointer) } for i := range f.items { if f.items[i].exchange == ev.GetExchange() { @@ -524,16 +527,20 @@ func (f *FundManager) Liquidate(ev common.EventHandler) { f.items[i].isLiquidated = true } } + return nil } // GetAllFunding returns basic representations of all current // holdings from the latest point -func (f *FundManager) GetAllFunding() []BasicItem { +func (f *FundManager) GetAllFunding() ([]BasicItem, error) { result := make([]BasicItem, len(f.items)) for i := range f.items { var usd decimal.Decimal if f.items[i].trackingCandles != nil { - latest := f.items[i].trackingCandles.Latest() + latest, err := f.items[i].trackingCandles.Latest() + if err != nil { + return nil, err + } if latest != nil { usd = latest.GetClosePrice() } @@ -548,25 +555,144 @@ func (f *FundManager) GetAllFunding() []BasicItem { USDPrice: usd, } } - return result + return result, nil } -// UpdateCollateral will recalculate collateral for an exchange +// UpdateFundingFromLiveData forcefully updates funding from a live source +func (f *FundManager) UpdateFundingFromLiveData(initialFundsSet bool) error { + exchanges, err := f.exchangeManager.GetExchanges() + if err != nil { + return err + } + for x := range exchanges { + var creds *account.Credentials + creds, err = exchanges[x].GetCredentials(context.TODO()) + if err != nil { + return err + } + assets := exchanges[x].GetAssetTypes(false) + for y := range assets { + if assets[y].IsFutures() { + // we set all holdings as spot + // futures currency holdings are collateral in the collateral currency + continue + } + var acc account.Holdings + acc, err = exchanges[x].UpdateAccountInfo(context.TODO(), assets[y]) + if err != nil { + return err + } + for z := range acc.Accounts { + if !acc.Accounts[z].Credentials.Equal(creds) { + continue + } + for i := range acc.Accounts[z].Currencies { + err = f.SetFunding(exchanges[x].GetName(), assets[y], &acc.Accounts[z].Currencies[i], initialFundsSet) + if err != nil { + return err + } + } + } + } + } + return nil +} + +// UpdateAllCollateral will update the collateral values +// of all stored exchanges +func (f *FundManager) UpdateAllCollateral(isLive, initialFundsSet bool) error { + exchanges, err := f.exchangeManager.GetExchanges() + if err != nil { + return err + } + + for x := range exchanges { + exchName := strings.ToLower(exchanges[x].GetName()) + exchangeCollateralCalculator := &gctorder.TotalCollateralCalculator{ + CalculateOffline: !isLive, + } + for y := range f.items { + if f.items[y].exchange != exchName { + continue + } + if f.items[y].asset.IsFutures() { + // futures positions aren't collateral, they utilise it + continue + } + var usd decimal.Decimal + if f.items[y].trackingCandles != nil { + var latest data.Event + latest, err = f.items[y].trackingCandles.Latest() + if err != nil { + return err + } + if latest != nil { + usd = latest.GetClosePrice() + } + } + if usd.IsZero() && exchangeCollateralCalculator.CalculateOffline { + continue + } + var side = gctorder.Buy + if !f.items[y].available.GreaterThan(decimal.Zero) { + side = gctorder.Sell + } + + exchangeCollateralCalculator.CollateralAssets = append(exchangeCollateralCalculator.CollateralAssets, gctorder.CollateralCalculator{ + CalculateOffline: !isLive, + CollateralCurrency: f.items[y].currency, + Asset: f.items[y].asset, + Side: side, + FreeCollateral: f.items[y].available, + LockedCollateral: f.items[y].reserved, + USDPrice: usd, + }) + } + + var collateral *gctorder.TotalCollateralResponse + collateral, err = exchanges[x].CalculateTotalCollateral(context.TODO(), exchangeCollateralCalculator) + if err != nil { + return err + } + for y := range f.items { + if f.items[y].exchange == exchName && + f.items[y].isCollateral { + if f.verbose { + log.Infof(common.FundManager, "Setting collateral %v %v %v to %v", f.items[y].exchange, f.items[y].asset, f.items[y].currency, collateral.AvailableCollateral) + } + f.items[y].available = collateral.AvailableCollateral + if !initialFundsSet { + f.items[y].initialFunds = collateral.AvailableCollateral + } + return nil + } + } + } + + return nil +} + +// UpdateCollateralForEvent will recalculate collateral for an exchange // based on the event passed in -func (f *FundManager) UpdateCollateral(ev common.EventHandler) error { +func (f *FundManager) UpdateCollateralForEvent(ev common.Event, isLive bool) error { if ev == nil { return common.ErrNilEvent } + if !f.HasFutures() { + // no collateral, no need to update + return nil + } + exchMap := make(map[string]exchange.IBotExchange) var collateralAmount decimal.Decimal var err error calculator := gctorder.TotalCollateralCalculator{ - CalculateOffline: true, + CalculateOffline: !isLive, } for i := range f.items { if f.items[i].asset.IsFutures() { - // futures positions aren't collateral, they use it + // futures positions aren't collateral, they utilise it continue } _, ok := exchMap[f.items[i].exchange] @@ -580,7 +706,11 @@ func (f *FundManager) UpdateCollateral(ev common.EventHandler) error { } var usd decimal.Decimal if f.items[i].trackingCandles != nil { - latest := f.items[i].trackingCandles.Latest() + var latest data.Event + latest, err = f.items[i].trackingCandles.Latest() + if err != nil { + return err + } if latest != nil { usd = latest.GetClosePrice() } @@ -594,7 +724,7 @@ func (f *FundManager) UpdateCollateral(ev common.EventHandler) error { } calculator.CollateralAssets = append(calculator.CollateralAssets, gctorder.CollateralCalculator{ - CalculateOffline: true, + CalculateOffline: !isLive, CollateralCurrency: f.items[i].currency, Asset: f.items[i].asset, Side: side, @@ -621,6 +751,9 @@ func (f *FundManager) UpdateCollateral(ev common.EventHandler) error { if f.items[i].exchange == ev.GetExchange() && f.items[i].asset == futureAsset && f.items[i].currency.Equal(futureCurrency) { + if f.verbose { + log.Infof(common.FundManager, "Setting collateral %v %v %v to %v", f.items[i].exchange, f.items[i].asset, f.items[i].currency, collat.AvailableCollateral) + } f.items[i].available = collat.AvailableCollateral return nil } @@ -652,7 +785,7 @@ func (f *FundManager) RealisePNL(receivingExchange string, receivingAsset asset. // HasExchangeBeenLiquidated checks for any items with a matching exchange // and returns whether it has been liquidated -func (f *FundManager) HasExchangeBeenLiquidated(ev common.EventHandler) bool { +func (f *FundManager) HasExchangeBeenLiquidated(ev common.Event) bool { for i := range f.items { if ev.GetExchange() == f.items[i].exchange { return f.items[i].isLiquidated @@ -660,3 +793,56 @@ func (f *FundManager) HasExchangeBeenLiquidated(ev common.EventHandler) bool { } return false } + +// SetFunding overwrites a funding setting. This is for live trading +// where external wallet amounts need to be synced +// As external sources may have additional currencies and balances +// versus the strategy currencies, they must be appended to +// help calculate collateral +func (f *FundManager) SetFunding(exchName string, item asset.Item, balance *account.Balance, initialFundsSet bool) error { + if exchName == "" { + return engine.ErrExchangeNameIsEmpty + } + if !item.IsValid() { + return asset.ErrNotSupported + } + if balance == nil { + return gctcommon.ErrNilPointer + } + if balance.Currency.IsEmpty() { + return currency.ErrCurrencyCodeEmpty + } + + exchName = strings.ToLower(exchName) + amount := decimal.NewFromFloat(balance.Total) + for i := range f.items { + if f.items[i].asset.IsFutures() { + continue + } + if f.items[i].exchange != exchName || + f.items[i].asset != item || + !f.items[i].currency.Equal(balance.Currency) { + continue + } + if f.verbose { + log.Infof(common.FundManager, "Setting %v %v %v balance to %v", exchName, item, balance.Currency, balance.Total) + } + if !initialFundsSet { + f.items[i].initialFunds = amount + } + f.items[i].available = amount + return nil + } + if f.verbose { + log.Debugf(common.FundManager, "Appending balance %v %v %v to %v", exchName, item, balance.Currency, balance.Total) + } + f.items = append(f.items, &Item{ + exchange: exchName, + asset: item, + currency: balance.Currency, + initialFunds: amount, + available: amount, + appendedViaAPI: true, + }) + return nil +} diff --git a/backtester/funding/funding_test.go b/backtester/funding/funding_test.go index 533eb733..04438e19 100644 --- a/backtester/funding/funding_test.go +++ b/backtester/funding/funding_test.go @@ -7,12 +7,17 @@ import ( "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" + "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "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/binance" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" ) @@ -20,36 +25,16 @@ var ( elite = decimal.NewFromInt(1337) neg = decimal.NewFromInt(-1) one = decimal.NewFromInt(1) - exchName = "exchname" + exchName = "binance" a = asset.Spot base = currency.DOGE quote = currency.XRP pair = currency.NewPair(base, quote) ) -// fakeEvent implements common.EventHandler without -// caring about the response, or dealing with import cycles -type fakeEvent struct{} - -func (f *fakeEvent) GetOffset() int64 { return 0 } -func (f *fakeEvent) SetOffset(int64) {} -func (f *fakeEvent) IsEvent() bool { return true } -func (f *fakeEvent) GetTime() time.Time { return time.Now() } -func (f *fakeEvent) Pair() currency.Pair { return pair } -func (f *fakeEvent) GetExchange() string { return exchName } -func (f *fakeEvent) GetInterval() gctkline.Interval { return gctkline.OneMin } -func (f *fakeEvent) GetAssetType() asset.Item { return asset.Spot } -func (f *fakeEvent) AppendReason(string) {} -func (f *fakeEvent) GetClosePrice() decimal.Decimal { return elite } -func (f *fakeEvent) AppendReasonf(s string, i ...interface{}) {} -func (f *fakeEvent) GetBase() *event.Base { return &event.Base{} } -func (f *fakeEvent) GetUnderlyingPair() currency.Pair { return pair } -func (f *fakeEvent) GetConcatReasons() string { return "" } -func (f *fakeEvent) GetReasons() []string { return nil } - func TestSetupFundingManager(t *testing.T) { t.Parallel() - f, err := SetupFundingManager(&engine.ExchangeManager{}, true, false) + f, err := SetupFundingManager(&engine.ExchangeManager{}, true, false, false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -59,7 +44,7 @@ func TestSetupFundingManager(t *testing.T) { if f.disableUSDTracking { t.Errorf("expected '%v received '%v'", false, true) } - f, err = SetupFundingManager(&engine.ExchangeManager{}, false, true) + f, err = SetupFundingManager(&engine.ExchangeManager{}, false, true, true) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -69,11 +54,14 @@ func TestSetupFundingManager(t *testing.T) { if !f.disableUSDTracking { t.Errorf("expected '%v received '%v'", true, false) } + if !f.verbose { + t.Errorf("expected '%v received '%v'", true, false) + } } func TestReset(t *testing.T) { t.Parallel() - f, err := SetupFundingManager(&engine.ExchangeManager{}, true, false) + f, err := SetupFundingManager(&engine.ExchangeManager{}, true, false, false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -85,7 +73,10 @@ func TestReset(t *testing.T) { if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } - f.Reset() + err = f.Reset() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if f.usingExchangeLevelFunding { t.Errorf("expected '%v received '%v'", false, true) } @@ -96,7 +87,7 @@ func TestReset(t *testing.T) { func TestIsUsingExchangeLevelFunding(t *testing.T) { t.Parallel() - f, err := SetupFundingManager(&engine.ExchangeManager{}, true, false) + f, err := SetupFundingManager(&engine.ExchangeManager{}, true, false, false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -112,12 +103,12 @@ func TestTransfer(t *testing.T) { items: nil, } err := f.Transfer(decimal.Zero, nil, nil, false) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } err = f.Transfer(decimal.Zero, &Item{}, nil, false) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } err = f.Transfer(decimal.Zero, &Item{}, &Item{}, false) if !errors.Is(err, errZeroAmountReceived) { @@ -261,14 +252,6 @@ func TestExists(t *testing.T) { if !f.Exists(&baseCopy) { t.Errorf("received '%v' expected '%v'", false, true) } - - currFunds, err := f.getFundingForEAC(exchName, a, base) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - if currFunds.pairedWith != nil { - t.Errorf("received '%v' expected '%v'", nil, currFunds.pairedWith) - } } func TestAddPair(t *testing.T) { @@ -334,32 +317,6 @@ func TestGetFundingForEvent(t *testing.T) { } } -func TestGetFundingForEAC(t *testing.T) { - t.Parallel() - f := FundManager{} - _, err := f.getFundingForEAC(exchName, a, base) - if !errors.Is(err, ErrFundsNotFound) { - t.Errorf("received '%v' expected '%v'", err, ErrFundsNotFound) - } - baseItem, err := CreateItem(exchName, a, pair.Base, decimal.Zero, decimal.Zero) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - err = f.AddItem(baseItem) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - - fundo, err := f.getFundingForEAC(exchName, a, base) - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) - } - - if !baseItem.Equal(fundo) { - t.Errorf("received '%v' expected '%v'", baseItem, fundo) - } -} - func TestGetFundingForEAP(t *testing.T) { t.Parallel() f := FundManager{} @@ -389,12 +346,12 @@ func TestGetFundingForEAP(t *testing.T) { } _, err = CreatePair(baseItem, nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } _, err = CreatePair(nil, quoteItem) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } p, err = CreatePair(baseItem, quoteItem) if !errors.Is(err, nil) { @@ -409,7 +366,10 @@ func TestGetFundingForEAP(t *testing.T) { func TestGenerateReport(t *testing.T) { t.Parallel() f := FundManager{} - report := f.GenerateReport() + report, err := f.GenerateReport() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if report == nil { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings t.Fatal("shouldn't be nil") } @@ -423,11 +383,14 @@ func TestGenerateReport(t *testing.T) { currency: currency.BTC, asset: a, } - err := f.AddItem(item) + err = f.AddItem(item) if err != nil { t.Fatal(err) } - report = f.GenerateReport() + report, err = f.GenerateReport() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if len(report.Items) != 1 { t.Fatal("expected 1") } @@ -440,7 +403,7 @@ func TestGenerateReport(t *testing.T) { exchange: exchName, initialFunds: decimal.NewFromInt(100), available: decimal.NewFromInt(200), - currency: currency.USD, + currency: currency.USDT, asset: a, }) if err != nil { @@ -448,9 +411,10 @@ func TestGenerateReport(t *testing.T) { } dfk := &kline.DataFromKline{ + Base: &data.Base{}, Item: gctkline.Item{ Exchange: exchName, - Pair: currency.NewPair(currency.BTC, currency.USD), + Pair: currency.NewPair(currency.BTC, currency.USDT), Asset: a, Interval: gctkline.OneHour, Candles: []gctkline.Candle{ @@ -469,9 +433,15 @@ func TestGenerateReport(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, nil) } f.items[0].trackingCandles = dfk - f.CreateSnapshot(dfk.Item.Candles[0].Time) + err = f.CreateSnapshot(dfk.Item.Candles[0].Time) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } - report = f.GenerateReport() + report, err = f.GenerateReport() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if len(report.Items) != 2 { t.Fatal("expected 2") } @@ -486,11 +456,14 @@ func TestGenerateReport(t *testing.T) { func TestCreateSnapshot(t *testing.T) { t.Parallel() f := FundManager{} - f.CreateSnapshot(time.Time{}) - f.items = append(f.items, &Item{}) - f.CreateSnapshot(time.Time{}) + err := f.CreateSnapshot(time.Time{}) + if !errors.Is(err, gctcommon.ErrDateUnset) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrDateUnset) + } + f.items = append(f.items, &Item{}) dfk := &kline.DataFromKline{ + Base: &data.Base{}, Item: gctkline.Item{ Candles: []gctkline.Candle{ { @@ -499,9 +472,11 @@ func TestCreateSnapshot(t *testing.T) { }, }, } - if err := dfk.Load(); err != nil { - t.Error(err) + err = dfk.Load() + if !errors.Is(err, data.ErrInvalidEventSupplied) { + t.Errorf("received '%v' expected '%v'", err, nil) } + f.items = append(f.items, &Item{ exchange: "test", asset: asset.Spot, @@ -512,23 +487,27 @@ func TestCreateSnapshot(t *testing.T) { transferFee: decimal.NewFromInt(1337), trackingCandles: dfk, }) - f.CreateSnapshot(dfk.Item.Candles[0].Time) + err = f.CreateSnapshot(dfk.Item.Candles[0].Time) + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } } func TestAddUSDTrackingData(t *testing.T) { t.Parallel() f := FundManager{} err := f.AddUSDTrackingData(nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } - err = f.AddUSDTrackingData(&kline.DataFromKline{}) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + err = f.AddUSDTrackingData(kline.NewDataFromKline()) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } dfk := &kline.DataFromKline{ + Base: &data.Base{}, Item: gctkline.Item{ Candles: []gctkline.Candle{ { @@ -538,8 +517,8 @@ func TestAddUSDTrackingData(t *testing.T) { }, } err = dfk.Load() - if !errors.Is(err, nil) { - t.Errorf("received '%v' expected '%v'", err, nil) + if !errors.Is(err, data.ErrInvalidEventSupplied) { + t.Errorf("received '%v' expected '%v'", err, data.ErrInvalidEventSupplied) } quoteItem, err := CreateItem(exchName, a, pair.Quote, elite, decimal.Zero) if !errors.Is(err, nil) { @@ -563,9 +542,10 @@ func TestAddUSDTrackingData(t *testing.T) { } dfk = &kline.DataFromKline{ + Base: &data.Base{}, Item: gctkline.Item{ Exchange: exchName, - Pair: currency.NewPair(pair.Quote, currency.USD), + Pair: currency.NewPair(pair.Quote, currency.USDT), Asset: a, Interval: gctkline.OneHour, Candles: []gctkline.Candle{ @@ -613,7 +593,10 @@ func TestUSDTrackingDisabled(t *testing.T) { func TestFundingLiquidate(t *testing.T) { t.Parallel() f := FundManager{} - f.Liquidate(nil) + err := f.Liquidate(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("recevied '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } f.items = append(f.items, &Item{ exchange: "test", asset: asset.Spot, @@ -621,13 +604,16 @@ func TestFundingLiquidate(t *testing.T) { available: decimal.NewFromInt(1337), }) - f.Liquidate(&signal.Signal{ + err = f.Liquidate(&signal.Signal{ Base: &event.Base{ Exchange: "test", AssetType: asset.Spot, - CurrencyPair: currency.NewPair(currency.BTC, currency.USD), + CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), }, }) + if !errors.Is(err, nil) { + t.Errorf("recevied '%v' expected '%v'", err, nil) + } if !f.items[0].available.IsZero() { t.Errorf("received '%v' expected '%v'", f.items[0].available, "0") } @@ -636,7 +622,10 @@ func TestFundingLiquidate(t *testing.T) { func TestHasExchangeBeenLiquidated(t *testing.T) { t.Parallel() f := FundManager{} - f.Liquidate(nil) + err := f.Liquidate(nil) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("recevied '%v' expected '%v'", err, gctcommon.ErrNilPointer) + } f.items = append(f.items, &Item{ exchange: "test", asset: asset.Spot, @@ -647,10 +636,13 @@ func TestHasExchangeBeenLiquidated(t *testing.T) { Base: &event.Base{ Exchange: "test", AssetType: asset.Spot, - CurrencyPair: currency.NewPair(currency.BTC, currency.USD), + CurrencyPair: currency.NewPair(currency.BTC, currency.USDT), }, } - f.Liquidate(ev) + err = f.Liquidate(ev) + if !errors.Is(err, nil) { + t.Errorf("recevied '%v' expected '%v'", err, nil) + } if !f.items[0].available.IsZero() { t.Errorf("received '%v' expected '%v'", f.items[0].available, "0") } @@ -662,7 +654,10 @@ func TestHasExchangeBeenLiquidated(t *testing.T) { func TestGetAllFunding(t *testing.T) { t.Parallel() f := FundManager{} - resp := f.GetAllFunding() + resp, err := f.GetAllFunding() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if len(resp) != 0 { t.Errorf("received '%v' expected '%v'", len(resp), 0) } @@ -674,7 +669,10 @@ func TestGetAllFunding(t *testing.T) { available: decimal.NewFromInt(1337), }) - resp = f.GetAllFunding() + resp, err = f.GetAllFunding() + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } if len(resp) != 1 { t.Errorf("received '%v' expected '%v'", len(resp), 1) } @@ -747,7 +745,7 @@ func TestCreateCollateral(t *testing.T) { t.Errorf("recevied '%v' expected '%v'", err, expectedError) } - expectedError = common.ErrNilArguments + expectedError = gctcommon.ErrNilPointer _, err = CreateCollateral(nil, contract) if !errors.Is(err, expectedError) { t.Errorf("recevied '%v' expected '%v'", err, expectedError) @@ -763,56 +761,48 @@ func TestUpdateCollateral(t *testing.T) { t.Parallel() f := &FundManager{} expectedError := common.ErrNilEvent - err := f.UpdateCollateral(nil) + err := f.UpdateCollateralForEvent(nil, false) if !errors.Is(err, expectedError) { t.Errorf("recevied '%v' expected '%v'", err, expectedError) } ev := &signal.Signal{ Base: &event.Base{ - Exchange: "ftx", + Exchange: exchName, AssetType: asset.Futures, CurrencyPair: currency.NewPair(currency.BTC, currency.USD), }, } f.items = append(f.items, &Item{ - exchange: "ftx", + exchange: exchName, asset: asset.Spot, currency: currency.BTC, available: decimal.NewFromInt(1336), }) em := engine.SetupExchangeManager() - exch, err := em.NewExchangeByName("ftx") + exch, err := em.NewExchangeByName(exchName) if err != nil { t.Fatal(err) } exch.SetDefaults() - cfg, err := exch.GetDefaultConfig() - if err != nil { - t.Fatal(err) - } - err = exch.Setup(cfg) - if err != nil { - t.Fatal(err) - } em.Add(exch) f.exchangeManager = em - expectedError = ErrFundsNotFound - err = f.UpdateCollateral(ev) + expectedError = nil + err = f.UpdateCollateralForEvent(ev, false) if !errors.Is(err, expectedError) { t.Errorf("recevied '%v' expected '%v'", err, expectedError) } - expectedError = nil + expectedError = gctcommon.ErrNotYetImplemented f.items = append(f.items, &Item{ - exchange: "ftx", + exchange: exchName, asset: asset.Futures, currency: currency.USD, available: decimal.NewFromInt(1336), isCollateral: true, }) - err = f.UpdateCollateral(ev) + err = f.UpdateCollateralForEvent(ev, false) if !errors.Is(err, expectedError) { t.Errorf("recevied '%v' expected '%v'", err, expectedError) } @@ -829,14 +819,14 @@ func TestLinkCollateralCurrency(t *testing.T) { t.Parallel() f := FundManager{} err := f.LinkCollateralCurrency(nil, currency.EMPTYCODE) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v', expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNilPointer) } item := &Item{} err = f.LinkCollateralCurrency(item, currency.EMPTYCODE) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v', expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNilPointer) } err = f.LinkCollateralCurrency(item, currency.BTC) @@ -865,3 +855,193 @@ func TestLinkCollateralCurrency(t *testing.T) { t.Errorf("received '%v', expected '%v'", err, nil) } } + +func TestSetFunding(t *testing.T) { + t.Parallel() + f := &FundManager{} + err := f.SetFunding("", 0, nil, false) + if !errors.Is(err, engine.ErrExchangeNameIsEmpty) { + t.Errorf("received '%v', expected '%v'", err, engine.ErrExchangeNameIsEmpty) + } + + err = f.SetFunding(exchName, 0, nil, false) + if !errors.Is(err, asset.ErrNotSupported) { + t.Errorf("received '%v', expected '%v'", err, asset.ErrNotSupported) + } + + err = f.SetFunding(exchName, asset.Spot, nil, false) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNilPointer) + } + + bal := &account.Balance{} + err = f.SetFunding(exchName, asset.Spot, bal, false) + if !errors.Is(err, currency.ErrCurrencyCodeEmpty) { + t.Errorf("received '%v', expected '%v'", err, currency.ErrCurrencyCodeEmpty) + } + + bal.Currency = currency.BTC + bal.Total = 1337 + err = f.SetFunding(exchName, asset.Spot, bal, false) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + if len(f.items) != 1 { + t.Fatalf("received '%v' expected '%v'", len(f.items), 1) + } + if !f.items[0].available.Equal(leet) { + t.Errorf("received '%v' expected '%v'", f.items[0].available, bal.Total) + } + if !f.items[0].initialFunds.Equal(leet) { + t.Errorf("received '%v' expected '%v'", f.items[0].available, bal.Total) + } + + bal.Total = 1338 + err = f.SetFunding(exchName, asset.Spot, bal, true) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + if !f.items[0].available.Equal(decimal.NewFromFloat(bal.Total)) { + t.Errorf("received '%v' expected '%v'", f.items[0].available, bal.Total) + } + if !f.items[0].initialFunds.Equal(leet) { + t.Errorf("received '%v' expected '%v'", f.items[0].available, leet) + } +} + +func TestUpdateFundingFromLiveData(t *testing.T) { + t.Parallel() + f := &FundManager{} + err := f.UpdateFundingFromLiveData(false) + if !errors.Is(err, engine.ErrNilSubsystem) { + t.Errorf("received '%v', expected '%v'", err, engine.ErrNilSubsystem) + } + + f.exchangeManager = engine.SetupExchangeManager() + err = f.UpdateFundingFromLiveData(false) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + + ff := &binance.Binance{} + ff.SetDefaults() + f.exchangeManager.Add(ff) + err = f.UpdateFundingFromLiveData(false) + if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { + t.Errorf("received '%v', expected '%v'", err, exchange.ErrCredentialsAreEmpty) + } + + // enter api keys to gain coverage here + apiKey := "" + apiSec := "" + subAccount := "" + if apiKey == "" || apiSec == "" { + // this test requires auth to get coverage + return + } + ff.SetCredentials(apiKey, apiSec, "", subAccount, "", "") + + err = f.UpdateFundingFromLiveData(true) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + + err = f.UpdateFundingFromLiveData(false) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } +} + +func TestUpdateAllCollateral(t *testing.T) { + t.Parallel() + f := &FundManager{} + err := f.UpdateAllCollateral(false, false) + if !errors.Is(err, engine.ErrNilSubsystem) { + t.Errorf("received '%v', expected '%v'", err, engine.ErrNilSubsystem) + } + + f.exchangeManager = engine.SetupExchangeManager() + err = f.UpdateAllCollateral(false, false) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + + ff := &binance.Binance{} + ff.SetDefaults() + f.exchangeManager.Add(ff) + err = f.UpdateAllCollateral(false, false) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNotYetImplemented) + } + + f.items = []*Item{ + { + exchange: exchName, + asset: asset.Spot, + currency: currency.BTC, + isCollateral: true, + }, + } + err = f.UpdateAllCollateral(false, false) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNotYetImplemented) + } + + f.items[0].trackingCandles = kline.NewDataFromKline() + err = f.items[0].trackingCandles.SetStream([]data.Event{ + &fakeEvent{}, + }) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } + + err = f.UpdateAllCollateral(false, false) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNotYetImplemented) + } + + f.items[0].asset = asset.Futures + err = f.UpdateAllCollateral(false, false) + if !errors.Is(err, gctcommon.ErrNotYetImplemented) { + t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNotYetImplemented) + } + + apiKey := "" + apiSec := "" + subAccount := "" + if apiKey == "" || apiSec == "" { + // this test requires auth to get coverage + return + } + ff.SetCredentials(apiKey, apiSec, "", subAccount, "", "") + err = f.UpdateAllCollateral(true, true) + if !errors.Is(err, nil) { + t.Errorf("received '%v', expected '%v'", err, nil) + } +} + +var leet = decimal.NewFromInt(1337) + +// fakeEvent implements common.Event without +// caring about the response, or dealing with import cycles +type fakeEvent struct{} + +func (f *fakeEvent) GetHighPrice() decimal.Decimal { return leet } +func (f *fakeEvent) GetLowPrice() decimal.Decimal { return leet } +func (f *fakeEvent) GetOpenPrice() decimal.Decimal { return leet } +func (f *fakeEvent) GetVolume() decimal.Decimal { return leet } +func (f *fakeEvent) GetOffset() int64 { return 0 } +func (f *fakeEvent) SetOffset(int64) {} +func (f *fakeEvent) IsEvent() bool { return true } +func (f *fakeEvent) GetTime() time.Time { return time.Now() } +func (f *fakeEvent) Pair() currency.Pair { return pair } +func (f *fakeEvent) GetExchange() string { return exchName } +func (f *fakeEvent) GetInterval() gctkline.Interval { return gctkline.OneMin } +func (f *fakeEvent) GetAssetType() asset.Item { return asset.Spot } +func (f *fakeEvent) AppendReason(string) {} +func (f *fakeEvent) GetClosePrice() decimal.Decimal { return elite } +func (f *fakeEvent) AppendReasonf(s string, i ...interface{}) {} +func (f *fakeEvent) GetBase() *event.Base { return &event.Base{} } +func (f *fakeEvent) GetUnderlyingPair() currency.Pair { return pair } +func (f *fakeEvent) GetConcatReasons() string { return "" } +func (f *fakeEvent) GetReasons() []string { return nil } diff --git a/backtester/funding/funding_types.go b/backtester/funding/funding_types.go index e3024584..d36f5ed5 100644 --- a/backtester/funding/funding_types.go +++ b/backtester/funding/funding_types.go @@ -1,6 +1,7 @@ package funding import ( + "errors" "time" "github.com/shopspring/decimal" @@ -8,26 +9,49 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) +var ( + // ErrFundsNotFound used when funds are requested but the funding is not found in the manager + ErrFundsNotFound = errors.New("funding not found") + // ErrAlreadyExists used when a matching item or pair is already in the funding manager + ErrAlreadyExists = errors.New("funding already exists") + // ErrUSDTrackingDisabled used when attempting to track USD values when disabled + ErrUSDTrackingDisabled = errors.New("USD tracking disabled") + + errCannotAllocate = errors.New("cannot allocate funds") + errZeroAmountReceived = errors.New("amount received less than or equal to zero") + errNegativeAmountReceived = errors.New("received negative decimal") + errNotEnoughFunds = errors.New("not enough funds") + errCannotTransferToSameFunds = errors.New("cannot send funds to self") + errTransferMustBeSameCurrency = errors.New("cannot transfer to different currency") + errCannotMatchTrackingToItem = errors.New("cannot match tracking data to funding items") + errNotFutures = errors.New("item linking collateral currencies must be a futures asset") + errExchangeManagerRequired = errors.New("exchange manager required") +) + // IFundingManager limits funding usage for portfolio event handling type IFundingManager interface { - Reset() + Reset() error IsUsingExchangeLevelFunding() bool - GetFundingForEvent(common.EventHandler) (IFundingPair, error) + GetFundingForEvent(common.Event) (IFundingPair, error) Transfer(decimal.Decimal, *Item, *Item, bool) error - GenerateReport() *Report + GenerateReport() (*Report, error) AddUSDTrackingData(*kline.DataFromKline) error - CreateSnapshot(time.Time) + CreateSnapshot(time.Time) error USDTrackingDisabled() bool - Liquidate(common.EventHandler) - GetAllFunding() []BasicItem - UpdateCollateral(common.EventHandler) error + Liquidate(common.Event) error + GetAllFunding() ([]BasicItem, error) + UpdateCollateralForEvent(common.Event, bool) error + UpdateAllCollateral(isLive, hasUpdateFunding bool) error + UpdateFundingFromLiveData(hasUpdatedFunding bool) error HasFutures() bool - HasExchangeBeenLiquidated(handler common.EventHandler) bool + HasExchangeBeenLiquidated(handler common.Event) bool RealisePNL(receivingExchange string, receivingAsset asset.Item, receivingCurrency currency.Code, realisedPNL decimal.Decimal) error + SetFunding(string, asset.Item, *account.Balance, bool) error } // IFundingTransferer allows for funding amounts to be transferred @@ -35,15 +59,15 @@ type IFundingManager interface { type IFundingTransferer interface { IsUsingExchangeLevelFunding() bool Transfer(decimal.Decimal, *Item, *Item, bool) error - GetFundingForEvent(common.EventHandler) (IFundingPair, error) - HasExchangeBeenLiquidated(handler common.EventHandler) bool + GetFundingForEvent(common.Event) (IFundingPair, error) + HasExchangeBeenLiquidated(handler common.Event) bool } // IFundingReader is a simple interface of // IFundingManager for readonly access at portfolio // manager type IFundingReader interface { - GetFundingForEvent(common.EventHandler) (IFundingPair, error) + GetFundingForEvent(common.Event) (IFundingPair, error) GetAllFunding() []BasicItem } @@ -120,6 +144,7 @@ type FundManager struct { disableUSDTracking bool items []*Item exchangeManager *engine.ExchangeManager + verbose bool } // Item holds funding data per currency item @@ -136,6 +161,7 @@ type Item struct { snapshot map[int64]ItemSnapshot isCollateral bool isLiquidated bool + appendedViaAPI bool collateralCandles map[currency.Code]kline.DataFromKline } @@ -190,8 +216,9 @@ type ReportItem struct { USDPairCandle *kline.DataFromKline Difference decimal.Decimal ShowInfinite bool - PairedWith currency.Code IsCollateral bool + AppendedViaAPI bool + PairedWith currency.Code } // ItemSnapshot holds USD values to allow for tracking diff --git a/backtester/funding/trackingcurrencies/trackingcurrencies.go b/backtester/funding/trackingcurrencies/trackingcurrencies.go index 0d85e31c..63708975 100644 --- a/backtester/funding/trackingcurrencies/trackingcurrencies.go +++ b/backtester/funding/trackingcurrencies/trackingcurrencies.go @@ -105,7 +105,7 @@ func CreateUSDTrackingPairs(tp []TrackingPair, em *engine.ExchangeManager) ([]Tr // tracks against USD value, ie is in rankedUSDs func CurrencyIsUSDTracked(code currency.Code) bool { for i := range rankedUSDs { - if code == rankedUSDs[i] { + if code.Equal(rankedUSDs[i]) { return true } } diff --git a/backtester/funding/trackingcurrencies/trackingcurrencies_test.go b/backtester/funding/trackingcurrencies/trackingcurrencies_test.go index aeaad61d..045bfa26 100644 --- a/backtester/funding/trackingcurrencies/trackingcurrencies_test.go +++ b/backtester/funding/trackingcurrencies/trackingcurrencies_test.go @@ -4,16 +4,17 @@ import ( "errors" "testing" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) var ( - exch = "binance" - a = asset.Spot - b = currency.BTC - q = currency.USDT + eName = "binance" + a = asset.Spot + b = currency.BTC + q = currency.USDT ) func TestCreateUSDTrackingPairs(t *testing.T) { @@ -30,41 +31,53 @@ func TestCreateUSDTrackingPairs(t *testing.T) { } em := engine.SetupExchangeManager() - _, err = CreateUSDTrackingPairs([]TrackingPair{{Exchange: exch}}, em) + _, err = CreateUSDTrackingPairs([]TrackingPair{{Exchange: eName}}, em) if !errors.Is(err, engine.ErrExchangeNotFound) { t.Errorf("received '%v' expected '%v'", err, engine.ErrExchangeNotFound) } s1 := TrackingPair{ - Exchange: exch, + Exchange: eName, Asset: a, Base: b, Quote: q, } - excher, err := em.NewExchangeByName(exch) + + exch, err := em.NewExchangeByName(eName) if err != nil { t.Fatal(err) } - _, err = excher.GetDefaultConfig() - if err != nil { - t.Fatal(err) - } - em.Add(excher) + exch.SetDefaults() + cp := currency.NewPair(s1.Base, s1.Quote) + cp2 := currency.NewPair(currency.LTC, currency.USDT) + cp3 := currency.NewPair(currency.LTC, currency.BTC) + exchB := exch.GetBase() + eba := exchB.CurrencyPairs.Pairs[a] + eba.Available = eba.Available.Add(cp) + eba.Enabled = eba.Enabled.Add(cp) + eba.Available = eba.Available.Add(cp2) + eba.Enabled = eba.Enabled.Add(cp2) + eba.Available = eba.Available.Add(cp3) + eba.Enabled = eba.Enabled.Add(cp3) + eba.AssetEnabled = convert.BoolPtr(true) + + em.Add(exch) resp, err := CreateUSDTrackingPairs([]TrackingPair{s1}, em) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if len(resp) != 1 { - t.Error("expected 1 currency setting as it contains a USD equiv") + t.Error("expected 1 currency setting as it contains a USDT equiv") } s1.Base = currency.LTC s1.Quote = currency.BTC + resp, err = CreateUSDTrackingPairs([]TrackingPair{s1}, em) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if len(resp) != 3 { - t.Error("expected 3 currency settings as it did not contain a USD equiv") + t.Error("expected 3 currency settings as it did not contain a USDT equiv") } } @@ -80,7 +93,7 @@ func TestFindMatchingUSDPairs(t *testing.T) { } tests := []testPair{ { - description: "already has USD", + description: "already has USDT", initialPair: currency.NewPair(currency.BTC, currency.USDT), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.USDT)}}, basePair: currency.EMPTYPAIR, @@ -96,7 +109,7 @@ func TestFindMatchingUSDPairs(t *testing.T) { expectedErr: nil, }, { - description: "quote currency has no matching USD pair", + description: "quote currency has no matching USDT pair", initialPair: currency.NewPair(currency.BTC, currency.LTC), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.LTC), currency.NewPair(currency.BTC, currency.DAI)}}, basePair: currency.NewPair(currency.BTC, currency.DAI), @@ -104,7 +117,7 @@ func TestFindMatchingUSDPairs(t *testing.T) { expectedErr: errNoMatchingQuoteUSDFound, }, { - description: "base currency has no matching USD pair", + description: "base currency has no matching USDT pair", initialPair: currency.NewPair(currency.BTC, currency.LTC), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.LTC), currency.NewPair(currency.LTC, currency.USDT)}}, basePair: currency.EMPTYPAIR, @@ -112,7 +125,7 @@ func TestFindMatchingUSDPairs(t *testing.T) { expectedErr: errNoMatchingBaseUSDFound, }, { - description: "both base and quote don't have USD pairs", + description: "both base and quote don't have USDT pairs", initialPair: currency.NewPair(currency.BTC, currency.LTC), availablePairs: ¤cy.PairStore{Available: currency.Pairs{currency.NewPair(currency.BTC, currency.LTC)}}, basePair: currency.EMPTYPAIR, @@ -167,7 +180,7 @@ func TestPairContainsUSD(t *testing.T) { { "usdltc", true, - currency.NewPair(currency.USD, currency.LTC), + currency.NewPair(currency.USDT, currency.LTC), }, { "btcdai", @@ -182,7 +195,7 @@ func TestPairContainsUSD(t *testing.T) { { "btcusd", true, - currency.NewPair(currency.BTC, currency.USD), + currency.NewPair(currency.BTC, currency.USDT), }, { "btcaud", diff --git a/backtester/main.go b/backtester/main.go index 4790835b..136333fb 100644 --- a/backtester/main.go +++ b/backtester/main.go @@ -4,8 +4,10 @@ import ( "encoding/json" "flag" "fmt" + "net/http" "os" "path/filepath" + "time" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/config" @@ -18,13 +20,13 @@ import ( "github.com/thrasher-corp/gocryptotrader/signaler" ) -var singleRunStrategyPath, templatePath, outputPath, btConfigDir, strategyPluginPath string -var printLogo, generateReport, darkReport, colourOutput, logSubHeader bool +var singleTaskStrategyPath, templatePath, outputPath, btConfigDir, strategyPluginPath, pprofURL string +var printLogo, generateReport, darkReport, colourOutput, logSubHeader, enablePProf bool func main() { wd, err := os.Getwd() if err != nil { - fmt.Printf("Could not get working directory. Error: %v.\n", err) + fmt.Printf("Could not get working directory. Error: %v\n", err) os.Exit(1) } @@ -34,34 +36,47 @@ func main() { btConfigDir = config.DefaultBTConfigDir log.Infof(log.Global, "Blank config received, using default path '%v'", btConfigDir) } + + if enablePProf { + go func() { + server := &http.Server{ + Addr: pprofURL, + ReadHeaderTimeout: time.Minute, + ReadTimeout: time.Minute, + } + + fmt.Println(server.ListenAndServe()) + }() + } + fe := file.Exists(btConfigDir) switch { case fe: btCfg, err = config.ReadBacktesterConfigFromPath(btConfigDir) if err != nil { - fmt.Printf("Could not read config. Error: %v.\n", err) + fmt.Printf("Could not read config. Error: %v\n", err) os.Exit(1) } case !fe && btConfigDir == config.DefaultBTConfigDir: btCfg, err = config.GenerateDefaultConfig() if err != nil { - fmt.Printf("Could not generate config. Error: %v.\n", err) + fmt.Printf("Could not generate config. Error: %v\n", err) os.Exit(1) } var btCfgJSON []byte btCfgJSON, err = json.MarshalIndent(btCfg, "", " ") if err != nil { - fmt.Printf("Could not generate config. Error: %v.\n", err) + fmt.Printf("Could not generate config. Error: %v\n", err) os.Exit(1) } err = os.MkdirAll(config.DefaultBTDir, file.DefaultPermissionOctal) if err != nil { - fmt.Printf("Could not generate config. Error: %v.\n", err) + fmt.Printf("Could not generate config. Error: %v\n", err) os.Exit(1) } err = os.WriteFile(btConfigDir, btCfgJSON, file.DefaultPermissionOctal) if err != nil { - fmt.Printf("Could not generate config. Error: %v.\n", err) + fmt.Printf("Could not generate config. Error: %v\n", err) os.Exit(1) } default: @@ -76,8 +91,8 @@ func main() { flagSet.WithBool("logsubheaders", &logSubHeader, btCfg.LogSubheaders) flagSet.WithBool("colouroutput", &colourOutput, btCfg.UseCMDColours) - if singleRunStrategyPath != "" && !file.Exists(singleRunStrategyPath) { - fmt.Printf("Strategy config path not found '%v'", singleRunStrategyPath) + if singleTaskStrategyPath != "" && !file.Exists(singleTaskStrategyPath) { + fmt.Printf("Strategy config path not found '%v'", singleTaskStrategyPath) os.Exit(1) } @@ -119,19 +134,19 @@ func main() { defaultLogSettings.AdvancedSettings.Headers.Error = common.CMDColours.Error + "[ERROR]" + common.CMDColours.Default err = log.SetGlobalLogConfig(defaultLogSettings) if err != nil { - fmt.Printf("Could not setup global logger. Error: %v.\n", err) + fmt.Printf("Could not setup global logger. Error: %v\n", err) os.Exit(1) } err = log.SetupGlobalLogger() if err != nil { - fmt.Printf("Could not setup global logger. Error: %v.\n", err) + fmt.Printf("Could not setup global logger. Error: %v\n", err) os.Exit(1) } err = common.RegisterBacktesterSubLoggers() if err != nil { - fmt.Printf("Could not register subloggers. Error: %v.\n", err) + fmt.Printf("Could not register subloggers. Error: %v\n", err) os.Exit(1) } @@ -145,18 +160,18 @@ func main() { if strategyPluginPath != "" { err = strategies.LoadCustomStrategies(strategyPluginPath) if err != nil { - fmt.Printf("Could not load custom strategies. Error: %v.\n", err) + fmt.Printf("Could not load custom strategies. Error: %v\n", err) os.Exit(1) } log.Infof(common.Backtester, "Loaded plugin %v\n", strategyPluginPath) } - if singleRunStrategyPath != "" { - dir := singleRunStrategyPath + if singleTaskStrategyPath != "" { + dir := singleTaskStrategyPath var cfg *config.Config cfg, err = config.ReadStrategyConfigFromFile(dir) if err != nil { - fmt.Printf("Could not read strategy config. Error: %v.\n", err) + fmt.Printf("Could not read strategy config. Error: %v\n", err) os.Exit(1) } var bt *backtest.BackTest @@ -169,23 +184,27 @@ func main() { }, }) if err != nil { - fmt.Printf("Could not execute strategy. Error: %v.\n", err) + fmt.Printf("Could not execute strategy. Error: %v\n", err) os.Exit(1) } if bt.MetaData.LiveTesting { err = bt.ExecuteStrategy(false) if err != nil { - fmt.Printf("Could execute strategy. Error: %v.\n", err) + fmt.Printf("Could not stop task %v %v. Error: %v\n", bt.MetaData.ID, bt.MetaData.Strategy, err) os.Exit(1) } interrupt := signaler.WaitForInterrupt() - log.Infof(log.Global, "Captured %v, shutdown requested.\n", interrupt) + log.Infof(log.Global, "Captured %v, shutdown requested\n", interrupt) log.Infoln(log.Global, "Exiting.") - bt.Stop() + err = bt.Stop() + if err != nil { + fmt.Printf("Could not stop task %v %v. Error: %v\n", bt.MetaData.ID, bt.MetaData.Strategy, err) + os.Exit(1) + } } else { err = bt.ExecuteStrategy(true) if err != nil { - fmt.Printf("Could execute strategy. Error: %v.\n", err) + fmt.Printf("Could not stop task %v %v. Error: %v\n", bt.MetaData.ID, bt.MetaData.Strategy, err) os.Exit(1) } } @@ -196,7 +215,7 @@ func main() { btCfg.Report.DarkMode = darkReport btCfg.Report.GenerateReport = generateReport - runManager := backtest.SetupRunManager() + runManager := backtest.NewTaskManager() go func(c *config.BacktesterConfig) { log.Info(log.GRPCSys, "Starting RPC server") @@ -204,14 +223,45 @@ func main() { s, err = backtest.SetupRPCServer(c, runManager) err = backtest.StartRPCServer(s) if err != nil { - fmt.Printf("Could not start RPC server. Error: %v.\n", err) + fmt.Printf("Could not start RPC server. Error: %v\n", err) os.Exit(1) } log.Info(log.GRPCSys, "Ready to receive commands") }(btCfg) interrupt := signaler.WaitForInterrupt() - log.Infof(log.Global, "Captured %v, shutdown requested.\n", interrupt) - log.Infoln(log.Global, "Exiting.") + log.Infof(log.Global, "Captured %v, shutdown requested\n", interrupt) + if btCfg.StopAllTasksOnClose { + log.Infoln(log.Global, "Stopping all running tasks on close") + var stopped []*backtest.TaskSummary + stopped, err = runManager.StopAllTasks() + if err != nil { + log.Error(common.Backtester, err) + } + for i := range stopped { + log.Infof(common.Backtester, "Task %v %v was stopped", stopped[i].MetaData.ID, stopped[i].MetaData.Strategy) + } + } else { + var tasks []*backtest.TaskSummary + tasks, err = runManager.List() + if err != nil { + log.Error(common.Backtester, err) + } + for i := range tasks { + if tasks[i].MetaData.ClosePositionsOnStop && !tasks[i].MetaData.Closed { + err = runManager.StopTask(tasks[i].MetaData.ID) + if err != nil { + log.Error(common.Backtester, err) + continue + } + log.Infof(common.Backtester, "Task %v %v was stopped", tasks[i].MetaData.ID, tasks[i].MetaData.Strategy) + } + } + } + log.Infoln(log.Global, "Exiting. Have a nice day") + err = log.CloseLogger() + if err != nil { + fmt.Println(err) + } } func parseFlags(wd string) map[string]bool { @@ -229,7 +279,7 @@ func parseFlags(wd string) map[string]bool { wd, "results") flag.StringVar( - &singleRunStrategyPath, + &singleTaskStrategyPath, "singlerunstrategypath", "", fmt.Sprintf("path to a strategy file. Will execute strategy and exit, instead of creating a GRPC server. Example %v", defaultStrategy)) @@ -261,7 +311,7 @@ func parseFlags(wd string) map[string]bool { flag.BoolVar( &colourOutput, "colouroutput", - false, + true, "if enabled, will print in colours, if your terminal supports \033[38;5;99m[colours like this]\u001b[0m") flag.BoolVar( &logSubHeader, @@ -278,6 +328,16 @@ func parseFlags(wd string) map[string]bool { "strategypluginpath", "", "example path: "+filepath.Join(wd, "plugins", "strategies", "example", "example.so")) + flag.BoolVar( + &enablePProf, + "enablepprof", + false, + "if enabled, runs a pprof server for debugging") + flag.StringVar( + &pprofURL, + "pprofurl", + "http://localhost:6060", + "") flag.Parse() // collect flags flags := make(map[string]bool) diff --git a/backtester/report/chart.go b/backtester/report/chart.go index b9c35ca9..35540857 100644 --- a/backtester/report/chart.go +++ b/backtester/report/chart.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) @@ -14,10 +14,10 @@ import ( // to show how much the overall assets are worth over time func createUSDTotalsChart(items []statistics.ValueAtTime, stats []statistics.FundingItemStatistics) (*Chart, error) { if items == nil { - return nil, fmt.Errorf("%w missing values at time", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing values at time", gctcommon.ErrNilPointer) } if stats == nil { - return nil, fmt.Errorf("%w missing funding item statistics", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing funding item statistics", gctcommon.ErrNilPointer) } response := &Chart{ AxisType: "logarithmic", @@ -36,6 +36,9 @@ func createUSDTotalsChart(items []statistics.ValueAtTime, stats []statistics.Fun for i := range stats { var plots []LinePlot + if stats[i].ReportItem.AppendedViaAPI { + continue + } for j := range stats[i].ReportItem.Snapshots { if stats[i].ReportItem.Snapshots[j].Available.IsZero() { response.ShowZeroDisclaimer = true @@ -58,13 +61,16 @@ func createUSDTotalsChart(items []statistics.ValueAtTime, stats []statistics.Fun // to show how many holdings of each type was held over the time of backtesting func createHoldingsOverTimeChart(stats []statistics.FundingItemStatistics) (*Chart, error) { if stats == nil { - return nil, fmt.Errorf("%w missing funding item statistics", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing funding item statistics", gctcommon.ErrNilPointer) } response := &Chart{ AxisType: "logarithmic", } for i := range stats { var plots []LinePlot + if stats[i].ReportItem.AppendedViaAPI { + continue + } for j := range stats[i].ReportItem.Snapshots { if stats[i].ReportItem.Snapshots[j].Available.IsZero() { response.ShowZeroDisclaimer = true @@ -85,41 +91,44 @@ func createHoldingsOverTimeChart(stats []statistics.FundingItemStatistics) (*Cha // createPNLCharts shows a running history of all realised and unrealised PNL values // over time -func createPNLCharts(items map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) (*Chart, error) { +func createPNLCharts(items map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) (*Chart, error) { if items == nil { - return nil, fmt.Errorf("%w missing currency pair statistics", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing currency pair statistics", gctcommon.ErrNilPointer) } response := &Chart{ AxisType: "linear", } for exch, assetMap := range items { - for item, pairMap := range assetMap { - for pair, result := range pairMap { - id := fmt.Sprintf("%v %v %v", - exch, - item, - pair) - uPNLName := fmt.Sprintf("%v Unrealised PNL", id) - rPNLName := fmt.Sprintf("%v Realised PNL", id) + for item, baseMap := range assetMap { + for b, quoteMap := range baseMap { + for q, result := range quoteMap { + id := fmt.Sprintf("%v %v %v%v", + exch, + item, + b, + q) + uPNLName := fmt.Sprintf("%v Unrealised PNL", id) + rPNLName := fmt.Sprintf("%v Realised PNL", id) - unrealisedPNL := ChartLine{Name: uPNLName} - realisedPNL := ChartLine{Name: rPNLName} - for i := range result.Events { - if result.Events[i].PNL != nil { - realisedPNL.LinePlots = append(realisedPNL.LinePlots, LinePlot{ - Value: result.Events[i].PNL.GetRealisedPNL().PNL.InexactFloat64(), - UnixMilli: result.Events[i].Time.UnixMilli(), - }) - unrealisedPNL.LinePlots = append(unrealisedPNL.LinePlots, LinePlot{ - Value: result.Events[i].PNL.GetUnrealisedPNL().PNL.InexactFloat64(), - UnixMilli: result.Events[i].Time.UnixMilli(), - }) + unrealisedPNL := ChartLine{Name: uPNLName} + realisedPNL := ChartLine{Name: rPNLName} + for i := range result.Events { + if result.Events[i].PNL != nil { + realisedPNL.LinePlots = append(realisedPNL.LinePlots, LinePlot{ + Value: result.Events[i].PNL.GetRealisedPNL().PNL.InexactFloat64(), + UnixMilli: result.Events[i].Time.UnixMilli(), + }) + unrealisedPNL.LinePlots = append(unrealisedPNL.LinePlots, LinePlot{ + Value: result.Events[i].PNL.GetUnrealisedPNL().PNL.InexactFloat64(), + UnixMilli: result.Events[i].Time.UnixMilli(), + }) + } } + if len(unrealisedPNL.LinePlots) == 0 || len(realisedPNL.LinePlots) == 0 { + continue + } + response.Data = append(response.Data, unrealisedPNL, realisedPNL) } - if len(unrealisedPNL.LinePlots) == 0 || len(realisedPNL.LinePlots) == 0 { - continue - } - response.Data = append(response.Data, unrealisedPNL, realisedPNL) } } } @@ -128,37 +137,39 @@ func createPNLCharts(items map[string]map[asset.Item]map[currency.Pair]*statisti // createFuturesSpotDiffChart highlights the difference in futures and spot prices // over time -func createFuturesSpotDiffChart(items map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) (*Chart, error) { +func createFuturesSpotDiffChart(items map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) (*Chart, error) { if items == nil { - return nil, fmt.Errorf("%w missing currency pair statistics", common.ErrNilArguments) + return nil, fmt.Errorf("%w missing currency pair statistics", gctcommon.ErrNilPointer) } currs := make(map[currency.Pair]linkCurrencyDiff) response := &Chart{ AxisType: "linear", } - upperFormat := currency.PairFormat{Uppercase: true} for _, assetMap := range items { - for item, pairMap := range assetMap { - for pair, result := range pairMap { - if item.IsFutures() { - p := result.UnderlyingPair.Format(upperFormat) - diff, ok := currs[p] - if !ok { - diff = linkCurrencyDiff{} + for item, baseMap := range assetMap { + for b, quoteMap := range baseMap { + for q, result := range quoteMap { + cp := currency.NewPair(b.Currency(), q.Currency()) + if item.IsFutures() { + p := result.UnderlyingPair.Format(currency.EMPTYFORMAT) + diff, ok := currs[p] + if !ok { + diff = linkCurrencyDiff{} + } + diff.FuturesPair = cp + diff.SpotPair = p + diff.FuturesEvents = result.Events + currs[p] = diff + } else { + p := cp.Format(currency.EMPTYFORMAT) + diff, ok := currs[p] + if !ok { + diff = linkCurrencyDiff{} + } + diff.SpotEvents = result.Events + currs[p] = diff } - diff.FuturesPair = pair - diff.SpotPair = p - diff.FuturesEvents = result.Events - currs[p] = diff - } else { - p := pair.Format(upperFormat) - diff, ok := currs[p] - if !ok { - diff = linkCurrencyDiff{} - } - diff.SpotEvents = result.Events - currs[p] = diff } } } diff --git a/backtester/report/chart_test.go b/backtester/report/chart_test.go index 642961cf..73304e3d 100644 --- a/backtester/report/chart_test.go +++ b/backtester/report/chart_test.go @@ -6,11 +6,11 @@ import ( "time" "github.com/shopspring/decimal" - "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" evkline "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/backtester/funding" + gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" @@ -20,8 +20,8 @@ import ( func TestCreateUSDTotalsChart(t *testing.T) { t.Parallel() _, err := createUSDTotalsChart(nil, nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } tt := time.Now() items := []statistics.ValueAtTime{ @@ -32,8 +32,8 @@ func TestCreateUSDTotalsChart(t *testing.T) { }, } _, err = createUSDTotalsChart(items, nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } stats := []statistics.FundingItemStatistics{ { @@ -65,8 +65,8 @@ func TestCreateUSDTotalsChart(t *testing.T) { func TestCreateHoldingsOverTimeChart(t *testing.T) { t.Parallel() _, err := createHoldingsOverTimeChart(nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } tt := time.Now() items := []statistics.FundingItemStatistics{ @@ -100,17 +100,18 @@ func TestCreateHoldingsOverTimeChart(t *testing.T) { func TestCreatePNLCharts(t *testing.T) { t.Parallel() _, err := createPNLCharts(nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } tt := time.Now() var d Data d.Statistics = &statistics.Statistic{} - d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.NewPair(currency.BTC, currency.USDT)] = &statistics.CurrencyPairStatistic{ + d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item] = &statistics.CurrencyPairStatistic{ Events: []statistics.DataAtOffset{ { PNL: &portfolio.PNLSummary{ @@ -128,7 +129,7 @@ func TestCreatePNLCharts(t *testing.T) { }, } - d.AddKlineItem(&gctkline.Item{ + err = d.SetKlineData(&gctkline.Item{ Exchange: testExchange, Pair: currency.NewPair(currency.BTC, currency.USDT), Asset: asset.Spot, @@ -144,9 +145,12 @@ func TestCreatePNLCharts(t *testing.T) { }, }, }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } err = d.enhanceCandles() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } _, err = createPNLCharts(d.Statistics.ExchangeAssetPairStatistics) @@ -158,8 +162,8 @@ func TestCreatePNLCharts(t *testing.T) { func TestCreateFuturesSpotDiffChart(t *testing.T) { t.Parallel() _, err := createFuturesSpotDiffChart(nil) - if !errors.Is(err, common.ErrNilArguments) { - t.Errorf("received '%v' expected '%v'", err, common.ErrNilArguments) + if !errors.Is(err, gctcommon.ErrNilPointer) { + t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } tt := time.Now() @@ -167,10 +171,11 @@ func TestCreateFuturesSpotDiffChart(t *testing.T) { cp2 := currency.NewPair(currency.BTC, currency.DOGE) var d Data d.Statistics = &statistics.Statistic{} - d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][cp] = &statistics.CurrencyPairStatistic{ + d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USD.Item] = &statistics.CurrencyPairStatistic{ Currency: cp, Events: []statistics.DataAtOffset{ { @@ -190,8 +195,9 @@ func TestCreateFuturesSpotDiffChart(t *testing.T) { }, }, } - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures][cp2] = &statistics.CurrencyPairStatistic{ + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Futures][currency.BTC.Item][currency.DOGE.Item] = &statistics.CurrencyPairStatistic{ UnderlyingPair: cp, Currency: cp2, Events: []statistics.DataAtOffset{ diff --git a/backtester/report/report.go b/backtester/report/report.go index a95327a0..557c8966 100644 --- a/backtester/report/report.go +++ b/backtester/report/report.go @@ -107,20 +107,23 @@ func (d *Data) GenerateReport() error { return nil } -// AddKlineItem appends a SET of candles for the report to enhance upon -// generation -func (d *Data) AddKlineItem(k *kline.Item) { - d.OriginalCandles = append(d.OriginalCandles, k) -} - -// UpdateItem updates an existing kline item for LIVE data usage -func (d *Data) UpdateItem(k *kline.Item) { +// SetKlineData updates an existing kline item for LIVE data usage +func (d *Data) SetKlineData(k *kline.Item) error { if len(d.OriginalCandles) == 0 { d.OriginalCandles = append(d.OriginalCandles, k) - } else { - d.OriginalCandles[0].Candles = append(d.OriginalCandles[0].Candles, k.Candles...) - d.OriginalCandles[0].RemoveDuplicates() + return nil } + for i := range d.OriginalCandles { + err := d.OriginalCandles[i].EqualSource(k) + if err != nil { + continue + } + d.OriginalCandles[i].Candles = append(d.OriginalCandles[i].Candles, k.Candles...) + d.OriginalCandles[i].RemoveDuplicateCandlesByTime() + return nil + } + d.OriginalCandles = append(d.OriginalCandles, k) + return nil } // enhanceCandles will enhance candle data with order information allowing @@ -145,7 +148,7 @@ func (d *Data) enhanceCandles() error { } statsForCandles := - d.Statistics.ExchangeAssetPairStatistics[lookup.Exchange][lookup.Asset][lookup.Pair] + d.Statistics.ExchangeAssetPairStatistics[lookup.Exchange][lookup.Asset][lookup.Pair.Base.Item][lookup.Pair.Quote.Item] if statsForCandles == nil { continue } @@ -217,11 +220,12 @@ func (d *Data) enhanceCandles() error { } func (d *DetailedCandle) copyCloseFromPreviousEvent(ek *EnhancedKline) { + cp := ek.Candles[len(ek.Candles)-1].Close // if the data is missing, ensure that all values just continue the previous candle's close price visually - d.Open = ek.Candles[len(ek.Candles)-1].Close - d.High = ek.Candles[len(ek.Candles)-1].Close - d.Low = ek.Candles[len(ek.Candles)-1].Close - d.Close = ek.Candles[len(ek.Candles)-1].Close + d.Open = cp + d.High = cp + d.Low = cp + d.Close = cp d.Colour = "white" d.Position = "aboveBar" d.Shape = "arrowDown" diff --git a/backtester/report/report_test.go b/backtester/report/report_test.go index b0df8c37..70c2e393 100644 --- a/backtester/report/report_test.go +++ b/backtester/report/report_test.go @@ -234,19 +234,21 @@ func TestGenerateReport(t *testing.T) { }, StrategyName: "testStrat", RiskFreeRate: decimal.NewFromFloat(0.03), - ExchangeAssetPairStatistics: map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic{ + ExchangeAssetPairStatistics: map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic{ e: { a: { - p: &statistics.CurrencyPairStatistic{ - LowestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(100)}, - HighestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(200)}, - MarketMovement: decimal.NewFromInt(100), - StrategyMovement: decimal.NewFromInt(100), - CompoundAnnualGrowthRate: decimal.NewFromInt(1), - BuyOrders: 1, - SellOrders: 1, - ArithmeticRatios: &statistics.Ratios{}, - GeometricRatios: &statistics.Ratios{}, + p.Base.Item: { + p.Quote.Item: &statistics.CurrencyPairStatistic{ + LowestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(100)}, + HighestClosePrice: statistics.ValueAtTime{Value: decimal.NewFromInt(200)}, + MarketMovement: decimal.NewFromInt(100), + StrategyMovement: decimal.NewFromInt(100), + CompoundAnnualGrowthRate: decimal.NewFromInt(1), + BuyOrders: 1, + SellOrders: 1, + ArithmeticRatios: &statistics.Ratios{}, + GeometricRatios: &statistics.Ratios{}, + }, }, }, }, @@ -323,23 +325,27 @@ func TestEnhanceCandles(t *testing.T) { if !errors.Is(err, errNoCandles) { t.Errorf("received: %v, expected: %v", err, errNoCandles) } - d.AddKlineItem(&gctkline.Item{}) + err = d.SetKlineData(&gctkline.Item{}) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } err = d.enhanceCandles() if !errors.Is(err, errStatisticsUnset) { t.Errorf("received: %v, expected: %v", err, errStatisticsUnset) } d.Statistics = &statistics.Statistic{} err = d.enhanceCandles() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.NewPair(currency.BTC, currency.USDT)] = &statistics.CurrencyPairStatistic{} + d.Statistics.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange] = make(map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot] = make(map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item] = make(map[*currency.Item]*statistics.CurrencyPairStatistic) + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item] = &statistics.CurrencyPairStatistic{} - d.AddKlineItem(&gctkline.Item{ + err = d.SetKlineData(&gctkline.Item{ Exchange: testExchange, Pair: currency.NewPair(currency.BTC, currency.USDT), Asset: asset.Spot, @@ -355,12 +361,15 @@ func TestEnhanceCandles(t *testing.T) { }, }, }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } err = d.enhanceCandles() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - d.AddKlineItem(&gctkline.Item{ + err = d.SetKlineData(&gctkline.Item{ Exchange: testExchange, Pair: currency.NewPair(currency.BTC, currency.USDT), Asset: asset.Spot, @@ -384,13 +393,16 @@ func TestEnhanceCandles(t *testing.T) { }, }, }) - - err = d.enhanceCandles() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.NewPair(currency.BTC, currency.USDT)].FinalOrders = compliance.Snapshot{ + err = d.enhanceCandles() + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } + + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item].FinalOrders = compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ { ClosePrice: decimal.NewFromInt(1335), @@ -403,11 +415,11 @@ func TestEnhanceCandles(t *testing.T) { Timestamp: tt, } err = d.enhanceCandles() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.NewPair(currency.BTC, currency.USDT)].FinalOrders = compliance.Snapshot{ + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item].FinalOrders = compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ { ClosePrice: decimal.NewFromInt(1335), @@ -423,11 +435,11 @@ func TestEnhanceCandles(t *testing.T) { Timestamp: tt, } err = d.enhanceCandles() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } - d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.NewPair(currency.BTC, currency.USDT)].FinalOrders = compliance.Snapshot{ + d.Statistics.ExchangeAssetPairStatistics[testExchange][asset.Spot][currency.BTC.Item][currency.USDT.Item].FinalOrders = compliance.Snapshot{ Orders: []compliance.SnapshotOrder{ { ClosePrice: decimal.NewFromInt(1335), @@ -443,8 +455,8 @@ func TestEnhanceCandles(t *testing.T) { Timestamp: tt, } err = d.enhanceCandles() - if err != nil { - t.Error(err) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) } if len(d.EnhancedCandles) == 0 { @@ -456,37 +468,46 @@ func TestUpdateItem(t *testing.T) { t.Parallel() d := Data{} tt := time.Now() - d.UpdateItem(&gctkline.Item{ + err := d.SetKlineData(&gctkline.Item{ Candles: []gctkline.Candle{ { Time: tt, }, }, }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if len(d.OriginalCandles) != 1 { t.Fatal("expected Original Candles len of 1") } if len(d.OriginalCandles[0].Candles) != 1 { t.Error("expected one candle") } - d.UpdateItem(&gctkline.Item{ + err = d.SetKlineData(&gctkline.Item{ Candles: []gctkline.Candle{ { Time: tt, }, }, }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if len(d.OriginalCandles[0].Candles) != 1 { t.Error("expected one candle") } - d.UpdateItem(&gctkline.Item{ + err = d.SetKlineData(&gctkline.Item{ Candles: []gctkline.Candle{ { Time: tt.Add(1), }, }, }) + if !errors.Is(err, nil) { + t.Errorf("received: %v, expected: %v", err, nil) + } if len(d.OriginalCandles[0].Candles) != 2 { t.Error("expected two candles") } diff --git a/backtester/report/report_types.go b/backtester/report/report_types.go index 867c6901..cff3d43b 100644 --- a/backtester/report/report_types.go +++ b/backtester/report/report_types.go @@ -24,8 +24,7 @@ var ( // Handler contains all functions required to generate statistical reporting for backtesting results type Handler interface { GenerateReport() error - AddKlineItem(*kline.Item) - UpdateItem(*kline.Item) + SetKlineData(*kline.Item) error UseDarkMode(bool) } diff --git a/backtester/report/tpl.gohtml b/backtester/report/tpl.gohtml index a8ca275d..e0e5398a 100644 --- a/backtester/report/tpl.gohtml +++ b/backtester/report/tpl.gohtml @@ -223,7 +223,7 @@ {{ range .Statistics.FundingStatistics.Report.Items}} - {{ if .IsCollateral}} + {{ if or .IsCollateral .AppendedViaAPI}} {{else }} {{.Exchange}} @@ -260,7 +260,7 @@ {{ range .Statistics.FundingStatistics.Report.Items}} - {{ if .IsCollateral}} + {{ if .IsCollateral }} {{.Exchange}} {{.Asset}} @@ -294,13 +294,15 @@ {{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}} {{ range $asset, $unused := .}} - {{ range $pair, $unused := .}} - - {{ $exchange}} - {{ $asset}} - {{ $pair}} - {{ $.Prettify.Decimal8 .MarketMovement}}% - + {{ range $base, $unused := .}} + {{ range $quote, $unused := .}} + + {{ $exchange}} + {{ $asset}} + {{ $base}}-{{$quote}} + {{ $.Prettify.Decimal8 .MarketMovement}}% + + {{end}} {{end}} {{end}} {{end}} @@ -329,22 +331,24 @@ {{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}} {{ range $asset, $unused := .}} - {{ range $pair, $unused := .}} - - {{ $exchange}} - {{ $asset}} - {{ $pair}} - {{ $.Prettify.Decimal8 .InitialHoldings.BaseInitialFunds }} {{.FinalHoldings.Pair.Base}} - {{ $.Prettify.Decimal8 .InitialHoldings.QuoteInitialFunds }} {{.FinalHoldings.Pair.Quote}} - {{ $.Prettify.Decimal8 .InitialHoldings.TotalInitialValue }} {{.FinalHoldings.Pair.Quote}} - {{ $.Prettify.Decimal8 .FinalHoldings.BaseSize }} {{ .FinalHoldings.Pair.Base}} - {{ $.Prettify.Decimal8 .FinalHoldings.QuoteSize }} {{ .FinalHoldings.Pair.Quote}} - {{ $.Prettify.Decimal8 .FinalHoldings.TotalValue }} {{ .FinalHoldings.Pair.Quote}} - {{ .IsStrategyProfitable }} - {{ .DoesPerformanceBeatTheMarket }} - {{ $.Prettify.Decimal8 .StrategyMovement }}% - {{ $.Prettify.Decimal8 .MarketMovement}}% - + {{ range $base, $unused := .}} + {{ range $quote, $unused := .}} + + {{ $exchange}} + {{ $asset}} + {{ $base}}-{{$quote}} + {{ $.Prettify.Decimal8 .InitialHoldings.BaseInitialFunds }} {{.FinalHoldings.Pair.Base}} + {{ $.Prettify.Decimal8 .InitialHoldings.QuoteInitialFunds }} {{.FinalHoldings.Pair.Quote}} + {{ $.Prettify.Decimal8 .InitialHoldings.TotalInitialValue }} {{.FinalHoldings.Pair.Quote}} + {{ $.Prettify.Decimal8 .FinalHoldings.BaseSize }} {{ .FinalHoldings.Pair.Base}} + {{ $.Prettify.Decimal8 .FinalHoldings.QuoteSize }} {{ .FinalHoldings.Pair.Quote}} + {{ $.Prettify.Decimal8 .FinalHoldings.TotalValue }} {{ .FinalHoldings.Pair.Quote}} + {{ .IsStrategyProfitable }} + {{ .DoesPerformanceBeatTheMarket }} + {{ $.Prettify.Decimal8 .StrategyMovement }}% + {{ $.Prettify.Decimal8 .MarketMovement}}% + + {{end}} {{end}} {{end}} {{end}} @@ -467,6 +471,8 @@ {{ range .Statistics.FundingStatistics.Report.Items}} + {{ if .AppendedViaAPI}} + {{else}} {{.Exchange}} {{.Asset}} @@ -477,6 +483,7 @@ {{ $.Prettify.Decimal64 .TransferFee}} {{ .IsCollateral }} + {{end}} {{end}} @@ -1224,116 +1231,151 @@ {{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}} {{ range $asset, $unused := .}} - {{ range $pair, $val := .}} -
+ {{ range $base, $val := .}} + {{ range $quote, $val := .}} +
-

Pair Statistics for {{$exchange}} {{ $asset}} {{ $pair}}

+

Pair Statistics for {{$exchange}} {{ $asset}} {{ $base}}-{{$quote}}

- - - {{ if $val.Asset.IsFutures }} - - - - - - - - - - - - - - - - - - - - - - - - - {{ else }} - - - - - - - - - - - - - - - - - - - - - - - - - {{end}} +
Long Orders{{ $.Prettify.Int $val.LongOrders}}
Short Orders{{ $.Prettify.Int $val.ShortOrders}}
Lowest Unrealised PNL{{ $.Prettify.Decimal8 $val.LowestUnrealisedPNL.Value}} at {{ $val.LowestUnrealisedPNL.Time}}
Highest Unrealised PNL{{ $.Prettify.Decimal8 $val.HighestUnrealisedPNL.Value}} at {{ $val.HighestUnrealisedPNL.Time}}
Lowest Realised PNL{{ $.Prettify.Decimal8 $val.LowestRealisedPNL.Value}} at {{ $val.LowestRealisedPNL.Time}}
Highest Realised PNL{{ $.Prettify.Decimal8 $val.HighestRealisedPNL.Value}} at {{ $val.HighestRealisedPNL.Time}}
Base Initial Funds{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseInitialFunds}} {{$val.FinalHoldings.Pair.Base}}
Quote Initial Funds{{ $.Prettify.Decimal8 $val.FinalHoldings.QuoteInitialFunds}} {{$val.FinalHoldings.Pair.Quote}}
Buy Orders{{ $.Prettify.Int $val.BuyOrders}}
Buy Amount{{ $.Prettify.Decimal8 $val.FinalHoldings.BoughtAmount}} {{$val.FinalHoldings.Pair.Base}}
Sell Orders{{ $.Prettify.Int $val.SellOrders}}
Sell Amount{{ $.Prettify.Decimal8 $val.FinalHoldings.SoldAmount}} {{$val.FinalHoldings.Pair.Base}}
+ + {{ if $val.Asset.IsFutures }} - - + + - {{ if $val.MaxDrawdown.Highest.Value.IsZero }} - {{else}} + + + + + + + + + + + + + + + + + + + + + {{ else }} + + + + + + + + + + + + + + + + + + + + + + + + + {{end}} + + + + + {{ if $val.MaxDrawdown.Highest.Value.IsZero }} + {{else}} + + + + + {{ end }} + + + + + + + + + + + + + + + + + {{ if $val.Asset.IsFutures }} + {{else}} + + + + + {{end}} + + + + + + {{ if $val.Asset.IsFutures }} + {{else}} + {{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }} - - + + + + + + {{ end }} - - - - - - - - - - - - - - - - - {{ if $val.Asset.IsFutures }} + {{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }} + + + + + + + + {{else}} - - + + + + + + + + + + + + + + + + + + + + + + - {{end}} - - - - - - {{ if $val.Asset.IsFutures }} - {{else}} - {{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }} - - - - - - - - - {{ end }} {{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }} @@ -1343,63 +1385,30 @@ - {{else}} - - - - - - - - - - - - - - - - - - - - - - - - - {{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }} - - - - - - - - - {{end }} - {{end}} + {{end }} {{end}} + {{end}} + +
Total Orders{{ $.Prettify.Int $val.TotalOrders}}Long Orders{{ $.Prettify.Int $val.BuyOrders}}
Short Orders{{ $.Prettify.Int $val.SellOrders}}
Lowest Unrealised PNL{{ $.Prettify.Decimal8 $val.LowestUnrealisedPNL.Value}} at {{ $val.LowestUnrealisedPNL.Time}}
Highest Unrealised PNL{{ $.Prettify.Decimal8 $val.HighestUnrealisedPNL.Value}} at {{ $val.HighestUnrealisedPNL.Time}}
Lowest Realised PNL{{ $.Prettify.Decimal8 $val.LowestRealisedPNL.Value}} at {{ $val.LowestRealisedPNL.Time}}
Highest Realised PNL{{ $.Prettify.Decimal8 $val.HighestRealisedPNL.Value}} at {{ $val.HighestRealisedPNL.Time}}
Base Initial Funds{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseInitialFunds}} {{$val.FinalHoldings.Pair.Base}}
Quote Initial Funds{{ $.Prettify.Decimal8 $val.FinalHoldings.QuoteInitialFunds}} {{$val.FinalHoldings.Pair.Quote}}
Buy Orders{{ $.Prettify.Int $val.BuyOrders}}
Buy Amount{{ $.Prettify.Decimal8 $val.FinalHoldings.BoughtAmount}} {{$val.FinalHoldings.Pair.Base}}
Sell Orders{{ $.Prettify.Int $val.SellOrders}}
Sell Amount{{ $.Prettify.Decimal8 $val.FinalHoldings.SoldAmount}} {{$val.FinalHoldings.Pair.Base}}
Total Orders{{ $.Prettify.Int $val.TotalOrders}}
Biggest DrawdownStart: {{ $val.MaxDrawdown.Highest.Time }} End: {{ $val.MaxDrawdown.Lowest.Time }} Drop: {{ $.Prettify.Decimal8 $val.MaxDrawdown.DrawdownPercent}}%
Starting Close Price{{ $.Prettify.Decimal8 $val.StartingClosePrice.Value}} {{$val.FinalHoldings.Pair.Quote}}
Ending Close Price{{ $.Prettify.Decimal8 $val.EndingClosePrice.Value}} {{ $val.FinalHoldings.Pair.Quote }}
Lowest Close Price{{ $.Prettify.Decimal8 $val.LowestClosePrice.Value}} {{$val.FinalHoldings.Pair.Quote}}
Highest Close Price{{ $.Prettify.Decimal8 $val.HighestClosePrice.Value}} {{ $val.FinalHoldings.Pair.Quote}}
Highest Committed Funds{{ $.Prettify.Decimal8 $val.HighestCommittedFunds.Value}} at {{ $val.HighestCommittedFunds.Time}}
Market Movement{{ $.Prettify.Decimal8 $val.MarketMovement}}%
Biggest DrawdownStart: {{ $val.MaxDrawdown.Highest.Time }} End: {{ $val.MaxDrawdown.Lowest.Time }} Drop: {{ $.Prettify.Decimal8 $val.MaxDrawdown.DrawdownPercent}}%Strategy Movement{{ $.Prettify.Decimal8 $val.StrategyMovement}}%
Did it beat the market?{{ .DoesPerformanceBeatTheMarket }}
Starting Close Price{{ $.Prettify.Decimal8 $val.StartingClosePrice.Value}} {{$val.FinalHoldings.Pair.Quote}}
Ending Close Price{{ $.Prettify.Decimal8 $val.EndingClosePrice.Value}} {{ $val.FinalHoldings.Pair.Quote }}
Lowest Close Price{{ $.Prettify.Decimal8 $val.LowestClosePrice.Value}} {{$val.FinalHoldings.Pair.Quote}}
Highest Close Price{{ $.Prettify.Decimal8 $val.HighestClosePrice.Value}} {{ $val.FinalHoldings.Pair.Quote}}
Final Holdings Value{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseValue}} {{ $val.FinalHoldings.Pair.Quote }}
Total Value{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValue}} {{ $val.FinalHoldings.Pair.Quote}}
Highest Committed Funds{{ $.Prettify.Decimal8 $val.HighestCommittedFunds.Value}} at {{ $val.HighestCommittedFunds.Time}}Total Value Lost to Volume Sizing{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLostToVolumeSizing}} {{$val.FinalHoldings.Pair.Quote}}
Total Value Lost to Slippage{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLostToSlippage}} {{ $val.FinalHoldings.Pair.Quote }}
Total Value Lost{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLost}} {{$val.FinalHoldings.Pair.Quote}}
Total Fees{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalFees}} {{ $val.FinalHoldings.Pair.Quote }}
Final Funds{{ $.Prettify.Decimal8 $val.FinalHoldings.QuoteSize}} {{ $val.FinalHoldings.Pair.Quote}}
Final Holdings{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseSize}} {{$val.FinalHoldings.Pair.Base}}
Market Movement{{ $.Prettify.Decimal8 $val.MarketMovement}}%
Strategy Movement{{ $.Prettify.Decimal8 $val.StrategyMovement}}%
Did it beat the market?{{ .DoesPerformanceBeatTheMarket }}
Final Holdings ValueTotal Value {{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValue}} {{ $val.FinalHoldings.Pair.Quote}}
Total Value Lost to Volume Sizing{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLostToVolumeSizing}} {{$val.FinalHoldings.Pair.Quote}}
Total Value Lost to Slippage{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLostToSlippage}} {{ $val.FinalHoldings.Pair.Quote }}
Total Value Lost{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValueLost}} {{$val.FinalHoldings.Pair.Quote}}
Total Fees{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalFees}} {{ $val.FinalHoldings.Pair.Quote }}
Final Funds{{ $.Prettify.Decimal8 $val.FinalHoldings.QuoteSize}} {{ $val.FinalHoldings.Pair.Quote}}
Final Holdings{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseSize}} {{$val.FinalHoldings.Pair.Base}}
Final Holdings Value{{ $.Prettify.Decimal8 $val.FinalHoldings.BaseValue}} {{ $val.FinalHoldings.Pair.Quote }}
Total Value{{ $.Prettify.Decimal8 $val.FinalHoldings.TotalValue}} {{ $val.FinalHoldings.Pair.Quote}}
+ {{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }} + Rates + + + + + + + + + {{ if $val.CompoundAnnualGrowthRate.IsZero}} + + {{else}} + + {{end}} +
Risk Free Rate{{$.Statistics.RiskFreeRate}}%
Compound Annual Growth RateN/A{{ $.Prettify.Decimal8 $val.CompoundAnnualGrowthRate}}%
- {{ if eq $.Statistics.FundingStatistics.Report.UsingExchangeLevelFunding false }} - Rates - - - - - - - - - {{ if $val.CompoundAnnualGrowthRate.IsZero}} - - {{else}} - - {{end}} - - -
Risk Free Rate{{$.Statistics.RiskFreeRate}}%
Compound Annual Growth RateN/A{{ $.Prettify.Decimal8 $val.CompoundAnnualGrowthRate}}%
+ {{ if gt $val.TotalOrders 1}} {{if $val.ShowMissingDataWarning}}

Missing data was detected during this backtesting run
Ratio calculations will be skewed

@@ -1446,7 +1455,9 @@ - {{end }} + {{end}} + {{end }} + {{end }}
{{end}} @@ -1462,6 +1473,8 @@ {{end}} {{end}} {{ range .Statistics.FundingStatistics.Items }} + {{ if .ReportItem.AppendedViaAPI}} + {{else}}

Funding Statistics for {{.ReportItem.Exchange}} {{.ReportItem.Asset}} {{.ReportItem.Currency}}

@@ -1540,6 +1553,7 @@
+ {{end}} {{end}} {{ if eq $.Config.StrategySettings.DisableUSDTracking false }}
@@ -1558,7 +1572,7 @@ ${{$.Prettify.Decimal8 .Statistics.FundingStatistics.Report.FinalFunds}} - Difference + Strategy movement {{$.Prettify.Decimal8 .Statistics.FundingStatistics.TotalUSDStatistics.HoldingValueDifference}}% @@ -1569,10 +1583,6 @@ Benchmark movement {{$.Prettify.Decimal8 .Statistics.FundingStatistics.TotalUSDStatistics.BenchmarkMarketMovement}}% - - Overall strategy movement - {{$.Prettify.Decimal8 .Statistics.FundingStatistics.TotalUSDStatistics.StrategyMovement}}% - Did strategy beat the benchmark? {{.Statistics.FundingStatistics.TotalUSDStatistics.DidStrategyBeatTheMarket}} @@ -1598,7 +1608,7 @@ {{ if .Statistics.FundingStatistics.TotalUSDStatistics.CompoundAnnualGrowthRate.IsZero}} N/A {{else}} - {{.Statistics.FundingStatistics.TotalUSDStatistics.CompoundAnnualGrowthRate}}% + {{$.Prettify.Decimal8 .Statistics.FundingStatistics.TotalUSDStatistics.CompoundAnnualGrowthRate}}% {{end}} @@ -1661,38 +1671,40 @@
{{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}} {{ range $asset, $unused := .}} - {{ range $pair, $val := .}} -
-

{{$exchange}} {{$asset}} {{ $pair }}

-
-
- - - - - - - - - - - - - {{range $val.FinalOrders.Orders}} + {{ range $base, $unused := .}} + {{ range $quote, $val := .}} +
+

{{$exchange}} {{$asset}} {{ $base }}-{{$quote}}

+
+
+
DateClose PriceSidePriceAmountFeeTotalSlippage Rate
- - - - - - - - + + + + + + + + - {{end}} - -
{{ .Order.Date }}{{ $.Prettify.Decimal8 .ClosePrice}} {{$pair.Quote}}{{ .Order.Side }}{{$.Prettify.Float8 .Order.Price }} {{$pair.Quote}}{{$.Prettify.Float8 .Order.Amount }} {{$pair.Base}}{{$.Prettify.Float8 .Order.Fee }} {{$pair.Quote}}{{ $.Prettify.Decimal8 .CostBasis }} {{$pair.Quote}}{{ $.Prettify.Decimal8 .SlippageRate }}%DateClose PriceSidePriceAmountFeeTotalSlippage Rate
-
+ + {{range $val.FinalOrders.Orders}} + + {{ .Order.Date }} + {{ $.Prettify.Decimal8 .ClosePrice}} {{$quote}} + {{ .Order.Side }} + {{$.Prettify.Float8 .Order.Price }} {{$quote}} + {{$.Prettify.Float8 .Order.Amount }} {{$base}} + {{$.Prettify.Float8 .Order.Fee }} {{$quote}} + {{ $.Prettify.Decimal8 .CostBasis }} {{.Order.FeeAsset}} + {{ $.Prettify.Decimal8 .SlippageRate }}% + + {{end}} + + +
+ {{end}} {{end}} {{end}} {{end}} @@ -1706,79 +1718,81 @@
{{ range $exchange, $unused := .Statistics.ExchangeAssetPairStatistics}} {{ range $asset, $unused :=.}} - {{ range $pair, $data := .}} -
-

{{$exchange}} {{$asset}} {{ $pair }}

-
-
- - - - - - - {{if $asset.IsFutures}} - - - - - {{ else }} - - - - - {{ end }} - - - - {{range $ev := $data.Events}} + {{ range $base, $unused := .}} + {{ range $quote, $data := .}} +
+

{{$exchange}} {{$asset}} {{ $base }}-{{$quote}}

+
+
+
DatePriceActionEvent DetailsHoldingsPosition DirectionUnrealised PNLRealised PNL{{$pair.Base}} Funds{{$pair.Quote}} FundsTotal value in {{$pair.Quote}}Committed funds in {{$pair.Quote}}
- {{ if ne $ev.FillEvent nil }} - - - - - {{ else if ne $ev.SignalEvent nil}} - - - - - {{ end }} + + + + {{if $asset.IsFutures}} - {{if ne $ev.PNL nil }} - - - - - {{else}} - - - - - {{end}} - {{else }} - - - - - {{end}} + + + + + {{ else }} + + + + + {{ end }} + - {{end}} - -
{{$ev.FillEvent.GetTime}}{{ $.Prettify.Decimal8 $ev.FillEvent.GetClosePrice}} {{if $asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$pair.Quote}}{{end}}{{$ev.FillEvent.GetDirection}} -
    - {{ range $ev.FillEvent.GetReasons }} -
  • {{.}}
  • - {{end}} -
-
{{$ev.SignalEvent.GetTime}}{{ $.Prettify.Decimal8 $ev.SignalEvent.GetClosePrice}} {{if $asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$pair.Quote}}{{end}}{{$ev.SignalEvent.GetDirection}} -
    - {{ range $ev.SignalEvent.GetReasons }} -
  • {{.}}
  • - {{end}} -
-
DatePriceActionEvent Details{{ $.Prettify.Decimal8 $ev.PNL.GetExposure}} {{$pair.Base}}-{{$pair.Quote}}{{$ev.PNL.GetDirection}}{{$.Prettify.Decimal8 $ev.PNL.GetUnrealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{$.Prettify.Decimal8 $ev.PNL.GetRealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}0 {{$pair.Base}}-{{$pair.Quote}}N/A00{{ $.Prettify.Decimal8 $ev.Holdings.BaseSize}} {{$pair.Base}}{{ $.Prettify.Decimal8 $ev.Holdings.QuoteSize}} {{$pair.Quote}}{{ $.Prettify.Decimal8 $ev.Holdings.TotalValue}} {{$pair.Quote}}{{ $.Prettify.Decimal8 $ev.Holdings.CommittedFunds}} {{$pair.Quote}}HoldingsPosition DirectionUnrealised PNLRealised PNL{{$base}} Funds{{$quote}} FundsTotal value in {{$quote}}Committed funds in {{$quote}}
-
+ + {{range $ev := $data.Events}} + + {{ if ne $ev.FillEvent nil }} + {{$ev.FillEvent.GetTime}} + {{ $.Prettify.Decimal8 $ev.FillEvent.GetClosePrice}} {{if $asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$quote}}{{end}} + {{$ev.FillEvent.GetDirection}} + +
    + {{ range $ev.FillEvent.GetReasons }} +
  • {{.}}
  • + {{end}} +
+ + {{ else if ne $ev.SignalEvent nil}} + {{$ev.SignalEvent.GetTime}} + {{ $.Prettify.Decimal8 $ev.SignalEvent.GetClosePrice}} {{if $asset.IsFutures}}{{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}}{{else}}{{$quote}}{{end}} + {{$ev.SignalEvent.GetDirection}} + +
    + {{ range $ev.SignalEvent.GetReasons }} +
  • {{.}}
  • + {{end}} +
+ + {{ end }} + {{if $asset.IsFutures}} + {{if ne $ev.PNL nil }} + {{ $.Prettify.Decimal8 $ev.PNL.GetExposure}} {{$base}}-{{$quote}} + {{$ev.PNL.GetDirection}} + {{$.Prettify.Decimal8 $ev.PNL.GetUnrealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}} + {{$.Prettify.Decimal8 $ev.PNL.GetRealisedPNL.PNL}} {{if ne $ev.PNL nil }}{{$ev.PNL.GetCollateralCurrency}}{{end}} + {{else}} + 0 {{$base}}-{{$quote}} + N/A + 0 + 0 + {{end}} + {{else }} + {{ $.Prettify.Decimal8 $ev.Holdings.BaseSize}} {{$base}} + {{ $.Prettify.Decimal8 $ev.Holdings.QuoteSize}} {{$quote}} + {{ $.Prettify.Decimal8 $ev.Holdings.TotalValue}} {{$quote}} + {{ $.Prettify.Decimal8 $ev.Holdings.CommittedFunds}} {{$quote}} + {{end}} + + {{end}} + + +
+ {{end}} {{end}} {{end}} {{end}} diff --git a/cmd/documentation/backtester_templates/backtester_config_examples_readme.tmpl b/cmd/documentation/backtester_templates/backtester_config_examples_readme.tmpl index 7b4fbcab..29b491b3 100644 --- a/cmd/documentation/backtester_templates/backtester_config_examples_readme.tmpl +++ b/cmd/documentation/backtester_templates/backtester_config_examples_readme.tmpl @@ -1,4 +1,4 @@ -{{define "backtester config examples" -}} +{{define "backtester config strategyexamples" -}} {{template "backtester-header" .}} ## {{.CapitalName}} package overview @@ -16,7 +16,8 @@ | dca-database-candles.strat | The same DCA strategy, but uses a database to retrieve candle data | | rsi-api-candles.strat | Runs a strategy using rsi figures to make buy or sell orders based on market figures | | t2b2-api-candles-exchange-funding.strat | Runs a more complex strategy using simultaneous signal processing, exchange level funding and MFI values to make buy or sell signals based on the two strongest and weakest MFI values | -| ftx-cash-carry.strat | Executes a cash and carry trade on FTX, buying BTC-USD while shorting the long dated futures contract BTC-20210924 | +| binance-cash-and-carry.strat | Executes a cash and carry trade on Binance, buying BTC-USD while shorting the long dated futures contract. Is not currently implemented | +| binance-live-cash-and-carry.strat | Executes a cash and carry trade on Binance using realtime 15 second candles, buying BTC-USD while shorting the long dated futures contract. Is not currently implemented | ### Want to make your own configs? Use the provided config builder under `/backtester/config/configbuilder` or modify tests under `/backtester/config/config_test.go` to generates strategy files quickly diff --git a/cmd/documentation/backtester_templates/backtester_config_readme.tmpl b/cmd/documentation/backtester_templates/backtester_config_readme.tmpl index 861d71f7..ea12dbbe 100644 --- a/cmd/documentation/backtester_templates/backtester_config_readme.tmpl +++ b/cmd/documentation/backtester_templates/backtester_config_readme.tmpl @@ -1,42 +1,43 @@ {{define "backtester config" -}} {{template "backtester-header" .}} ## {{.CapitalName}} package overview +This readme contains details for both the GoCryptoTrader Backtester config structure along with the strategy config structure -## Backtester Config overview +## GoCryptoTrader Backtester Config overview Below are the details for the GoCryptoTrader Backtester _application_ config. Strategy config overview is below this section | Key | Description | Example | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------| -| PrintLogo | Whether to print the GoCryptoTrader Backtester logo on startup. Recommended because it looks good | `true` | -| Verbose | Whether to receive verbose output. If running a GRPC server, it outputs to the server, not to the client | `false` | -| LogSubheaders | Whether log output contains a descriptor of what area the log is coming from, for example `STRATEGY`. Helpful for debugging | `true` | -| SingleRun | Whether or not to run the GoCryptoTrader Backtester to read the `SingleRunStrategyConfig` strategy and exit afterwards. If false, will run a GRPC server | `false` | -| SingleRunStrategyConfig | The path to the strategy to run when `SingleRun` is `true` | `path\to\strategy\example.strat` | -| Report | Contains details on the output report after a successful backtesting run | See Report table below | -| GRPC | Contains GRPC server details | See GRPC table below | -| UseCMDColours | If enabled, will output pretty colours of your choosing when running the application | `true` | -| Colours | Contains details on what the colour definitions are | See Colours table below | +| print-logo | Whether to print the GoCryptoTrader Backtester logo on startup. Recommended because it looks good | `true` | +| verbose | Whether to receive verbose output. If running a GRPC server, it outputs to the server, not to the client | `false` | +| log-subheaders | Whether log output contains a descriptor of what area the log is coming from, for example `STRATEGY`. Helpful for debugging | `true` | +| stop-all-tasks-on-close | When closing the application, the Backtester will attempt to stop all active tasks | `true` | +| plugin-path | When using custom strategy plugins, you can enter the path here to automatically load the plugin | `true` | +| report | Contains details on the output report after a successful backtesting run | See Report table below | +| grpc | Contains GRPC server details | See GRPC table below | +| use-cmd-colours | If enabled, will output pretty colours of your choosing when running the application | `true` | +| cmd-colours | Contains details on what the colour definitions are | See Colours table below | ### Backtester Config Report overview | Key | Description | Example | |----------------|----------------------------------------------------------------------|---------------------------------| -| GenerateReport | Whether or not to output a report after a successful backtesting run | `true` | -| TemplatePath | The path for the template to use when generating a report | `/backtester/report/tpl.gohtml` | -| OutputPath | The path where report output is saved | `/backtester/results` | -| DarkMode | Whether or not the report defaults to using dark mode | `true` | +| output-report | Whether or not to output a report after a successful backtesting run | `true` | +| template-path | The path for the template to use when generating a report | `/backtester/report/tpl.gohtml` | +| output-path | The path where report output is saved | `/backtester/results` | +| dark-mode | Whether or not the report defaults to using dark mode | `true` | ### Backtester Config GRPC overview | Key | Description | Example | |------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------| -| Username | Your username to negotiate a successful connection with the server | `rpcuser` | -| Password | Your password to negotiate a successful connection with the server | `helloImTheDefaultPassword` | -| Enabled | Whether the server is enabled. Setting this to `false` and `SingleRun` to `false` would be inadvisable | `true` | -| ListenAddress | The listen address for the GRPC server | `localhost:42069` | -| GRPCProxyEnabled | If enabled, creates a proxy server to interact with the GRPC server via HTTP commands | `true` | -| GRPCProxyListenAddress | The address for the proxy to listen on | `localhost:9053` | -| TLSDir | The directory for holding your TLS certifications to make connections to the server. Will be generated by default on startup if not present | `/backtester/config/location/` | +| username | Your username to negotiate a successful connection with the server | `rpcuser` | +| password | Your password to negotiate a successful connection with the server | `helloImTheDefaultPassword` | +| enabled | Whether the server is enabled. Setting this to `false` and `SingleRun` to `false` would be inadvisable | `true` | +| listenAddress | The listen address for the GRPC server | `localhost:9054` | +| grpcProxyEnabled | If enabled, creates a proxy server to interact with the GRPC server via HTTP commands | `true` | +| grpcProxyListenAddress | The address for the proxy to listen on | `localhost:9053` | +| tls-dir | The directory for holding your TLS certifications to make connections to the server. Will be generated by default on startup if not present | `/backtester/config/location/` | ### Backtester Config Colours overview @@ -79,123 +80,111 @@ See below for a set of tables and fields, expected values and what they can do #### Config -| Key | Description | -|-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs | -| Goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal | -| CurrencySettings | Currency settings is an array of settings for each individual currency you wish to run the strategy against | -| StrategySettings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions | -| FundingSettings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level | -| PortfolioSettings | Contains a list of global rules for the portfolio manager. CurrencySettings contain their own rules on things like how big a position is allowable, the portfolio manager rules are the same, but override any individual currency's settings | -| StatisticSettings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio | - +| Key | Description | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| nickname | A nickname for the specific config. When running multiple variants of the same strategy, use the nickname to help differentiate between runs | +| goal | A description of what you would hope the outcome to be. When verifying output, you can review and confirm whether the strategy met that goal | +| strategy-settings | Select which strategy to run, what custom settings to load and whether the strategy can assess multiple currencies at once to make more in-depth decisions | +| funding-settings | Defines whether individual funding settings can be used. Defines the funding exchange, asset, currencies at an individual level | +| currency-settings | Currency settings is an array of settings for each individual currency you wish to run the strategy against | +| data-settings | Holds data retrieval settings. Determines how the GoCryptoTraderBacktester will fetch data and in what format | +| portfolio-settings | Contains a list of global rules for the portfolio manager. CurrencySettings contain their own rules on things like how big a position is allowable, the portfolio manager rules are the same, but override any individual currency's settings | +| statistic-settings | Contains settings that impact statistics calculation. Such as the risk-free rate for the sharpe ratio | #### Strategy Settings -| Key | Description | Example | -|----------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| -| Name | The strategy to use | `rsi` | -| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | -| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | -| DisableUSDTracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` | - +| Key | Description | Example | +|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| name | The strategy to use | `rsi` | +| use-simultaneous-signal-processing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | +| disable-usd-tracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` | +| custom-settings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | #### Funding Config Settings -| Key | Description | Example | -|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| -| UseExchangeLevelFunding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` | -| ExchangeLevelFunding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` | - +| Key | Description | Example | +|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| use-exchange-level-funding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` | +| exchange-level-funding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` | ##### Funding Item Config Settings -| Key | Description | Example | -|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| -| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | -| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | -| Currency | The currency to set funds | `BTC` | -| InitialFunds | The initial funding for the currency | `1337` | -| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | - +| Key | Description | Example | +|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| +| exchange-name | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | +| asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | +| currency | The currency to set funds | `BTC` | +| initial-funds | The initial funding for the currency | `1337` | +| transfer-fee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | #### Currency Settings -| Key | Description | Example | -|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------| -| ExchangeName | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | -| Asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | -| Base | The base of a currency | `BTC` | -| Quote | The quote of a currency | `USDT` | -| InitialFunds | A legacy field, will be temporarily migrated to `InitialQuoteFunds` if present in your strat config | `` | -| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | - | -| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - | -| MinimumSlippagePercent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` | -| MaximumSlippagePercent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` | -| MakerFee | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` | -| TakerFee | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` | -| MaximumHoldingsRatio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` | -| CanUseExchangeLimits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` | -| SkipCandleVolumeFitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` | -| SpotSettings | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below | -| FuturesSettings | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below | +| Key | Description | Example | +|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------| +| exchange-name | The exchange to load. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | +| asset | The asset type. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | +| base | The base of a currency | `BTC` | +| quote | The quote of a currency | `USDT` | +| spot-details | An optional field which contains initial funding data for SPOT currency pairs | See SpotSettings table below | +| future-detailss | An optional field which contains leverage data for FUTURES currency pairs | See FuturesSettings table below | +| buy-side | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount |-- | +| sell-side | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount |-- | +| min-slippage-percent | Is the lower bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 90, then the most a price can be affected is 10% | `90` | +| max-slippage-percent | Is the upper bounds in a random number generated that make purchases more expensive, or sell events less valuable. If this value is 99, then the least a price can be affected is 1%. Set both upper and lower to 100 to have no randomness applied to purchase events | `100` | +| maker-fee-override | The fee to use when sizing and purchasing currency. If `nil`, will lookup an exchange's fee details | `0.001` | +| taker-fee-override | Unused fee for when an order is placed in the orderbook, rather than taken from the orderbook. If `nil`, will lookup an exchange's fee details | `0.002` | +| maximum-holdings-ratio | When multiple currency settings are used, you may set a maximum holdings ratio to prevent having too large a stake in a single currency | `0.5` | +| skip-candle-volume-fitting | When placing orders, by default the BackTester will shrink an order's size to fit the candle data's volume so as to not rewrite history. Set this to `true` to ignore this and to set order size at what the portfolio manager prescribes | `false` | +| use-exchange-order-limits | Will lookup exchange rules around purchase sizing eg minimum order increments of 0.0005. Note: Will retrieve up-to-date rules which may not have existed for the data you are using. Best to use this when considering to use this strategy live | `false` | +| use-exchange-pnl-calculation | Instead of simulating the exchange's own way of calculating PNL, use a default method which calculates the value of an asset | `false` | ##### SpotSettings -| Key | Description | Example | -|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| -| InitialBaseFunds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` | -| InitialQuoteFunds | The funds that the GoCryptoTraderBacktester has for the quote currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `10000` | +| Key | Description | Example | +|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| initial-base-funds | The funds that the GoCryptoTraderBacktester has for the base currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `2` | +| initial-quote-funds | The funds that the GoCryptoTraderBacktester has for the quote currency. This is only required if the strategy setting `UseExchangeLevelFunding` is `false` | `10000` | ##### FuturesSettings | Key | Description | Example | |----------|------------------------------------------------------------------------------------------|---------| -| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` | +| leverage | This struct defines the leverage rules that this specific currency setting must abide by | `1` | -#### PortfolioSettings - -| Key | Description | -|----------|------------------------------------------------------------------------------------------------------------------------| -| Leverage | This struct defines the leverage rules that this specific currency setting must abide by | -| BuySide | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | -| SellSide | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | - -#### StatisticsSettings - -| Key | Description | Example | -|--------------|-------------------------------------------------------------------------|---------| -| RiskFreeRate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` | +### DataSettings +| Key | Description | Example | +|---------------------------|--------------------------------------------------------------------------------------------------------|---------------| +| interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | +| data-type | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` | +| verbose-exchange-requests | When retrieving candle data from an exchange, print verbose request/response details | `false` | +| api-data | Holds API data settings. See table `APIData` | | +| database-data | Holds database data settings. See table `DatabaseData` | | +| live-data | Holds API data settings. See table `LiveData` | | +| csv-data | Holds CSV data settings. See table `CSVData` | | #### APIData -| Key | Description | Example | -|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | -| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | -| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | +| Key | Description | Example | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| +| start-date | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | +| end-date | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | +| inclusive-end-date | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | #### CSVData -| Key | Description | Example | -|----------|--------------------------------------------------------------------------------------------------------|--------------------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| FullPath | The file to load | `/data/exchangelist.csv` | +| Key | Description | Example | +|-----------|------------------|--------------------------| +| full-path | The file to load | `/data/exchangelist.csv` | #### DatabaseData -| Key | Description | Example | -|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `trade` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| StartDate | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | -| EndDate | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | -| Config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` | -| Path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` | -| InclusiveEndDate | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | +| Key | Description | Example | +|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| +| start-date | The start date to retrieve data | `2021-01-23T11:00:00+11:00` | +| end-date | The end date to retrieve data | `2021-01-24T11:00:00+11:00` | +| config | This is the same struct used as your GoCryptoTrader database config. See below tables for breakdown | `see below` | +| path | If using SQLite, the path to the directory, not the file. Leaving blank will use GoCryptoTrader's default database path | `` | +| inclusive-end-date | When enabled, the end date's candle is included in the results. ie `2021-01-24T11:00:00+11:00` with a one hour candle, the final candle will be `2021-01-24T11:00:00+11:00` to `2021-01-24T12:00:00+11:00` | `false` | ##### database @@ -219,32 +208,65 @@ See below for a set of tables and fields, expected values and what they can do #### LiveData -| Key | Description | Example | -|-----------------------|--------------------------------------------------------------------------------------------------------|---------------| -| DataType | Choose whether `candle` or `trade` data is used. If trades are used, they will be converted to candles | `candle` | -| Interval | The candle interval in `time.Duration` format eg set as`15000000000` for a value of `time.Second * 15` | `15000000000` | -| APIKeyOverride | Will set the GoCryptoTrader exchange to use the following API Key | `1234` | -| APISecretOverride | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` | -| APIClientIDOverride | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` | -| API2FAOverride | Will set the GoCryptoTrader exchange to use the following 2FA seed | `hello-moto` | -| APISubaccountOverride | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` | -| RealOrders | Whether to place real orders. You really should never consider using this. Ever ever | `true` | +| Key | Description | Example | +|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|---------------| +| new-event-timeout | The time allowed to wait for new data before exiting the strategy. Ensures new data is always coming in | `60000000000` | +| data-check-timer | The interval in which to check exchange API's for new data | `1000000000` | +| real-orders | Whether to place real orders with real money. Its likely you should never want to set this to true | `false` | +| close-positions-on-stop | As live trading doesn't stop until you tell it to, you can trigger a close of your position(s) when you stop the strategy | `true` | +| data-request-retry-tolerance | Rather than immediately closing a strategy on failure to retreive candle data, having a retry tolerance allows multiple attempts to return data | `3` | +| data-request-retry-wait-time | How long to wait in between request retries | `500000000` | +| exchange-credentials | A list of exchange credentials. See table named `ExchangeCredentials` | | + +##### ExchangeCredentials Settings + +| Key | Description | Example | +|-------------|-----------------------------------------------------------|-----------| +| exchange | The exchange to apply credentials to | `binance` | +| credentials | The API credentials to use. See table named `Credentials` | | + +##### Credentials Settings + +| Key | Description | Example | +|-----------------|---------------------------------------------------------------------------------------------|--------------| +| Key | Will set the GoCryptoTrader exchange to use the following API Key | `1234` | +| Secret | Will set the GoCryptoTrader exchange to use the following API Secret | `5678` | +| ClientID | Will set the GoCryptoTrader exchange to use the following API Client ID | `9012` | +| PEMKey | Private key for certain API requests. If you don't know it, you probably don't need it | `hello-moto` | +| SubAccount | Will set the GoCryptoTrader exchange to use the following subaccount on supported exchanges | `subzero` | +| OneTimePassword | Will set the GoCryptoTrader exchange to use the following 2FA seed | `subzero` | + +#### PortfolioSettings + +| Key | Description | +|-----------|------------------------------------------------------------------------------------------------------------------------| +| leverage | This struct defines the leverage rules that this specific currency setting must abide by | +| buy-side | This struct defines the buying side rules this specific currency setting must abide by such as maximum purchase amount | +| sell-side | This struct defines the selling side rules this specific currency setting must abide by such as maximum selling amount | ##### Leverage Settings -| Key | Description | Example | -|--------------------------------|------------------------------------------------------------------------------------------|---------| -| CanUseLeverage | Allows the use of leverage | `false` | -| MaximumOrdersWithLeverageRatio | If the ratio of leveraged orders for a currency exceeds this, the order cannot be placed | `0.5` | -| MaximumLeverageRate | Orders cannot be placed with leverage over this amount | `100` | +| Key | Description | Example | +|------------------------------------|-------------------------------|---------| +| can-use-leverage | Allows the use of leverage | `false` | +| maximum-orders-with-leverage-ratio | currently unused | `0.5` | +| maximum-leverage-rate | currently unused | `100` | +| maximum-collateral-leverage-rate | currently unused | `100` | ##### Buy/Sell Settings -| Key | Description | Example | -|--------------|------------------------------------------------------------------------------------------------------------------|---------| -| MinimumSize | If the order's quantity is below this, the order cannot be placed | `0.1` | -| MaximumSize | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` | -| MaximumTotal | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` | +| Key | Description | Example | +|---------------|------------------------------------------------------------------------------------------------------------------|---------| +| minimum-size | If the order's quantity is below this, the order cannot be placed | `0.1` | +| maximum-size | If the order's quantity is over this amount, it cannot be placed and will be reduced to the maximum amount | `10` | +| maximum-total | If the order's price * amount exceeds this number, the order cannot be placed and will be reduced to this figure | `1337` | + + +#### StatisticsSettings + +| Key | Description | Example | +|----------------|-------------------------------------------------------------------------|---------| +| risk-free-rate | The risk free rate used in the calculation of sharpe and sortino ratios | `0.03` | ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/cmd/documentation/backtester_templates/backtester_data_kline_live_readme.tmpl b/cmd/documentation/backtester_templates/backtester_data_kline_live_readme.tmpl index 87200f8e..6a1f9d62 100644 --- a/cmd/documentation/backtester_templates/backtester_data_kline_live_readme.tmpl +++ b/cmd/documentation/backtester_templates/backtester_data_kline_live_readme.tmpl @@ -5,7 +5,7 @@ This package will retrieve data for the backtester via continuous requests to live endpoints ## Important notice -Live trading is not fully implemented and you should never consider setting `RealOrders` to `true` in a config. *Past performance is no guarantee of future results* +Its incredibly risky to enable `real-orders`. *Past performance is no guarantee of future results* ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/cmd/documentation/backtester_templates/backtester_eventhandlers_strategies_ftxcashandcarry_readme.tmpl b/cmd/documentation/backtester_templates/backtester_eventhandlers_strategies_binancecashandcarry_readme.tmpl similarity index 78% rename from cmd/documentation/backtester_templates/backtester_eventhandlers_strategies_ftxcashandcarry_readme.tmpl rename to cmd/documentation/backtester_templates/backtester_eventhandlers_strategies_binancecashandcarry_readme.tmpl index 58940db8..24ec3d2a 100644 --- a/cmd/documentation/backtester_templates/backtester_eventhandlers_strategies_ftxcashandcarry_readme.tmpl +++ b/cmd/documentation/backtester_templates/backtester_eventhandlers_strategies_binancecashandcarry_readme.tmpl @@ -1,21 +1,24 @@ -{{define "backtester eventhandlers strategies ftxcashandcarry" -}} +{{define "backtester eventhandlers strategies binancecashandcarry" -}} {{template "backtester-header" .}} -## FTX Cash and carry strategy overview +## Binance Cash and carry strategy overview + +## Important +This strategy was initially designed for the exchange FTX. It is currently being ported to Binance. It does not work at present. ### Description Cash and carry is a strategy which takes advantage of the difference in pricing between a long-dated futures contract and a SPOT asset. -By default, this cash and carry strategy will, upon the first data event, purchase BTC-USD SPOT asset from FTX exchange and then, once filled, raise a SHORT for BTC-20210924 FUTURES contract. +By default, this cash and carry strategy will, upon the first data event, purchase BTC-USD SPOT asset from Binance exchange and then, once filled, raise a SHORT for BTC-20210924 FUTURES contract. On the last event, the strategy will close the SHORT position by raising a LONG of the same contract amount, thereby netting the difference in prices ### Requirements -- At this time of writing, this strategy is only compatible with FTX +- At this time of writing, this strategy is only compatible with Binance - This strategy *requires* `Simultaneous Signal Processing` aka [use-simultaneous-signal-processing](/backtester/config/README.md). - This strategy *requires* `Exchange Level Funding` aka [use-exchange-level-funding](/backtester/config/README.md). ### Creating a strategy config - The long-dated futures contract will need to be part of the `currency-settings` of the contract - Funding for purchasing SPOT assets will need to be part of `funding-settings` -- See the [example config](./config/examples/ftx-cash-carry.strat) +- See the [example config](./config/strategyexamples/binance-cash-carry.strat) ### Customisation This strategy does support strategy customisation in the following ways: @@ -31,4 +34,5 @@ This strategy does support strategy customisation in the following ways: ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} {{template "donations" .}} -{{end}} \ No newline at end of file +{{end}} + diff --git a/cmd/documentation/backtester_templates/backtester_funding_readme.tmpl b/cmd/documentation/backtester_templates/backtester_funding_readme.tmpl index 634867d2..8b55103b 100644 --- a/cmd/documentation/backtester_templates/backtester_funding_readme.tmpl +++ b/cmd/documentation/backtester_templates/backtester_funding_readme.tmpl @@ -39,7 +39,7 @@ Yes! Though it does use some things to consider. - For example, if an indicator is very strong on one exchange, but not another, you may wish to transfer funds to the strongest exchange to act upon - It comes with the assumption that a transfer is actually possible in the candle timeframe your strategy runs on. - For example, a 1 minute candle strategy likely would not be able to process a transfer of funds and have another exchange use it in that timeframe. So any positive results from such a strategy may not be reflected in real-world scenarios -- You can only transfer to the same currency eg BTC from Binance to FTX, no conversions +- You can only transfer to the same currency eg BTC from Binance to Kraken, no conversions - You set the transfer fee in your config ### Do I need to add funding settings to my config if Exchange Level Funding is disabled? @@ -47,28 +47,29 @@ No. The already existing `CurrencySettings` will populate the funding manager wi #### Strategy Settings -| Key | Description | Example | -| --- | ------- | --- | -| Name | The strategy to use | `rsi` | -| UsesSimultaneousProcessing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | -| CustomSettings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | - -#### Funding Settings - -| Key | Description | Example | -| --- | ------- | --- | -| UseExchangeLevelFunding | This allows shared exchange funds to be used in your strategy. Requires `UsesSimultaneousProcessing` to be set to `true` to use | `false` | -| ExchangeLevelFunding | This is a list of funding definitions if `UseExchangeLevelFunding` is set to true | See below table | +| Key | Description | Example | +|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| +| name | The strategy to use | `rsi` | +| use-simultaneous-signal-processing | This denotes whether multiple currencies are processed simultaneously with the strategy function `OnSimultaneousSignals`. Eg If you have multiple CurrencySettings and only wish to purchase BTC-USDT when XRP-DOGE is 1337, this setting is useful as you can analyse both signal events to output a purchase call for BTC | `true` | +| disable-usd-tracking | If `false`, will track all currencies used in your strategy against USD equivalent candles. For example, if you are running a strategy for BTC/XRP, then the GoCryptoTrader Backtester will also retreive candles data for BTC/USD and XRP/USD to then track strategy performance against a single currency. This also tracks against USDT and other USD tracked stablecoins, so one exchange supporting USDT and another BUSD will still allow unified strategy performance analysis. If disabled, will not track against USD, this can be especially helpful when running strategies under live, database and CSV based data | `false` | +| custom-settings | This is a map where you can enter custom settings for a strategy. The RSI strategy allows for customisation of the upper, lower and length variables to allow you to change them from 70, 30 and 14 respectively to 69, 36, 12 | `"custom-settings": { "rsi-high": 70, "rsi-low": 30, "rsi-period": 14 } ` | #### Funding Config Settings -| Key | Description | Example | -| --- | ------- | ----- | -| ExchangeName | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | -| Asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports| `spot` | -| Currency | The currency to set funds | `BTC` | -| InitialFunds | The initial funding for the currency | `1337` | -| TransferFee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | +| Key | Description | Example | +|----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| +| use-exchange-level-funding | Allows shared funding at an exchange asset level. You can set funding for `USDT` and all pairs that feature `USDT` will have access to those funds when making orders. See [this](/backtester/funding/README.md) for more information | `false` | +| exchange-level-funding | An array of exchange level funding settings. See below, or [this](/backtester/funding/README.md) for more information | `[]` | + +##### Funding Item Config Settings + +| Key | Description | Example | +|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------| +| exchange-name | The exchange to set funds. See [here](https://github.com/thrasher-corp/gocryptotrader/blob/master/README.md) for a list of supported exchanges | `Binance` | +| asset | The asset type to set funds. Typically, this will be `spot`, however, see [this package](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/asset/asset.go) for the various asset types GoCryptoTrader supports | `spot` | +| currency | The currency to set funds | `BTC` | +| initial-funds | The initial funding for the currency | `1337` | +| transfer-fee | If your strategy utilises transferring of funds via the Funding Manager, this is deducted upon doing so | `0.005` | ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/cmd/documentation/backtester_templates/backtester_readme.tmpl b/cmd/documentation/backtester_templates/backtester_readme.tmpl index 4bcc72c9..e94cea93 100644 --- a/cmd/documentation/backtester_templates/backtester_readme.tmpl +++ b/cmd/documentation/backtester_templates/backtester_readme.tmpl @@ -27,20 +27,23 @@ An event-driven backtesting tool to test and iterate trading strategies using hi - Fund transfer. At a strategy level, transfer funds between exchanges to allow for complex strategy design - Backtesting support for futures asset types - Example cash and carry spot futures strategy -- Long-running application -- GRPC server implementation +- Long-running application as a GRPC server +- Custom strategy plugins +- Live data source trading. Traders can move their back tested strategies and use them against current live data ## Planned Features We welcome pull requests on any feature for the Backtester! We will be especially appreciative of any contribution towards the following planned features: | Feature | Description | |---------|-------------| +| Perpetual futures support | Accounting for hourly funding rates in user's overall positions allows for much greater strategic depth | +| Margin borrowing support | Allowing strategies to utilise margin borrowing to have larger positions and handling borrow rate payments | | Leverage support | Leverage is a good way to enhance profit and loss and is important to include in strategies | +| Live ticker data | A potential feature as live trading works off candle data which is only processed at intervals. Adding ticker data as a strategic source allows for faster decision making | +| Live orderbook data | Processing orders based off the latest orderbook data allows for much more accurate order placement and reduces surprise slippage | | Enhance config-builder | Create an application that can create strategy configs in a more visual manner and execute them via GRPC to allow for faster customisation of strategies | | Save Backtester results to database | This will allow for easier comparison of results over time | | Backtester result comparison report | Providing an executive summary of Backtester database results | -| Currency correlation | Compare multiple exchange, asset, currencies for a candle interval against indicators to highlight correlated pairs for use in pairs trading | -| Improve live trading functionality | Live trading is currently only a proof Of concept. Adding live support for running multiple currencies and running off orderbook data will allow for esteemed traders to use their backtested strategies | ## How does it work? diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go index e45a670c..a8071a70 100644 --- a/cmd/documentation/documentation.go +++ b/cmd/documentation/documentation.go @@ -171,11 +171,26 @@ func main() { // Github API missing contributors contributors = append(contributors, []Contributor{ + { + Login: "cornelk", + URL: "https://github.com/cornelk", + Contributions: 2, + }, { Login: "herenow", URL: "https://github.com/herenow", Contributions: 2, }, + { + Login: "if1live", + URL: "https://github.com/if1live", + Contributions: 2, + }, + { + Login: "lozdog245", + URL: "https://github.com/lozdog245", + Contributions: 2, + }, { Login: "mshogin", URL: "https://github.com/mshogin", @@ -191,6 +206,31 @@ func main() { URL: "https://github.com/tk42", Contributions: 2, }, + { + Login: "blombard", + URL: "https://github.com/blombard", + Contributions: 1, + }, + { + Login: "cavapoo2", + URL: "https://github.com/cavapoo2", + Contributions: 1, + }, + { + Login: "CodeLingoTeam", + URL: "https://github.com/CodeLingoTeam", + Contributions: 1, + }, + { + Login: "CodeLingoBot", + URL: "https://github.com/CodeLingoBot", + Contributions: 1, + }, + { + Login: "Daanikus", + URL: "https://github.com/Daanikus", + Contributions: 1, + }, { Login: "daniel-cohen", URL: "https://github.com/daniel-cohen", @@ -213,36 +253,6 @@ func main() { URL: "https://github.com/idoall", Contributions: 1, }, - { - Login: "mattkanwisher", - URL: "https://github.com/mattkanwisher", - Contributions: 1, - }, - { - Login: "mKurrels", - URL: "https://github.com/mKurrels", - Contributions: 1, - }, - { - Login: "m1kola", - URL: "https://github.com/m1kola", - Contributions: 1, - }, - { - Login: "cavapoo2", - URL: "https://github.com/cavapoo2", - Contributions: 1, - }, - { - Login: "zeldrinn", - URL: "https://github.com/zeldrinn", - Contributions: 1, - }, - { - Login: "starit", - URL: "https://github.com/starit", - Contributions: 1, - }, { Login: "Jimexist", URL: "https://github.com/Jimexist", @@ -253,45 +263,36 @@ func main() { URL: "https://github.com/lookfirst", Contributions: 1, }, + { + Login: "m1kola", + URL: "https://github.com/m1kola", + Contributions: 1, + }, + { + Login: "mattkanwisher", + URL: "https://github.com/mattkanwisher", + Contributions: 1, + }, { Login: "merkeld", URL: "https://github.com/merkeld", Contributions: 1, }, { - Login: "CodeLingoTeam", - URL: "https://github.com/CodeLingoTeam", + Login: "mKurrels", + URL: "https://github.com/mKurrels", Contributions: 1, }, { - Login: "Daanikus", - URL: "https://github.com/Daanikus", - Contributions: 1, - }, { - Login: "CodeLingoBot", - URL: "https://github.com/CodeLingoBot", + Login: "starit", + URL: "https://github.com/starit", Contributions: 1, }, { - Login: "blombard", - URL: "https://github.com/blombard", + Login: "zeldrinn", + URL: "https://github.com/zeldrinn", Contributions: 1, }, - { - Login: "soxipy", - URL: "https://github.com/soxipy", - Contributions: 2, - }, - { - Login: "lozdog245", - URL: "https://github.com/lozdog245", - Contributions: 2, - }, - { - Login: "if1live", - URL: "https://github.com/if1live", - Contributions: 2, - }, }...) if verbose { diff --git a/common/math/math.go b/common/math/math.go index 188d14af..376f35ad 100644 --- a/common/math/math.go +++ b/common/math/math.go @@ -12,7 +12,11 @@ var ( // ErrNoNegativeResults is returned when no negative results are allowed ErrNoNegativeResults = errors.New("cannot calculate with no negative values") // ErrInexactConversion is returned when a decimal does not convert to float exactly - ErrInexactConversion = errors.New("inexact conversion from decimal to float detected") + ErrInexactConversion = errors.New("inexact conversion from decimal to float detected") + // ErrPowerDifferenceTooSmall when values are too close when calculating the exponent value, + // it returns zero + ErrPowerDifferenceTooSmall = errors.New("calculated power is too small to use") + errZeroValue = errors.New("cannot calculate average of no values") errNegativeValueOutOfRange = errors.New("received negative number less than -1") errGeometricNegative = errors.New("cannot calculate a geometric mean with negative values") @@ -260,6 +264,9 @@ func DecimalCompoundAnnualGrowthRate(openValue, closeValue, intervalsPerYear, nu closeOverOpen := closeValue.Div(openValue) exp := intervalsPerYear.Div(numberOfIntervals) pow := DecimalPow(closeOverOpen, exp) + if pow.IsZero() { + return decimal.Zero, ErrPowerDifferenceTooSmall + } k := pow.Sub(decimal.NewFromInt(1)).Mul(decimal.NewFromInt(100)) return k, nil } @@ -381,6 +388,9 @@ func DecimalGeometricMean(values []decimal.Decimal) (decimal.Decimal, error) { // handle ^0.x and instead returns 1 func DecimalPow(x, y decimal.Decimal) decimal.Decimal { pow := math.Pow(x.InexactFloat64(), y.InexactFloat64()) + if math.IsNaN(pow) || math.IsInf(pow, 0) { + return decimal.Zero + } return decimal.NewFromFloat(pow) } diff --git a/common/math/math_test.go b/common/math/math_test.go index fc73f164..140cbc3f 100644 --- a/common/math/math_test.go +++ b/common/math/math_test.go @@ -444,6 +444,7 @@ func TestFinancialGeometricAverage(t *testing.T) { } func TestArithmeticAverage(t *testing.T) { + t.Parallel() values := []float64{1, 2, 3, 4, 5, 6, 7, 8} _, err := ArithmeticMean(nil) if !errors.Is(err, errZeroValue) { @@ -846,6 +847,7 @@ func TestDecimalFinancialGeometricAverage(t *testing.T) { } func TestDecimalArithmeticAverage(t *testing.T) { + t.Parallel() values := []decimal.Decimal{ decimal.NewFromInt(1), decimal.NewFromInt(2), @@ -869,3 +871,29 @@ func TestDecimalArithmeticAverage(t *testing.T) { t.Error("expected 4.5") } } + +func TestDecimalPow(t *testing.T) { + t.Parallel() + pow := DecimalPow(decimal.NewFromInt(2), decimal.NewFromInt(2)) + if !pow.Equal(decimal.NewFromInt(4)) { + t.Errorf("received '%v' expected '%v'", pow, 4) + } + + // zero + pow = DecimalPow(decimal.Zero, decimal.NewFromInt(1)) + if !pow.Equal(decimal.Zero) { + t.Errorf("received '%v' expected '%v'", pow, 0) + } + + // inf + pow = DecimalPow(decimal.Zero, decimal.NewFromInt(-3)) + if !pow.Equal(decimal.Zero) { + t.Errorf("received '%v' expected '%v'", pow, 0) + } + + // nan + pow = DecimalPow(decimal.NewFromInt(-1), decimal.NewFromFloat(0.1111)) + if !pow.Equal(decimal.Zero) { + t.Errorf("received '%v' expected '%v'", pow, 0) + } +} diff --git a/currency/code.go b/currency/code.go index fc7db0b5..1eebe3ee 100644 --- a/currency/code.go +++ b/currency/code.go @@ -336,3 +336,29 @@ func (c Code) IsCryptocurrency() bool { func (c Code) IsStableCurrency() bool { return c.Item != nil && c.Item.Role == Stable } + +// Currency allows an item to revert to a code +func (i *Item) Currency() Code { + if i == nil { + return EMPTYCODE + } + return NewCode(i.Symbol) +} + +// UpperCurrency allows an item to revert to a code +// taking an upper +func (i *Item) UpperCurrency() Code { + if i == nil { + return EMPTYCODE.Upper() + } + return NewCode(i.Symbol).Upper() +} + +// LowerCurrency allows an item to revert to a code +// returning in lower format +func (i *Item) LowerCurrency() Code { + if i == nil { + return EMPTYCODE.Lower() + } + return NewCode(i.Symbol).Lower() +} diff --git a/engine/currency_state_manager.md b/engine/currency_state_manager.md index e84b08aa..b3ec29aa 100644 --- a/engine/currency_state_manager.md +++ b/engine/currency_state_manager.md @@ -1,22 +1,22 @@ -# GoCryptoTrader package Currency state manager - - - - -[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml) -[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/currency_state_manager) -[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) - - -This currency_state_manager package is part of the GoCryptoTrader codebase. - -## This is still in active development - -You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). - -Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) +# GoCryptoTrader package Currency state manager + + + + +[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/currency_state_manager) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This currency_state_manager package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) ## Current Features for Currency state manager + The state manager keeps currency states up to date, which include: @@ -27,22 +27,22 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + This allows for an internal state check to compliment internal and external strategies. - -## Contribution - -Please feel free to submit any pull requests or suggest any desired features to be added. - -When submitting a PR, please abide by our coding guidelines: - -+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). -+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. -+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). -+ Pull requests need to be based on and opened against the `master` branch. -## Donations - - - -If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: - +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + ***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc*** diff --git a/engine/helpers.go b/engine/helpers.go index f21533f4..29c41e8b 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -576,7 +576,7 @@ func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[curre for x := range accounts { for y := range accounts[x].Accounts { for z := range accounts[x].Accounts[y].Currencies { - currencyName := accounts[x].Accounts[y].Currencies[z].CurrencyName + currencyName := accounts[x].Accounts[y].Currencies[z].Currency total := accounts[x].Accounts[y].Currencies[z].Total onHold := accounts[x].Accounts[y].Currencies[z].Hold avail := accounts[x].Accounts[y].Currencies[z].AvailableWithoutBorrow @@ -586,7 +586,7 @@ func GetCollatedExchangeAccountInfoByCoin(accounts []account.Holdings) map[curre info, ok := result[currencyName] if !ok { accountInfo := account.Balance{ - CurrencyName: currencyName, + Currency: currencyName, Total: total, Hold: onHold, Free: free, diff --git a/engine/helpers_test.go b/engine/helpers_test.go index a3073efb..7ee60726 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -855,9 +855,9 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { account.SubAccount{ Currencies: []account.Balance{ { - CurrencyName: currency.BTC, - Total: 100, - Hold: 0, + Currency: currency.BTC, + Total: 100, + Hold: 0, }, }, }) @@ -870,14 +870,14 @@ func TestGetCollatedExchangeAccountInfoByCoin(t *testing.T) { account.SubAccount{ Currencies: []account.Balance{ { - CurrencyName: currency.LTC, - Total: 100, - Hold: 0, + Currency: currency.LTC, + Total: 100, + Hold: 0, }, { - CurrencyName: currency.BTC, - Total: 100, - Hold: 0, + Currency: currency.BTC, + Total: 100, + Hold: 0, }, }, }) diff --git a/engine/order_manager.go b/engine/order_manager.go index 37d44a13..972956d2 100644 --- a/engine/order_manager.go +++ b/engine/order_manager.go @@ -465,7 +465,6 @@ func (m *OrderManager) Submit(ctx context.Context, newOrder *order.Submit) (*Ord if err != nil { return nil, err } - // Checks for exchange min max limits for order amounts before order // execution can occur err = exch.CheckOrderExecutionLimits(newOrder.AssetType, @@ -478,7 +477,6 @@ func (m *OrderManager) Submit(ctx context.Context, newOrder *order.Submit) (*Ord newOrder.Exchange, err) } - // Determines if current trading activity is turned off by the exchange for // the currency pair err = exch.CanTradePair(newOrder.Pair, newOrder.AssetType) @@ -683,22 +681,20 @@ func (m *OrderManager) processOrders() { err) continue } - if len(orders) > 0 && len(result) > 0 { - for z := range result { - var upsertResponse *OrderUpsertResponse - upsertResponse, err = m.UpsertOrder(&result[z]) - if err != nil { - log.Error(log.OrderMgr, err) - } else { - for i := range orders { - if orders[i].InternalOrderID != upsertResponse.OrderDetails.InternalOrderID { - continue - } - orders[i] = orders[len(orders)-1] - orders = orders[:len(orders)-1] - break - } + for z := range result { + var upsertResponse *OrderUpsertResponse + upsertResponse, err = m.UpsertOrder(&result[z]) + if err != nil { + log.Error(log.OrderMgr, err) + continue + } + for i := range orders { + if orders[i].InternalOrderID != upsertResponse.OrderDetails.InternalOrderID { + continue } + orders[i] = orders[len(orders)-1] + orders = orders[:len(orders)-1] + break } } diff --git a/engine/portfolio_manager.go b/engine/portfolio_manager.go index 11fef5e5..1845eb2c 100644 --- a/engine/portfolio_manager.go +++ b/engine/portfolio_manager.go @@ -162,7 +162,7 @@ func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) next: for z := range accounts[x].Accounts[y].Currencies { for i := range currencies { - if !accounts[x].Accounts[y].Currencies[z].CurrencyName.Equal(currencies[i].CurrencyName) { + if !accounts[x].Accounts[y].Currencies[z].Currency.Equal(currencies[i].Currency) { continue } currencies[i].Hold += accounts[x].Accounts[y].Currencies[z].Hold @@ -173,7 +173,7 @@ func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) continue next } currencies = append(currencies, account.Balance{ - CurrencyName: accounts[x].Accounts[y].Currencies[z].CurrencyName, + Currency: accounts[x].Accounts[y].Currencies[z].Currency, Total: accounts[x].Accounts[y].Currencies[z].Total, Hold: accounts[x].Accounts[y].Currencies[z].Hold, Free: accounts[x].Accounts[y].Currencies[z].Free, @@ -184,20 +184,20 @@ func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) } for j := range currencies { - if !m.base.ExchangeAddressExists(accounts[x].Exchange, currencies[j].CurrencyName) { + if !m.base.ExchangeAddressExists(accounts[x].Exchange, currencies[j].Currency) { if currencies[j].Total <= 0 { continue } log.Debugf(log.PortfolioMgr, "Portfolio: Adding new exchange address: %s, %s, %f, %s\n", accounts[x].Exchange, - currencies[j].CurrencyName, + currencies[j].Currency, currencies[j].Total, portfolio.ExchangeAddress) m.base.Addresses = append(m.base.Addresses, portfolio.Address{ Address: accounts[x].Exchange, - CoinType: currencies[j].CurrencyName, + CoinType: currencies[j].Currency, Balance: currencies[j].Total, Description: portfolio.ExchangeAddress, }) @@ -207,14 +207,14 @@ func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) if currencies[j].Total <= 0 { log.Debugf(log.PortfolioMgr, "Portfolio: Removing %s %s entry.\n", accounts[x].Exchange, - currencies[j].CurrencyName) - m.base.RemoveExchangeAddress(accounts[x].Exchange, currencies[j].CurrencyName) + currencies[j].Currency) + m.base.RemoveExchangeAddress(accounts[x].Exchange, currencies[j].Currency) continue } balance, ok := m.base.GetAddressBalance(accounts[x].Exchange, portfolio.ExchangeAddress, - currencies[j].CurrencyName) + currencies[j].Currency) if !ok { continue } @@ -222,10 +222,10 @@ func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) if balance != currencies[j].Total { log.Debugf(log.PortfolioMgr, "Portfolio: Updating %s %s entry with balance %f.\n", accounts[x].Exchange, - currencies[j].CurrencyName, + currencies[j].Currency, currencies[j].Total) m.base.UpdateExchangeAddressBalance(accounts[x].Exchange, - currencies[j].CurrencyName, + currencies[j].Currency, currencies[j].Total) } } diff --git a/engine/rpcserver.go b/engine/rpcserver.go index a68f002b..ea936353 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -192,9 +192,14 @@ func (s *RPCServer) StartRPCRESTProxy() { } go func() { - if err := http.ListenAndServe(s.Config.RemoteControl.GRPC.GRPCProxyListenAddress, mux); err != nil { - log.Errorf(log.GRPCSys, "gRPC proxy failed to server: %s\n", err) - return + server := &http.Server{ + Addr: s.Config.RemoteControl.GRPC.GRPCProxyListenAddress, + ReadHeaderTimeout: time.Minute, + ReadTimeout: time.Minute, + } + + if err = server.ListenAndServe(); err != nil { + log.Errorf(log.GRPCSys, "GRPC proxy failed to server: %s\n", err) } }() @@ -636,7 +641,7 @@ func createAccountInfoRequest(h account.Holdings) (*gctrpc.GetAccountInfoRespons continue } a.Currencies = append(a.Currencies, &gctrpc.AccountCurrencyInfo{ - Currency: y.CurrencyName.String(), + Currency: y.Currency.String(), TotalValue: y.Total, Hold: y.Hold, Free: y.Free, @@ -677,7 +682,7 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream subAccounts := make([]*gctrpc.AccountCurrencyInfo, len(initAcc.Accounts[x].Currencies)) for y := range initAcc.Accounts[x].Currencies { subAccounts[y] = &gctrpc.AccountCurrencyInfo{ - Currency: initAcc.Accounts[x].Currencies[y].CurrencyName.String(), + Currency: initAcc.Accounts[x].Currencies[y].Currency.String(), TotalValue: initAcc.Accounts[x].Currencies[y].Total, Hold: initAcc.Accounts[x].Currencies[y].Hold, } @@ -724,7 +729,7 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream subAccounts := make([]*gctrpc.AccountCurrencyInfo, len(holdings.Accounts[x].Currencies)) for y := range holdings.Accounts[x].Currencies { subAccounts[y] = &gctrpc.AccountCurrencyInfo{ - Currency: holdings.Accounts[x].Currencies[y].CurrencyName.String(), + Currency: holdings.Accounts[x].Currencies[y].Currency.String(), TotalValue: holdings.Accounts[x].Currencies[y].Total, Hold: holdings.Accounts[x].Currencies[y].Hold, } @@ -4824,15 +4829,15 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe free := decimal.NewFromFloat(acc.Currencies[i].AvailableWithoutBorrow) cal := order.CollateralCalculator{ CalculateOffline: r.CalculateOffline, - CollateralCurrency: acc.Currencies[i].CurrencyName, + CollateralCurrency: acc.Currencies[i].Currency, Asset: a, FreeCollateral: free, LockedCollateral: total.Sub(free), } if r.CalculateOffline && - !acc.Currencies[i].CurrencyName.Equal(currency.USD) { + !acc.Currencies[i].Currency.Equal(currency.USD) { var tick *ticker.Price - tickerCurr := currency.NewPair(acc.Currencies[i].CurrencyName, currency.USD) + tickerCurr := currency.NewPair(acc.Currencies[i].Currency, currency.USD) if !spotPairs.Contains(tickerCurr, true) { // cannot price currency to calculate collateral continue diff --git a/engine/rpcserver_test.go b/engine/rpcserver_test.go index 31b66771..4efe8b08 100644 --- a/engine/rpcserver_test.go +++ b/engine/rpcserver_test.go @@ -245,12 +245,12 @@ func (f fExchange) FetchAccountInfo(_ context.Context, a asset.Item) (account.Ho AssetType: a, Currencies: []account.Balance{ { - CurrencyName: currency.USD, - Total: 1337, + Currency: currency.USD, + Total: 1337, }, { - CurrencyName: currency.BTC, - Total: 13337, + Currency: currency.BTC, + Total: 13337, }, }, }, diff --git a/exchanges/account/account.go b/exchanges/account/account.go index 8c573cf9..81f4b0ce 100644 --- a/exchanges/account/account.go +++ b/exchanges/account/account.go @@ -115,7 +115,7 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding for item, balance := range currencyHoldings { balance.m.Lock() currencyBalances[target] = Balance{ - CurrencyName: currency.Code{Item: item, UpperCase: true}, + Currency: currency.Code{Item: item, UpperCase: true}, Total: balance.total, Hold: balance.hold, Free: balance.free, @@ -281,10 +281,10 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error { } for y := range incoming.Accounts[x].Currencies { - bal := currencyBalances[incoming.Accounts[x].Currencies[y].CurrencyName.Item] + bal := currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item] if bal == nil { bal = &ProtectedBalance{} - currencyBalances[incoming.Accounts[x].Currencies[y].CurrencyName.Item] = bal + currencyBalances[incoming.Accounts[x].Currencies[y].Currency.Item] = bal } bal.load(incoming.Accounts[x].Currencies[y]) } diff --git a/exchanges/account/account_test.go b/exchanges/account/account_test.go index 520b9020..90fe5980 100644 --- a/exchanges/account/account_test.go +++ b/exchanges/account/account_test.go @@ -18,7 +18,7 @@ func TestCollectBalances(t *testing.T) { accounts, err := CollectBalances( map[string][]Balance{ "someAccountID": { - {CurrencyName: currency.BTC, Total: 40000, Hold: 1}, + {Currency: currency.BTC, Total: 40000, Hold: 1}, }, }, asset.Spot, @@ -31,7 +31,7 @@ func TestCollectBalances(t *testing.T) { if subAccount.AssetType != asset.Spot { t.Error("subAccount AssetType not set correctly") } - if balance.CurrencyName != currency.BTC || balance.Total != 40000 || balance.Hold != 1 { + if balance.Currency != currency.BTC || balance.Total != 40000 || balance.Hold != 1 { t.Error("subAccount currency balance not set correctly") } if err != nil { @@ -112,9 +112,9 @@ func TestGetHoldings(t *testing.T) { ID: "1337", Currencies: []Balance{ { - CurrencyName: currency.BTC, - Total: 100, - Hold: 20, + Currency: currency.BTC, + Total: 100, + Hold: 20, }, }, }}, @@ -132,9 +132,9 @@ func TestGetHoldings(t *testing.T) { ID: "1337", Currencies: []Balance{ { - CurrencyName: currency.BTC, - Total: 100, - Hold: 20, + Currency: currency.BTC, + Total: 100, + Hold: 20, }, }, }}, @@ -182,9 +182,9 @@ func TestGetHoldings(t *testing.T) { t.Errorf("expecting 1337 but received %s", u.Accounts[0].ID) } - if !u.Accounts[0].Currencies[0].CurrencyName.Equal(currency.BTC) { + if !u.Accounts[0].Currencies[0].Currency.Equal(currency.BTC) { t.Errorf("expecting BTC but received %s", - u.Accounts[0].Currencies[0].CurrencyName) + u.Accounts[0].Currencies[0].Currency) } if u.Accounts[0].Currencies[0].Total != 100 { @@ -228,9 +228,9 @@ func TestGetHoldings(t *testing.T) { AssetType: asset.MarginFunding, Currencies: []Balance{ { - CurrencyName: currency.BTC, - Total: 100000, - Hold: 20, + Currency: currency.BTC, + Total: 100000, + Hold: 20, }, }, }}, @@ -309,9 +309,9 @@ func TestGetBalance(t *testing.T) { ID: "1337", Currencies: []Balance{ { - CurrencyName: currency.BTC, - Total: 2, - Hold: 1, + Currency: currency.BTC, + Total: 2, + Hold: 1, }, }, }, @@ -424,9 +424,9 @@ func TestUpdate(t *testing.T) { ID: "1337", Currencies: []Balance{ { - CurrencyName: currency.BTC, - Total: 100, - Hold: 20, + Currency: currency.BTC, + Total: 100, + Hold: 20, }, }, }, @@ -436,9 +436,9 @@ func TestUpdate(t *testing.T) { ID: "1337", Currencies: []Balance{ { - CurrencyName: currency.BTC, - Total: 100, - Hold: 20, + Currency: currency.BTC, + Total: 100, + Hold: 20, }, }, }, @@ -456,9 +456,9 @@ func TestUpdate(t *testing.T) { ID: "1337", Currencies: []Balance{ { - CurrencyName: currency.BTC, - Total: 100, - Hold: 20, + Currency: currency.BTC, + Total: 100, + Hold: 20, }, }, }, diff --git a/exchanges/account/account_types.go b/exchanges/account/account_types.go index 644c34a6..8e8c1280 100644 --- a/exchanges/account/account_types.go +++ b/exchanges/account/account_types.go @@ -50,9 +50,9 @@ type SubAccount struct { Currencies []Balance } -// Balance is a sub type to store currency name and individual totals +// Balance is a sub-type to store currency name and individual totals type Balance struct { - CurrencyName currency.Code + Currency currency.Code Total float64 Hold float64 Free float64 diff --git a/exchanges/account/credentials.go b/exchanges/account/credentials.go index b68067e6..3ac0e8e1 100644 --- a/exchanges/account/credentials.go +++ b/exchanges/account/credentials.go @@ -118,7 +118,7 @@ func (c *Credentials) Equal(other *Credentials) bool { other != nil && c.Key == other.Key && c.ClientID == other.ClientID && - c.SubAccount == other.SubAccount + (c.SubAccount == other.SubAccount || c.SubAccount == "" && other.SubAccount == "main" || c.SubAccount == "main" && other.SubAccount == "") } // ContextCredentialsStore protects the stored credentials for use in a context diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index e95b53dc..2bcf2c36 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -104,10 +104,10 @@ func (a *Alphapoint) UpdateAccountInfo(ctx context.Context, assetType asset.Item balances := make([]account.Balance, len(acc.Currencies)) for i := range acc.Currencies { balances[i] = account.Balance{ - CurrencyName: currency.NewCode(acc.Currencies[i].Name), - Total: float64(acc.Currencies[i].Balance), - Hold: float64(acc.Currencies[i].Hold), - Free: float64(acc.Currencies[i].Balance) - float64(acc.Currencies[i].Hold), + Currency: currency.NewCode(acc.Currencies[i].Name), + Total: float64(acc.Currencies[i].Balance), + Hold: float64(acc.Currencies[i].Hold), + Free: float64(acc.Currencies[i].Balance) - float64(acc.Currencies[i].Hold), } } diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 6efe2e60..25b0edfd 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -1189,10 +1189,9 @@ func (b *Binance) FetchSpotExchangeLimits(ctx context.Context) ([]order.MinMaxLe assets = append(assets, asset.Spot) case "MARGIN": assets = append(assets, asset.Margin) - case "LEVERAGED", "TRD_GRP_003", "TRD_GRP_004", "TRD_GRP_005": // unused permissions default: - return nil, fmt.Errorf("unhandled asset type for exchange limits loading %s", - spot.Symbols[x].Permissions[y]) + // "LEVERAGED", "TRD_GRP_003", "TRD_GRP_004", "TRD_GRP_005" etc are unused permissions + // for spot exchange limits } } diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 10cc49ac..2b725d33 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -706,10 +706,10 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( locked := raw.Balances[i].Locked.InexactFloat64() currencyBalance = append(currencyBalance, account.Balance{ - CurrencyName: currency.NewCode(raw.Balances[i].Asset), - Total: free + locked, - Hold: locked, - Free: free, + Currency: currency.NewCode(raw.Balances[i].Asset), + Total: free + locked, + Hold: locked, + Free: free, }) } @@ -723,10 +723,10 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( var currencyDetails []account.Balance for i := range accData.Assets { currencyDetails = append(currencyDetails, account.Balance{ - CurrencyName: currency.NewCode(accData.Assets[i].Asset), - Total: accData.Assets[i].WalletBalance, - Hold: accData.Assets[i].WalletBalance - accData.Assets[i].AvailableBalance, - Free: accData.Assets[i].AvailableBalance, + Currency: currency.NewCode(accData.Assets[i].Asset), + Total: accData.Assets[i].WalletBalance, + Hold: accData.Assets[i].WalletBalance - accData.Assets[i].AvailableBalance, + Free: accData.Assets[i].AvailableBalance, }) } @@ -742,10 +742,10 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( currencyDetails := accountCurrencyDetails[accData[i].AccountAlias] accountCurrencyDetails[accData[i].AccountAlias] = append( currencyDetails, account.Balance{ - CurrencyName: currency.NewCode(accData[i].Asset), - Total: accData[i].Balance, - Hold: accData[i].Balance - accData[i].AvailableBalance, - Free: accData[i].AvailableBalance, + Currency: currency.NewCode(accData[i].Asset), + Total: accData[i].Balance, + Hold: accData[i].Balance - accData[i].AvailableBalance, + Free: accData[i].AvailableBalance, }, ) } @@ -761,7 +761,7 @@ func (b *Binance) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( var currencyDetails []account.Balance for i := range accData.UserAssets { currencyDetails = append(currencyDetails, account.Balance{ - CurrencyName: currency.NewCode(accData.UserAssets[i].Asset), + Currency: currency.NewCode(accData.UserAssets[i].Asset), Total: accData.UserAssets[i].Free + accData.UserAssets[i].Locked, Hold: accData.UserAssets[i].Locked, Free: accData.UserAssets[i].Free, @@ -1772,7 +1772,7 @@ func (b *Binance) GetHistoricCandlesExtended(ctx context.Context, pair currency. if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/binanceus/binanceus_wrapper.go b/exchanges/binanceus/binanceus_wrapper.go index e9fe7bc3..1db8e066 100644 --- a/exchanges/binanceus/binanceus_wrapper.go +++ b/exchanges/binanceus/binanceus_wrapper.go @@ -441,10 +441,10 @@ func (bi *Binanceus) UpdateAccountInfo(ctx context.Context, assetType asset.Item locked := theAccount.Balances[i].Locked.InexactFloat64() currencyBalance[i] = account.Balance{ - CurrencyName: currency.NewCode(theAccount.Balances[i].Asset), - Total: freeBalance + locked, - Hold: locked, - Free: freeBalance, + Currency: currency.NewCode(theAccount.Balances[i].Asset), + Total: freeBalance + locked, + Hold: locked, + Free: freeBalance, } } acc.Currencies = currencyBalance @@ -957,7 +957,7 @@ func (bi *Binanceus) GetHistoricCandlesExtended(ctx context.Context, pair curren if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", bi.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 3952980f..4d1f7d18 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -516,10 +516,10 @@ func (b *Bitfinex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) if Accounts[i].ID == accountBalance[x].Type { Accounts[i].Currencies = append(Accounts[i].Currencies, account.Balance{ - CurrencyName: currency.NewCode(accountBalance[x].Currency), - Total: accountBalance[x].Amount, - Hold: accountBalance[x].Amount - accountBalance[x].Available, - Free: accountBalance[x].Available, + Currency: currency.NewCode(accountBalance[x].Currency), + Total: accountBalance[x].Amount, + Hold: accountBalance[x].Amount - accountBalance[x].Available, + Free: accountBalance[x].Available, }) } } @@ -1174,7 +1174,7 @@ func (b *Bitfinex) GetHistoricCandlesExtended(ctx context.Context, pair currency if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 865dc04c..8e62f2d8 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -380,10 +380,10 @@ func (b *Bithumb) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( } exchangeBalances = append(exchangeBalances, account.Balance{ - CurrencyName: currency.NewCode(key), - Total: totalAmount, - Hold: hold, - Free: avail, + Currency: currency.NewCode(key), + Total: totalAmount, + Hold: hold, + Free: avail, }) } diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index e7602dd9..4dda6520 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -513,8 +513,8 @@ func (b *Bitmex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a accountBalances[accountID] = append( accountBalances[accountID], account.Balance{ - CurrencyName: currency.NewCode(wallet.Currency), - Total: wallet.Amount, + Currency: currency.NewCode(wallet.Currency), + Total: wallet.Amount, }, ) } diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 087e2459..8d9016fd 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -442,10 +442,10 @@ func (b *Bitstamp) UpdateAccountInfo(ctx context.Context, assetType asset.Item) currencies := make([]account.Balance, 0, len(accountBalance)) for k, v := range accountBalance { currencies = append(currencies, account.Balance{ - CurrencyName: currency.NewCode(k), - Total: v.Balance, - Hold: v.Reserved, - Free: v.Available, + Currency: currency.NewCode(k), + Total: v.Balance, + Hold: v.Reserved, + Free: v.Available, }) } response.Accounts = append(response.Accounts, account.SubAccount{ @@ -956,7 +956,7 @@ func (b *Bitstamp) GetHistoricCandlesExtended(ctx context.Context, pair currency if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index d722ab0f..ec129724 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -410,10 +410,10 @@ func (b *Bittrex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( currencies := make([]account.Balance, len(balanceData)) for i := range balanceData { currencies[i] = account.Balance{ - CurrencyName: currency.NewCode(balanceData[i].CurrencySymbol), - Total: balanceData[i].Total, - Hold: balanceData[i].Total - balanceData[i].Available, - Free: balanceData[i].Available, + Currency: currency.NewCode(balanceData[i].CurrencySymbol), + Total: balanceData[i].Total, + Hold: balanceData[i].Total - balanceData[i].Available, + Free: balanceData[i].Available, } } @@ -1044,7 +1044,7 @@ func (b *Bittrex) GetHistoricCandles(ctx context.Context, pair currency.Pair, a }) } ret.SortCandlesByTimestamp(false) - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() return ret, nil } diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 6a76b090..42e53c35 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -447,10 +447,10 @@ func (b *BTCMarkets) UpdateAccountInfo(ctx context.Context, assetType asset.Item acc.AssetType = assetType for x := range data { acc.Currencies = append(acc.Currencies, account.Balance{ - CurrencyName: currency.NewCode(data[x].AssetName), - Total: data[x].Balance, - Hold: data[x].Locked, - Free: data[x].Available, + Currency: currency.NewCode(data[x].AssetName), + Total: data[x].Balance, + Hold: data[x].Locked, + Free: data[x].Available, }) } resp.Accounts = append(resp.Accounts, acc) @@ -1148,7 +1148,7 @@ func (b *BTCMarkets) GetHistoricCandlesExtended(ctx context.Context, p currency. if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", b.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index bd28639a..d6cb8739 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -401,10 +401,10 @@ func (b *BTSE) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc currencies := make([]account.Balance, len(balance)) for b := range balance { currencies[b] = account.Balance{ - CurrencyName: currency.NewCode(balance[b].Currency), - Total: balance[b].Total, - Hold: balance[b].Total - balance[b].Available, - Free: balance[b].Available, + Currency: currency.NewCode(balance[b].Currency), + Total: balance[b].Total, + Hold: balance[b].Total - balance[b].Available, + Free: balance[b].Available, } } a.Exchange = b.Name diff --git a/exchanges/bybit/bybit_wrapper.go b/exchanges/bybit/bybit_wrapper.go index b85814bf..2d55fd8c 100644 --- a/exchanges/bybit/bybit_wrapper.go +++ b/exchanges/bybit/bybit_wrapper.go @@ -712,10 +712,10 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a currencyBalance := make([]account.Balance, len(balances)) for i := range balances { currencyBalance[i] = account.Balance{ - CurrencyName: currency.NewCode(balances[i].CoinName), - Total: balances[i].Total, - Hold: balances[i].Locked, - Free: balances[i].Total - balances[i].Locked, + Currency: currency.NewCode(balances[i].CoinName), + Total: balances[i].Total, + Hold: balances[i].Locked, + Free: balances[i].Total - balances[i].Locked, } } @@ -731,10 +731,10 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a currencyBalance := make([]account.Balance, len(balances)) for coinName, data := range balances { currencyBalance[i] = account.Balance{ - CurrencyName: currency.NewCode(coinName), - Total: data.WalletBalance, - Hold: data.WalletBalance - data.AvailableBalance, - Free: data.AvailableBalance, + Currency: currency.NewCode(coinName), + Total: data.WalletBalance, + Hold: data.WalletBalance - data.AvailableBalance, + Free: data.AvailableBalance, } i++ } @@ -749,10 +749,10 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a acc.Currencies = []account.Balance{ { - CurrencyName: currency.USD, - Total: balance.WalletBalance, - Hold: balance.WalletBalance - balance.AvailableBalance, - Free: balance.AvailableBalance, + Currency: currency.USD, + Total: balance.WalletBalance, + Hold: balance.WalletBalance - balance.AvailableBalance, + Free: balance.AvailableBalance, }, } @@ -2037,7 +2037,7 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", by.Name, summary) } - klineItem.RemoveDuplicates() + klineItem.RemoveDuplicateCandlesByTime() klineItem.RemoveOutsideRange(start, end) klineItem.SortCandlesByTimestamp(false) return klineItem, nil diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index ccf60c85..8fe05bca 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -329,7 +329,7 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite profileID := accountBalance[i].ProfileID currencies := accountCurrencies[profileID] accountCurrencies[profileID] = append(currencies, account.Balance{ - CurrencyName: currency.NewCode(accountBalance[i].Currency), + Currency: currency.NewCode(accountBalance[i].Currency), Total: accountBalance[i].Balance, Hold: accountBalance[i].Hold, Free: accountBalance[i].Available, @@ -1013,7 +1013,7 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, p currency if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", c.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index ff68978e..fe1e7c98 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -324,60 +324,60 @@ func (c *COINUT) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a var balances = []account.Balance{ { - CurrencyName: currency.BCH, - Total: bal.BCH, + Currency: currency.BCH, + Total: bal.BCH, }, { - CurrencyName: currency.BTC, - Total: bal.BTC, + Currency: currency.BTC, + Total: bal.BTC, }, { - CurrencyName: currency.BTG, - Total: bal.BTG, + Currency: currency.BTG, + Total: bal.BTG, }, { - CurrencyName: currency.CAD, - Total: bal.CAD, + Currency: currency.CAD, + Total: bal.CAD, }, { - CurrencyName: currency.ETC, - Total: bal.ETC, + Currency: currency.ETC, + Total: bal.ETC, }, { - CurrencyName: currency.ETH, - Total: bal.ETH, + Currency: currency.ETH, + Total: bal.ETH, }, { - CurrencyName: currency.LCH, - Total: bal.LCH, + Currency: currency.LCH, + Total: bal.LCH, }, { - CurrencyName: currency.LTC, - Total: bal.LTC, + Currency: currency.LTC, + Total: bal.LTC, }, { - CurrencyName: currency.MYR, - Total: bal.MYR, + Currency: currency.MYR, + Total: bal.MYR, }, { - CurrencyName: currency.SGD, - Total: bal.SGD, + Currency: currency.SGD, + Total: bal.SGD, }, { - CurrencyName: currency.USD, - Total: bal.USD, + Currency: currency.USD, + Total: bal.USD, }, { - CurrencyName: currency.USDT, - Total: bal.USDT, + Currency: currency.USDT, + Total: bal.USDT, }, { - CurrencyName: currency.XMR, - Total: bal.XMR, + Currency: currency.XMR, + Total: bal.XMR, }, { - CurrencyName: currency.ZEC, - Total: bal.ZEC, + Currency: currency.ZEC, + Total: bal.ZEC, }, } info.Exchange = c.Name diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 82b3ba20..b2c6dec0 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -1444,7 +1444,7 @@ func (b *Base) CalculateTotalCollateral(ctx context.Context, calculator *order.T } // GetCollateralCurrencyForContract returns the collateral currency for an asset and contract pair -func (b *Base) GetCollateralCurrencyForContract(asset.Item, currency.Pair) (currency.Code, asset.Item, error) { +func (b *Base) GetCollateralCurrencyForContract(a asset.Item, cp currency.Pair) (currency.Code, asset.Item, error) { return currency.Code{}, asset.Empty, common.ErrNotYetImplemented } diff --git a/exchanges/exchange_test.go b/exchanges/exchange_test.go index a67a8171..f3a4a38f 100644 --- a/exchanges/exchange_test.go +++ b/exchanges/exchange_test.go @@ -2644,3 +2644,41 @@ func TestSetRequester(t *testing.T) { t.Fatal("requester not set correctly") } } + +func TestGetCollateralCurrencyForContract(t *testing.T) { + t.Parallel() + b := Base{} + _, _, err := b.GetCollateralCurrencyForContract(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE)) + if !errors.Is(err, common.ErrNotYetImplemented) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNotYetImplemented) + } +} + +func TestGetCurrencyForRealisedPNL(t *testing.T) { + t.Parallel() + b := Base{} + _, _, err := b.GetCurrencyForRealisedPNL(asset.Empty, currency.EMPTYPAIR) + if !errors.Is(err, common.ErrNotYetImplemented) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNotYetImplemented) + } +} + +func TestHasAssetTypeAccountSegregation(t *testing.T) { + t.Parallel() + b := Base{ + Name: "RAWR", + Features: Features{ + Supports: FeaturesSupported{ + REST: true, + RESTCapabilities: protocol.Features{ + HasAssetTypeAccountSegregation: true, + }, + }, + }, + } + + has := b.HasAssetTypeAccountSegregation() + if !has { + t.Errorf("expected '%v' received '%v'", true, false) + } +} diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index de8d46cf..914a4d20 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -361,7 +361,7 @@ func (e *EXMO) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc currencies := make([]account.Balance, 0, len(result.Balances)) for x, y := range result.Balances { var exchangeCurrency account.Balance - exchangeCurrency.CurrencyName = currency.NewCode(x) + exchangeCurrency.Currency = currency.NewCode(x) for z, w := range result.Reserved { if z != x { continue diff --git a/exchanges/ftx/ftx.go b/exchanges/ftx/ftx.go index b6ca0fc3..0382326c 100644 --- a/exchanges/ftx/ftx.go +++ b/exchanges/ftx/ftx.go @@ -764,7 +764,11 @@ func (f *FTX) Order( req := make(map[string]interface{}) req["market"] = marketName req["side"] = side - req["price"] = price + if orderType == "market" { + req["price"] = nil + } else { + req["price"] = price + } req["type"] = orderType req["size"] = size if reduceOnly { @@ -922,7 +926,7 @@ func (f *FTX) DeleteTriggerOrder(ctx context.Context, orderID string) (string, e // GetFills gets order fills data and ensures that all // fills are retrieved from the supplied timeframe -func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Item, startTime, endTime time.Time) ([]FillsData, error) { +func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Item, startTime, endTime time.Time, orderID string) ([]FillsData, error) { var resp []FillsData var nextEnd = endTime limit := 200 @@ -946,6 +950,9 @@ func (f *FTX) GetFills(ctx context.Context, market currency.Pair, item asset.Ite params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) params.Set("end_time", strconv.FormatInt(nextEnd.Unix(), 10)) } + if orderID != "" { + params.Set("orderId", orderID) + } endpoint := common.EncodeURLValues(getFills, params) err := f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, endpoint, nil, &data) if err != nil { diff --git a/exchanges/ftx/ftx_test.go b/exchanges/ftx/ftx_test.go index f8ba0453..5b42d017 100644 --- a/exchanges/ftx/ftx_test.go +++ b/exchanges/ftx/ftx_test.go @@ -678,6 +678,7 @@ func TestSubmitOrder(t *testing.T) { Amount: 1, AssetType: asset.Spot, ClientOrderID: "order12345679$$$$$", + RetrieveFees: true, } _, err = f.SubmitOrder(context.Background(), orderSubmission) if err != nil { @@ -766,25 +767,30 @@ func TestGetFills(t *testing.T) { t.Skip() } _, err := f.GetFills(context.Background(), - currency.Pair{}, asset.Futures, time.Now().Add(time.Hour*24*365), time.Now()) + currency.Pair{}, asset.Futures, time.Now().Add(time.Hour*24*365), time.Now(), "") if !errors.Is(err, errStartTimeCannotBeAfterEndTime) { t.Errorf("received '%v' expected '%v'", err, errStartTimeCannotBeAfterEndTime) } _, err = f.GetFills(context.Background(), - currency.Pair{}, asset.Futures, time.Time{}, time.Time{}) + currency.Pair{}, asset.Futures, time.Time{}, time.Time{}, "") if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } _, err = f.GetFills(context.Background(), - currency.Pair{}, asset.Futures, time.Now().Add(-time.Hour*24*365), time.Now()) + currency.Pair{}, asset.Futures, time.Now().Add(-time.Hour*24*365), time.Now(), "") if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } _, err = f.GetFills(context.Background(), - spotPair, asset.Spot, time.Now().Add(-time.Hour*24*365), time.Now()) + spotPair, asset.Spot, time.Now().Add(-time.Hour*24*365), time.Now(), "") + if !errors.Is(err, nil) { + t.Errorf("received '%v' expected '%v'", err, nil) + } + _, err = f.GetFills(context.Background(), + currency.EMPTYPAIR, asset.Futures, time.Time{}, time.Time{}, "177453606715") if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } @@ -2819,3 +2825,36 @@ func TestGetReferralRebateRate(t *testing.T) { t.Fatal(err) } } + +func TestGetCollateralCurrencyForContract(t *testing.T) { + t.Parallel() + c, a, err := f.GetCollateralCurrencyForContract(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE)) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + if a != asset.Futures { + t.Fatalf("received: '%v' but expected: '%v'", a, asset.Futures) + } + if !c.Equal(currency.USD) { + t.Fatalf("received: '%v' but expected: '%v'", c, currency.USD) + } +} + +func TestGetCurrencyForRealisedPNL(t *testing.T) { + t.Parallel() + c, a, err := f.GetCurrencyForRealisedPNL(asset.Futures, currency.NewPair(currency.XRP, currency.BABYDOGE)) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + if a != asset.Spot { + t.Fatalf("received: '%v' but expected: '%v'", a, asset.Spot) + } + if !c.Equal(currency.USD) { + t.Fatalf("received: '%v' but expected: '%v'", c, currency.USD) + } + + _, _, err = f.GetCurrencyForRealisedPNL(asset.Spot, currency.NewPair(currency.SHIB, currency.DOGE)) + if !errors.Is(err, order.ErrNotFuturesAsset) { + t.Fatalf("received: '%v' but expected: '%v'", err, order.ErrNotFuturesAsset) + } +} diff --git a/exchanges/ftx/ftx_types.go b/exchanges/ftx/ftx_types.go index 3bfc0cc7..fc1b2d4c 100644 --- a/exchanges/ftx/ftx_types.go +++ b/exchanges/ftx/ftx_types.go @@ -337,7 +337,7 @@ type OrderData struct { ClientID string `json:"clientId"` CreatedAt time.Time `json:"createdAt"` FilledSize float64 `json:"filledSize"` - Future currency.Pair `json:"future"` + Future string `json:"future"` ID int64 `json:"id"` IOC bool `json:"ioc"` Market currency.Pair `json:"market"` diff --git a/exchanges/ftx/ftx_wrapper.go b/exchanges/ftx/ftx_wrapper.go index ebef1536..199f8776 100644 --- a/exchanges/ftx/ftx_wrapper.go +++ b/exchanges/ftx/ftx_wrapper.go @@ -168,6 +168,14 @@ func (f *FTX) SetDefaults() { f.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit f.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout f.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit + + err = f.LoadCollateralWeightings(context.TODO()) + if err != nil { + log.Errorf(log.ExchangeSys, + "%s failed to store collateral weightings. Err: %s", + f.Name, + err) + } } // Setup takes in the supplied exchange configuration details and sets params @@ -206,15 +214,6 @@ func (f *FTX) Setup(exch *config.Exchange) error { return err } - if err = f.CurrencyPairs.IsAssetEnabled(asset.Futures); err == nil { - err = f.LoadCollateralWeightings(context.TODO()) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to store collateral weightings. Err: %s", - f.Name, - err) - } - } return f.Websocket.SetupNewConnection(stream.ConnectionSetup{ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout, ResponseMaxLimit: exch.WebsocketResponseMaxLimit, @@ -490,7 +489,7 @@ func (f *FTX) UpdateAccountInfo(ctx context.Context, a asset.Item) (account.Hold hold := balances[x].Total - balances[x].AvailableWithoutBorrow acc.Currencies = append(acc.Currencies, account.Balance{ - CurrencyName: balances[x].Coin, + Currency: balances[x].Coin, Total: balances[x].Total, Hold: hold, AvailableWithoutBorrow: balances[x].AvailableWithoutBorrow, @@ -648,10 +647,10 @@ func (f *FTX) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitRe return nil, err } - if s.Side == order.Ask { + if s.Side.IsShort() { s.Side = order.Sell } - if s.Side == order.Bid { + if s.Side.IsLong() { s.Side = order.Buy } @@ -674,7 +673,43 @@ func (f *FTX) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitRe return nil, err } - return s.DeriveSubmitResponse(strconv.FormatInt(tempResp.ID, 10)) + resp, err := s.DeriveSubmitResponse(strconv.FormatInt(tempResp.ID, 10)) + if err != nil { + return nil, err + } + if !s.RetrieveFees { + return resp, nil + } + time.Sleep(s.RetrieveFeeDelay) + fills, err := f.GetFills(ctx, s.Pair, s.AssetType, time.Time{}, time.Time{}, strconv.FormatInt(tempResp.ID, 10)) + if err != nil { + // choosing to return with no error so that a valid order is still returned to caller + log.Errorf(log.ExchangeSys, "could not retrieve fees for order %v: %v", tempResp.ID, err) + return resp, nil + } + for i := range fills { + resp.Fee += fills[i].Fee + var side order.Side + side, err = order.StringToOrderSide(fills[i].Side) + if err != nil { + return nil, err + } + if resp.FeeAsset.IsEmpty() { + resp.FeeAsset = fills[i].FeeCurrency + } + resp.Trades = append(resp.Trades, order.TradeHistory{ + Price: fills[i].Price, + Amount: fills[i].Size, + Fee: fills[i].Fee, + Exchange: f.Name, + TID: strconv.FormatInt(fills[i].TradeID, 10), + Side: side, + Timestamp: fills[i].Time, + IsMaker: fills[i].Liquidity == "maker", + FeeAsset: fills[i].FeeCurrency.String(), + }) + } + return resp, nil } // ModifyOrder will allow of changing orderbook placement and limit to @@ -1270,7 +1305,7 @@ func (f *FTX) GetHistoricCandlesExtended(ctx context.Context, p currency.Pair, a if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", f.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil @@ -1696,7 +1731,7 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, request *order.PositionsR allPositions: for { var fills []FillsData - fills, err = f.GetFills(ctx, request.Pairs[x], request.Asset, request.StartDate, endTime) + fills, err = f.GetFills(ctx, request.Pairs[x], request.Asset, request.StartDate, endTime, "") if err != nil { return nil, err } @@ -1760,12 +1795,15 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, request *order.PositionsR } // GetCollateralCurrencyForContract returns the collateral currency for an asset and contract pair -func (f *FTX) GetCollateralCurrencyForContract(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) { +func (f *FTX) GetCollateralCurrencyForContract(a asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) { return currency.USD, asset.Futures, nil } // GetCurrencyForRealisedPNL returns where to put realised PNL -func (f *FTX) GetCurrencyForRealisedPNL(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) { +func (f *FTX) GetCurrencyForRealisedPNL(a asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) { + if !a.IsFutures() { + return currency.EMPTYCODE, asset.Empty, fmt.Errorf("%v %w", a, order.ErrNotFuturesAsset) + } return currency.USD, asset.Spot, nil } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 7a65472a..3823d515 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -357,10 +357,10 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a var currData []account.Balance for k := range resp.Result { currData = append(currData, account.Balance{ - CurrencyName: currency.NewCode(k), - Total: resp.Result[k].Available + resp.Result[k].Freeze, - Hold: resp.Result[k].Freeze, - Free: resp.Result[k].Available, + Currency: currency.NewCode(k), + Total: resp.Result[k].Available + resp.Result[k].Freeze, + Hold: resp.Result[k].Freeze, + Free: resp.Result[k].Available, }) } info.Accounts = append(info.Accounts, account.SubAccount{ @@ -383,8 +383,8 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a } balances = append(balances, account.Balance{ - CurrencyName: currency.NewCode(x), - Hold: lockedF, + Currency: currency.NewCode(x), + Hold: lockedF, }) } default: @@ -402,7 +402,7 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a var updated bool for i := range balances { - if !balances[i].CurrencyName.Equal(currency.NewCode(x)) { + if !balances[i].Currency.Equal(currency.NewCode(x)) { continue } balances[i].Total = balances[i].Hold + availAmount @@ -413,8 +413,8 @@ func (g *Gateio) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a } if !updated { balances = append(balances, account.Balance{ - CurrencyName: currency.NewCode(x), - Total: availAmount, + Currency: currency.NewCode(x), + Total: availAmount, }) } } diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index e267d82b..b56eb309 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -324,10 +324,10 @@ func (g *Gemini) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a currencies := make([]account.Balance, len(accountBalance)) for i := range accountBalance { currencies[i] = account.Balance{ - CurrencyName: currency.NewCode(accountBalance[i].Currency), - Total: accountBalance[i].Amount, - Hold: accountBalance[i].Amount - accountBalance[i].Available, - Free: accountBalance[i].Available, + Currency: currency.NewCode(accountBalance[i].Currency), + Total: accountBalance[i].Amount, + Hold: accountBalance[i].Amount - accountBalance[i].Available, + Free: accountBalance[i].Available, } } diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 18ff2b40..74718bf5 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -436,10 +436,10 @@ func (h *HitBTC) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a currencies := make([]account.Balance, 0, len(accountBalance)) for i := range accountBalance { currencies = append(currencies, account.Balance{ - CurrencyName: currency.NewCode(accountBalance[i].Currency), - Total: accountBalance[i].Available + accountBalance[i].Reserved, - Hold: accountBalance[i].Reserved, - Free: accountBalance[i].Available, + Currency: currency.NewCode(accountBalance[i].Currency), + Total: accountBalance[i].Available + accountBalance[i].Reserved, + Hold: accountBalance[i].Reserved, + Free: accountBalance[i].Available, }) } @@ -951,7 +951,7 @@ func (h *HitBTC) GetHistoricCandlesExtended(ctx context.Context, pair currency.P if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", h.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 368d2e03..56f2e34d 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -639,8 +639,8 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac continue } currData := account.Balance{ - CurrencyName: currency.NewCode(resp.Data[i].List[0].Currency), - Total: resp.Data[i].List[0].Balance, + Currency: currency.NewCode(resp.Data[i].List[0].Currency), + Total: resp.Data[i].List[0].Balance, } if len(resp.Data[i].List) > 1 && resp.Data[i].List[1].Type == "frozen" { currData.Hold = resp.Data[i].List[1].Balance @@ -668,7 +668,7 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac for j := range balances { frozen := balances[j].Type == "frozen" for i := range currencyDetails { - if currencyDetails[i].CurrencyName.String() == balances[j].Currency { + if currencyDetails[i].Currency.String() == balances[j].Currency { if frozen { currencyDetails[i].Hold = balances[j].Balance } else { @@ -681,14 +681,14 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac if frozen { currencyDetails = append(currencyDetails, account.Balance{ - CurrencyName: currency.NewCode(balances[j].Currency), - Hold: balances[j].Balance, + Currency: currency.NewCode(balances[j].Currency), + Hold: balances[j].Balance, }) } else { currencyDetails = append(currencyDetails, account.Balance{ - CurrencyName: currency.NewCode(balances[j].Currency), - Total: balances[j].Balance, + Currency: currency.NewCode(balances[j].Currency), + Total: balances[j].Balance, }) } } @@ -706,10 +706,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac var mainAcctBalances []account.Balance for x := range acctInfo.Data { mainAcctBalances = append(mainAcctBalances, account.Balance{ - CurrencyName: currency.NewCode(acctInfo.Data[x].Symbol), - Total: acctInfo.Data[x].MarginBalance, - Hold: acctInfo.Data[x].MarginFrozen, - Free: acctInfo.Data[x].MarginAvailable, + Currency: currency.NewCode(acctInfo.Data[x].Symbol), + Total: acctInfo.Data[x].MarginBalance, + Hold: acctInfo.Data[x].MarginFrozen, + Free: acctInfo.Data[x].MarginAvailable, }) } @@ -733,10 +733,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac } for y := range a.Data { currencyDetails = append(currencyDetails, account.Balance{ - CurrencyName: currency.NewCode(a.Data[y].Symbol), - Total: a.Data[y].MarginBalance, - Hold: a.Data[y].MarginFrozen, - Free: a.Data[y].MarginAvailable, + Currency: currency.NewCode(a.Data[y].Symbol), + Total: a.Data[y].MarginBalance, + Hold: a.Data[y].MarginFrozen, + Free: a.Data[y].MarginAvailable, }) } } @@ -751,10 +751,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac var mainAcctBalances []account.Balance for x := range mainAcctData.AccData { mainAcctBalances = append(mainAcctBalances, account.Balance{ - CurrencyName: currency.NewCode(mainAcctData.AccData[x].Symbol), - Total: mainAcctData.AccData[x].MarginBalance, - Hold: mainAcctData.AccData[x].MarginFrozen, - Free: mainAcctData.AccData[x].MarginAvailable, + Currency: currency.NewCode(mainAcctData.AccData[x].Symbol), + Total: mainAcctData.AccData[x].MarginBalance, + Hold: mainAcctData.AccData[x].MarginFrozen, + Free: mainAcctData.AccData[x].MarginAvailable, }) } @@ -778,10 +778,10 @@ func (h *HUOBI) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac } for y := range a.AssetsData { currencyDetails = append(currencyDetails, account.Balance{ - CurrencyName: currency.NewCode(a.AssetsData[y].Symbol), - Total: a.AssetsData[y].MarginBalance, - Hold: a.AssetsData[y].MarginFrozen, - Free: a.AssetsData[y].MarginAvailable, + Currency: currency.NewCode(a.AssetsData[y].Symbol), + Total: a.AssetsData[y].MarginBalance, + Hold: a.AssetsData[y].MarginFrozen, + Free: a.AssetsData[y].MarginAvailable, }) } } diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index 5100765a..36b99141 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -286,10 +286,10 @@ func (i *ItBit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac fullBalance := make([]account.Balance, 0, len(amounts)) for key := range amounts { fullBalance = append(fullBalance, account.Balance{ - CurrencyName: currency.NewCode(key), - Total: amounts[key].Total, - Hold: amounts[key].Hold, - Free: amounts[key].Free, + Currency: currency.NewCode(key), + Total: amounts[key].Total, + Hold: amounts[key].Hold, + Free: amounts[key].Free, }) } diff --git a/exchanges/kline/kline.go b/exchanges/kline/kline.go index 0e3c94f2..f3d98bf7 100644 --- a/exchanges/kline/kline.go +++ b/exchanges/kline/kline.go @@ -183,17 +183,14 @@ func (k *Item) FillMissingDataWithEmptyEntries(i *IntervalRangeHolder) { } } -// RemoveDuplicates removes any duplicate candles -func (k *Item) RemoveDuplicates() { +// RemoveDuplicateCandlesByTime removes any duplicate candles +func (k *Item) RemoveDuplicateCandlesByTime() { var newCandles []Candle + candleMap := make(map[int64]struct{}) for x := range k.Candles { - if x == 0 { - newCandles = append(newCandles, k.Candles[x]) - continue - } - if !k.Candles[x].Time.Equal(k.Candles[x-1].Time) { - // don't add duplicate + if _, ok := candleMap[k.Candles[x].Time.UnixNano()]; !ok { newCandles = append(newCandles, k.Candles[x]) + candleMap[k.Candles[x].Time.UnixNano()] = struct{}{} } } @@ -566,3 +563,20 @@ func CreateIntervalTime(tt time.Time) IntervalTime { func (i *IntervalTime) Equal(tt time.Time) bool { return tt.Unix() == i.Ticks } + +// EqualSource checks whether two sets of candles +// come from the same data source +func (k *Item) EqualSource(i *Item) error { + if k == nil || i == nil { + return common.ErrNilPointer + } + if k.Exchange != i.Exchange || + k.Asset != i.Asset || + !k.Pair.Equal(i.Pair) { + return fmt.Errorf("%v %v %v %w %v %v %v", k.Exchange, k.Asset, k.Pair, ErrItemNotEqual, i.Exchange, i.Asset, i.Pair) + } + if !k.UnderlyingPair.IsEmpty() && !i.UnderlyingPair.IsEmpty() && !k.UnderlyingPair.Equal(i.UnderlyingPair) { + return fmt.Errorf("%w %v %v", ErrItemUnderlyingNotEqual, k.UnderlyingPair, i.UnderlyingPair) + } + return nil +} diff --git a/exchanges/kline/kline_test.go b/exchanges/kline/kline_test.go index 640a379e..a1adf8dc 100644 --- a/exchanges/kline/kline_test.go +++ b/exchanges/kline/kline_test.go @@ -879,6 +879,7 @@ func BenchmarkJustifyIntervalTimeStoringUnixValues2(b *testing.B) { } func TestConvertToNewInterval(t *testing.T) { + t.Parallel() _, err := ConvertToNewInterval(nil, OneMin) if !errors.Is(err, errNilKline) { t.Errorf("received '%v' expected '%v'", err, errNilKline) @@ -966,6 +967,7 @@ func TestConvertToNewInterval(t *testing.T) { } func TestGetClosePriceAtTime(t *testing.T) { + t.Parallel() tt := time.Now() k := Item{ Candles: []Candle{ @@ -991,3 +993,34 @@ func TestGetClosePriceAtTime(t *testing.T) { t.Errorf("received '%v' expected '%v'", err, ErrNotFoundAtTime) } } + +func TestRemoveDuplicateCandlesByTime(t *testing.T) { + t.Parallel() + tt := time.Now() + k := Item{ + Candles: []Candle{ + { + // out of order duplicate time + Time: tt.Add(time.Hour), + Close: 1337, + }, + { + Time: tt, + Close: 1337, + }, + { + Time: tt.Add(time.Hour), + Close: 1338, + }, + }, + } + k.RemoveDuplicateCandlesByTime() + if len(k.Candles) != 2 { + t.Errorf("received '%v' expected '%v'", len(k.Candles), 2) + } + k.Candles[0].Time = tt + k.RemoveDuplicateCandlesByTime() + if len(k.Candles) != 1 { + t.Errorf("received '%v' expected '%v'", len(k.Candles), 1) + } +} diff --git a/exchanges/kline/kline_types.go b/exchanges/kline/kline_types.go index 5ad9d073..be720fca 100644 --- a/exchanges/kline/kline_types.go +++ b/exchanges/kline/kline_types.go @@ -51,14 +51,18 @@ var ( ErrCanOnlyDownscaleCandles = errors.New("interval must be a longer duration to scale") // ErrWholeNumberScaling returns when old interval data cannot neatly fit into new interval size ErrWholeNumberScaling = errors.New("new interval must scale properly into new candle") - errNilKline = errors.New("kline item is nil") // ErrNotFoundAtTime returned when looking up a candle at a specific time ErrNotFoundAtTime = errors.New("candle not found at time") - + // ErrItemNotEqual returns when comparison between two kline items fail + ErrItemNotEqual = errors.New("kline item not equal") + // ErrItemUnderlyingNotEqual returns when the underlying pair is not equal + ErrItemUnderlyingNotEqual = errors.New("kline item underlying pair not equal") // ErrValidatingParams defines an error when the kline params are either not // enabled or are invalid. ErrValidatingParams = errors.New("kline param(s) are invalid") + errNilKline = errors.New("kline item is nil") + // SupportedIntervals is a list of all supported intervals SupportedIntervals = []Interval{ FifteenSecond, diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 18681536..d7fdee88 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -604,8 +604,8 @@ func (k *Kraken) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a continue } balances = append(balances, account.Balance{ - CurrencyName: currency.NewCode(translatedCurrency), - Total: bal[key], + Currency: currency.NewCode(translatedCurrency), + Total: bal[key], }) } info.Accounts = append(info.Accounts, account.SubAccount{ @@ -620,8 +620,8 @@ func (k *Kraken) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a for name := range bal.Accounts { for code := range bal.Accounts[name].Balances { balances = append(balances, account.Balance{ - CurrencyName: currency.NewCode(code).Upper(), - Total: bal.Accounts[name].Balances[code], + Currency: currency.NewCode(code).Upper(), + Total: bal.Accounts[name].Balances[code], }) } info.Accounts = append(info.Accounts, account.SubAccount{ diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index d21402b3..2ee87de6 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -338,10 +338,10 @@ func (l *Lbank) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac return info, parseErr } acc.Currencies = append(acc.Currencies, account.Balance{ - CurrencyName: c, - Total: totalVal, - Hold: totalHold, - Free: totalVal - totalHold, + Currency: c, + Total: totalVal, + Hold: totalHold, + Free: totalVal - totalHold, }) } @@ -980,7 +980,7 @@ func (l *Lbank) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pa if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", l.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index a90706a5..0945ac08 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -295,10 +295,10 @@ func (l *LocalBitcoins) UpdateAccountInfo(ctx context.Context, assetType asset.I AssetType: assetType, Currencies: []account.Balance{ { - CurrencyName: currency.BTC, - Total: accountBalance.Total.Balance, - Hold: accountBalance.Total.Balance - accountBalance.Total.Sendable, - Free: accountBalance.Total.Sendable, + Currency: currency.BTC, + Total: accountBalance.Total.Balance, + Hold: accountBalance.Total.Balance - accountBalance.Total.Sendable, + Free: accountBalance.Total.Sendable, }}, }) diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index 553b3cf1..93f899f3 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -562,10 +562,10 @@ func (o *OKCoin) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a } currencyAccount.Currencies = append(currencyAccount.Currencies, account.Balance{ - CurrencyName: currency.NewCode(currencies[i].Currency), - Total: totalValue, - Hold: hold, - Free: totalValue - hold, + Currency: currency.NewCode(currencies[i].Currency), + Total: totalValue, + Hold: hold, + Free: totalValue - hold, }) } @@ -1048,7 +1048,7 @@ func (o *OKCoin) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a return kline.Item{}, err } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil @@ -1098,7 +1098,7 @@ func (o *OKCoin) GetHistoricCandlesExtended(ctx context.Context, pair currency.P if len(summary) > 0 { log.Warnf(log.ExchangeSys, "%v - %v", o.Base.Name, summary) } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/okx/okx_wrapper.go b/exchanges/okx/okx_wrapper.go index 71271bf1..306732c4 100644 --- a/exchanges/okx/okx_wrapper.go +++ b/exchanges/okx/okx_wrapper.go @@ -527,10 +527,10 @@ func (ok *Okx) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc free := balances[i].AvailBal locked := balances[i].FrozenBalance currencyBalance[i] = account.Balance{ - CurrencyName: currency.NewCode(balances[i].Currency), - Total: balances[i].Balance, - Hold: locked, - Free: free, + Currency: currency.NewCode(balances[i].Currency), + Total: balances[i].Balance, + Hold: locked, + Free: free, } } acc.Currencies = currencyBalance @@ -1439,7 +1439,7 @@ func (ok *Okx) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pai }) } } - ret.RemoveDuplicates() + ret.RemoveDuplicateCandlesByTime() ret.RemoveOutsideRange(start, end) ret.SortCandlesByTimestamp(false) return ret, nil diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index f203def3..6d01fcf7 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -1945,3 +1945,16 @@ func TestGetOrdersRequest_Filter(t *testing.T) { } } } + +func TestIsValidOrderSubmissionSide(t *testing.T) { + t.Parallel() + if IsValidOrderSubmissionSide(UnknownSide) { + t.Error("expected false") + } + if !IsValidOrderSubmissionSide(Buy) { + t.Error("expected true") + } + if IsValidOrderSubmissionSide(CouldNotBuy) { + t.Error("expected false") + } +} diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index cc101427..d4ecb48e 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -32,7 +32,7 @@ var ( // Submit contains all properties of an order that may be required // for an order to be created on an exchange // Each exchange has their own requirements, so not all fields -// are required to be populated +// need to be populated type Submit struct { Exchange string Type Type @@ -62,6 +62,13 @@ type Submit struct { TriggerPrice float64 ClientID string // TODO: Shift to credentials ClientOrderID string + // RetrieveFees use if an API submit order response does not return fees + // enabling this will perform additional request(s) to retrieve them + // and set it in the SubmitResponse + RetrieveFees bool + // RetrieveFeeDelay some exchanges take time to properly save order data + // and cannot retrieve fees data immediately + RetrieveFeeDelay time.Duration } // SubmitResponse is what is returned after submitting an order to an exchange @@ -90,6 +97,7 @@ type SubmitResponse struct { OrderID string Trades []TradeHistory Fee float64 + FeeAsset currency.Code Cost float64 } diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 4f4eb3a7..b9b87232 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -19,9 +19,10 @@ const ( orderSubmissionValidSides = Buy | Sell | Bid | Ask | Long | Short shortSide = Short | Sell | Ask longSide = Long | Buy | Bid - inactiveStatuses = Filled | Cancelled | InsufficientBalance | MarketUnavailable | Rejected | PartiallyCancelled | Expired | Closed | AnyStatus | Cancelling | Liquidated - activeStatuses = Active | Open | PartiallyFilled | New | PendingCancel | Hidden | AutoDeleverage | Pending - notPlaced = InsufficientBalance | MarketUnavailable | Rejected + + inactiveStatuses = Filled | Cancelled | InsufficientBalance | MarketUnavailable | Rejected | PartiallyCancelled | Expired | Closed | AnyStatus | Cancelling | Liquidated + activeStatuses = Active | Open | PartiallyFilled | New | PendingCancel | Hidden | AutoDeleverage | Pending + notPlaced = InsufficientBalance | MarketUnavailable | Rejected ) var ( @@ -39,7 +40,12 @@ var ( errOrderDetailIsNil = errors.New("order detail is nil") ) -// Validate checks the supplied data and returns whether or not it's valid +// IsValidOrderSubmissionSide validates that the order side is a valid submission direction +func IsValidOrderSubmissionSide(s Side) bool { + return s != UnknownSide && orderSubmissionValidSides&s == s +} + +// Validate checks the supplied data and returns whether it's valid func (s *Submit) Validate(opt ...validate.Checker) error { if s == nil { return ErrSubmissionIsNil @@ -61,8 +67,8 @@ func (s *Submit) Validate(opt ...validate.Checker) error { return fmt.Errorf("'%s' %w", s.AssetType, asset.ErrNotSupported) } - if s.Side == UnknownSide || orderSubmissionValidSides&s.Side != s.Side { - return ErrSideIsInvalid + if !IsValidOrderSubmissionSide(s.Side) { + return fmt.Errorf("%w %v", ErrSideIsInvalid, s.Side) } if s.Type != Market && s.Type != Limit { diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 1c0608b6..c8ca82a4 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -419,8 +419,8 @@ func (p *Poloniex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) currencies := make([]account.Balance, 0, len(accountBalance.Currency)) for x, y := range accountBalance.Currency { currencies = append(currencies, account.Balance{ - CurrencyName: currency.NewCode(x), - Total: y, + Currency: currency.NewCode(x), + Total: y, }) } diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 2ecf2e15..01757266 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -311,9 +311,8 @@ func (y *Yobit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac currencies := make([]account.Balance, 0, len(accountBalance.FundsInclOrders)) for x, y := range accountBalance.FundsInclOrders { var exchangeCurrency account.Balance - exchangeCurrency.CurrencyName = currency.NewCode(x) + exchangeCurrency.Currency = currency.NewCode(x) exchangeCurrency.Total = y - exchangeCurrency.Hold = 0 for z, w := range accountBalance.Funds { if z == x { exchangeCurrency.Hold = y - w diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 29a59e69..63facd85 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -386,10 +386,10 @@ func (z *ZB) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (accou } balances[i] = account.Balance{ - CurrencyName: currency.NewCode(coins[i].EnName), - Total: hold + avail, - Hold: hold, - Free: avail, + Currency: currency.NewCode(coins[i].EnName), + Total: hold + avail, + Hold: hold, + Free: avail, } } diff --git a/gctrpc/rpc.pb.go b/gctrpc/rpc.pb.go index 09320e8e..24dd8508 100644 --- a/gctrpc/rpc.pb.go +++ b/gctrpc/rpc.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.0 +// protoc-gen-go v1.28.1 // protoc (unknown) // source: rpc.proto @@ -16123,9 +16123,9 @@ var file_rpc_proto_rawDesc = []byte{ 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3a, 0x01, - 0x2a, 0x12, 0x73, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x76, + 0x31, 0x2f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x73, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, @@ -16152,14 +16152,14 @@ var file_rpc_proto_rawDesc = []byte{ 0x65, 0x72, 0x69, 0x63, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3a, 0x01, 0x2a, 0x12, + 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, + 0x2f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x74, - 0x69, 0x63, 0x6b, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x5b, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x54, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, + 0x65, 0x74, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x5b, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x12, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x69, @@ -16170,8 +16170,8 @@ var file_rpc_proto_rawDesc = []byte{ 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, - 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x3a, 0x01, 0x2a, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, + 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x3a, 0x01, 0x2a, 0x22, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, + 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x12, 0x67, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, @@ -16225,17 +16225,17 @@ var file_rpc_proto_rawDesc = []byte{ 0x64, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x61, - 0x64, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x7f, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, + 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, + 0x31, 0x2f, 0x61, 0x64, 0x64, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x7f, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x22, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x77, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, + 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, 0x22, 0x1a, 0x2f, 0x76, 0x31, 0x2f, + 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x66, 0x6f, 0x6c, 0x69, 0x6f, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x77, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x46, 0x6f, 0x72, 0x65, 0x78, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, @@ -16254,53 +16254,53 @@ var file_rpc_proto_rawDesc = []byte{ 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x52, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, + 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x52, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, - 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x62, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6d, + 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, + 0x67, 0x65, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x62, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x6a, 0x0a, 0x0d, + 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, 0x76, 0x31, + 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x6a, 0x0a, 0x0d, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x16, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x5e, 0x0a, 0x09, 0x57, 0x68, 0x61, 0x6c, + 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x69, 0x6d, 0x75, 0x6c, + 0x61, 0x74, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x5e, 0x0a, 0x09, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x68, 0x61, 0x6c, 0x65, 0x42, 0x6f, 0x6d, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x69, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x68, 0x61, 0x6c, - 0x65, 0x62, 0x6f, 0x6d, 0x62, 0x3a, 0x01, 0x2a, 0x12, 0x5e, 0x0a, 0x0b, 0x43, 0x61, 0x6e, 0x63, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x77, + 0x68, 0x61, 0x6c, 0x65, 0x62, 0x6f, 0x6d, 0x62, 0x12, 0x5e, 0x0a, 0x0b, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x14, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x01, 0x2a, 0x12, 0x7a, 0x0a, 0x11, 0x43, 0x61, 0x6e, 0x63, + 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x7a, 0x0a, 0x11, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x61, 0x74, 0x63, 0x68, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x62, 0x61, 0x74, 0x63, 0x68, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x72, 0x0a, 0x0f, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, + 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, + 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x62, 0x61, 0x74, 0x63, 0x68, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x73, 0x12, 0x72, 0x0a, 0x0f, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x41, 0x6c, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, - 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x61, 0x6c, 0x6c, 0x6f, - 0x72, 0x64, 0x65, 0x72, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x45, + 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x61, + 0x6c, 0x6c, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x57, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, @@ -16310,14 +16310,14 @@ var file_rpc_proto_rawDesc = []byte{ 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x22, 0x0c, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, - 0x64, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x5e, 0x0a, 0x0b, 0x52, 0x65, 0x6d, + 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x76, 0x31, + 0x2f, 0x61, 0x64, 0x64, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1a, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x6d, 0x6f, 0x76, - 0x65, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0xb2, 0x01, 0x0a, 0x21, 0x47, 0x65, + 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x12, 0xb2, 0x01, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x30, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, @@ -16326,9 +16326,9 @@ var file_rpc_proto_rawDesc = []byte{ 0x74, 0x1a, 0x31, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x22, 0x1d, 0x2f, 0x76, - 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x64, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0xaa, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, 0x01, 0x2a, 0x22, + 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x64, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0xaa, 0x01, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, @@ -16337,9 +16337,9 @@ var file_rpc_proto_rawDesc = []byte{ 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1b, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, - 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x9e, 0x01, 0x0a, 0x1a, + 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x64, 0x65, 0x70, + 0x6f, 0x73, 0x69, 0x74, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x9e, 0x01, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, @@ -16347,33 +16347,33 @@ var file_rpc_proto_rawDesc = []byte{ 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x67, - 0x65, 0x74, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x66, 0x65, 0x72, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x6c, 0x0a, 0x11, + 0x65, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x3a, 0x01, 0x2a, 0x22, 0x1e, 0x2f, 0x76, + 0x31, 0x2f, 0x67, 0x65, 0x74, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x6c, 0x0a, 0x11, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, 0x74, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1b, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x46, 0x69, 0x61, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, - 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x66, 0x69, - 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x8b, 0x01, 0x0a, 0x1b, 0x57, + 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, + 0x77, 0x66, 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x8b, 0x01, 0x0a, 0x1b, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x46, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x1d, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x22, 0x28, 0x2f, 0x76, 0x31, - 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, - 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x77, 0x66, 0x69, 0x61, 0x74, - 0x66, 0x75, 0x6e, 0x64, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x82, 0x01, 0x0a, 0x13, 0x57, 0x69, 0x74, + 0x6e, 0x73, 0x65, 0x22, 0x33, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x3a, 0x01, 0x2a, 0x22, 0x28, + 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x69, 0x74, 0x68, 0x64, + 0x72, 0x61, 0x77, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x77, 0x66, + 0x69, 0x61, 0x74, 0x66, 0x75, 0x6e, 0x64, 0x73, 0x12, 0x82, 0x01, 0x0a, 0x13, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1c, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, - 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x69, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x9d, 0x01, + 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, + 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x69, 0x64, 0x12, 0x9d, 0x01, 0x0a, 0x1a, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x29, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, @@ -16381,9 +16381,9 @@ var file_rpc_proto_rawDesc = []byte{ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x22, 0x1d, 0x2f, 0x76, 0x31, - 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x62, 0x79, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x91, 0x01, + 0x6e, 0x73, 0x65, 0x22, 0x28, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x3a, 0x01, 0x2a, 0x22, 0x1d, + 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, + 0x65, 0x6e, 0x74, 0x62, 0x79, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x91, 0x01, 0x0a, 0x16, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x44, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, @@ -16391,9 +16391,9 @@ var file_rpc_proto_rawDesc = []byte{ 0x2a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x42, 0x79, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x1e, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, - 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x64, 0x61, 0x74, 0x65, 0x3a, 0x01, - 0x2a, 0x12, 0x73, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, + 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x69, 0x74, 0x68, + 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x62, 0x79, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x73, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, @@ -16406,23 +16406,23 @@ var file_rpc_proto_rawDesc = []byte{ 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x6c, 0x6f, - 0x67, 0x67, 0x65, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x76, + 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, + 0x74, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x72, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x76, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, - 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, - 0x69, 0x72, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x6a, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, + 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x70, 0x61, 0x69, 0x72, 0x73, 0x12, 0x6a, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x69, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x22, 0x13, 0x2f, 0x76, 0x31, 0x2f, - 0x73, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, 0x69, 0x72, 0x3a, - 0x01, 0x2a, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, + 0x73, 0x65, 0x22, 0x1e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x3a, 0x01, 0x2a, 0x22, 0x13, 0x2f, + 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x70, 0x61, + 0x69, 0x72, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x62, 0x6f, 0x6f, 0x6b, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, @@ -16471,17 +16471,17 @@ var file_rpc_proto_rawDesc = []byte{ 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x75, 0x70, - 0x6c, 0x6f, 0x61, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x78, 0x0a, 0x13, 0x47, 0x43, 0x54, 0x53, 0x63, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, + 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x2f, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x78, 0x0a, 0x13, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x22, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x61, 0x64, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, - 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x65, 0x61, 0x64, 0x3a, 0x01, - 0x2a, 0x12, 0x70, 0x0a, 0x0f, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, + 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, + 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x72, 0x65, 0x61, + 0x64, 0x12, 0x70, 0x0a, 0x0f, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, @@ -16500,30 +16500,30 @@ var file_rpc_proto_rawDesc = []byte{ 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x3a, 0x01, 0x2a, 0x12, 0x6e, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, + 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x12, 0x6e, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x6f, 0x70, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, - 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x73, 0x74, - 0x6f, 0x70, 0x61, 0x6c, 0x6c, 0x3a, 0x01, 0x2a, 0x12, 0x73, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, + 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x2f, 0x73, 0x74, 0x6f, 0x70, 0x61, 0x6c, 0x6c, 0x12, 0x73, 0x0a, 0x10, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0x77, 0x0a, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x3a, 0x01, 0x2a, 0x22, 0x12, 0x2f, 0x76, 0x31, 0x2f, 0x67, + 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x6c, 0x69, 0x73, 0x74, 0x12, 0x77, 0x0a, 0x17, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x12, 0x20, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x43, 0x54, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x41, 0x75, 0x74, 0x6f, 0x4c, 0x6f, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x22, 0x16, 0x2f, 0x76, 0x31, - 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x6c, - 0x6f, 0x61, 0x64, 0x3a, 0x01, 0x2a, 0x12, 0x7b, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, + 0x6e, 0x73, 0x65, 0x22, 0x21, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1b, 0x3a, 0x01, 0x2a, 0x22, 0x16, + 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x63, 0x74, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x61, 0x75, + 0x74, 0x6f, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x7b, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x43, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, @@ -16664,8 +16664,8 @@ var file_rpc_proto_rawDesc = []byte{ 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, - 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x64, 0x61, 0x74, 0x61, - 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x3a, 0x01, 0x2a, 0x12, 0x81, 0x01, + 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x73, 0x65, 0x72, 0x74, 0x64, + 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x12, 0x81, 0x01, 0x0a, 0x18, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x27, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, @@ -16704,25 +16704,25 @@ var file_rpc_proto_rawDesc = []byte{ 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x74, 0x64, 0x61, - 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x3a, 0x01, 0x2a, 0x12, 0x9d, 0x01, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, + 0x74, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x9d, 0x01, 0x0a, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x12, 0x2f, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x74, 0x61, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x4a, 0x6f, 0x62, 0x50, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x22, 0x24, 0x2f, 0x76, - 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x61, 0x74, 0x61, 0x68, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, 0x69, 0x73, 0x69, - 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x68, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x3a, 0x01, 0x2a, 0x22, + 0x24, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x61, 0x74, 0x61, 0x68, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x6a, 0x6f, 0x62, 0x70, 0x72, 0x65, 0x72, 0x65, 0x71, 0x75, + 0x69, 0x73, 0x69, 0x74, 0x65, 0x12, 0x68, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x18, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x65, 0x74, 0x6d, - 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x3a, 0x01, 0x2a, 0x12, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x67, + 0x65, 0x74, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x12, 0x5f, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x63, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x63, 0x74, diff --git a/gctrpc/rpc.pb.gw.go b/gctrpc/rpc.pb.gw.go index a3fe8b74..be42c416 100644 --- a/gctrpc/rpc.pb.gw.go +++ b/gctrpc/rpc.pb.gw.go @@ -3414,20 +3414,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetInfo", runtime.WithHTTPPathPattern("/v1/getinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetInfo", runtime.WithHTTPPathPattern("/v1/getinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetInfo_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3438,20 +3439,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSubsystems", runtime.WithHTTPPathPattern("/v1/getsubsystems")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSubsystems", runtime.WithHTTPPathPattern("/v1/getsubsystems")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetSubsystems_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetSubsystems_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetSubsystems_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetSubsystems_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3462,20 +3464,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableSubsystem", runtime.WithHTTPPathPattern("/v1/enablesubsystem")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableSubsystem", runtime.WithHTTPPathPattern("/v1/enablesubsystem")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_EnableSubsystem_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_EnableSubsystem_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_EnableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_EnableSubsystem_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3486,20 +3489,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableSubsystem", runtime.WithHTTPPathPattern("/v1/disablesubsystem")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableSubsystem", runtime.WithHTTPPathPattern("/v1/disablesubsystem")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_DisableSubsystem_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_DisableSubsystem_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_DisableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_DisableSubsystem_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3510,20 +3514,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRPCEndpoints", runtime.WithHTTPPathPattern("/v1/getrpcendpoints")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRPCEndpoints", runtime.WithHTTPPathPattern("/v1/getrpcendpoints")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetRPCEndpoints_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetRPCEndpoints_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetRPCEndpoints_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetRPCEndpoints_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3534,20 +3539,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCommunicationRelayers", runtime.WithHTTPPathPattern("/v1/getcommunicationrelayers")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCommunicationRelayers", runtime.WithHTTPPathPattern("/v1/getcommunicationrelayers")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetCommunicationRelayers_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetCommunicationRelayers_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCommunicationRelayers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCommunicationRelayers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3558,20 +3564,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchanges", runtime.WithHTTPPathPattern("/v1/getexchanges")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchanges", runtime.WithHTTPPathPattern("/v1/getexchanges")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetExchanges_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetExchanges_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchanges_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3582,20 +3589,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableExchange", runtime.WithHTTPPathPattern("/v1/disableexchange")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableExchange", runtime.WithHTTPPathPattern("/v1/disableexchange")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_DisableExchange_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_DisableExchange_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_DisableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_DisableExchange_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3606,20 +3614,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeInfo", runtime.WithHTTPPathPattern("/v1/getexchangeinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeInfo", runtime.WithHTTPPathPattern("/v1/getexchangeinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetExchangeInfo_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetExchangeInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3630,20 +3639,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCode", runtime.WithHTTPPathPattern("/v1/getexchangeotp")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCode", runtime.WithHTTPPathPattern("/v1/getexchangeotp")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetExchangeOTPCode_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetExchangeOTPCode_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeOTPCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeOTPCode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3654,20 +3664,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCodes", runtime.WithHTTPPathPattern("/v1/getexchangeotps")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCodes", runtime.WithHTTPPathPattern("/v1/getexchangeotps")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetExchangeOTPCodes_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetExchangeOTPCodes_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeOTPCodes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeOTPCodes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3678,20 +3689,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableExchange", runtime.WithHTTPPathPattern("/v1/enableexchange")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableExchange", runtime.WithHTTPPathPattern("/v1/enableexchange")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_EnableExchange_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_EnableExchange_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_EnableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_EnableExchange_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3702,20 +3714,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTicker", runtime.WithHTTPPathPattern("/v1/getticker")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTicker", runtime.WithHTTPPathPattern("/v1/getticker")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetTicker_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetTicker_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetTicker_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetTicker_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3726,20 +3739,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTickers", runtime.WithHTTPPathPattern("/v1/gettickers")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTickers", runtime.WithHTTPPathPattern("/v1/gettickers")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetTickers_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetTickers_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetTickers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetTickers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3750,20 +3764,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbook", runtime.WithHTTPPathPattern("/v1/getorderbook")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbook", runtime.WithHTTPPathPattern("/v1/getorderbook")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetOrderbook_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetOrderbook_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbook_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbook_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3774,20 +3789,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbooks", runtime.WithHTTPPathPattern("/v1/getorderbooks")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbooks", runtime.WithHTTPPathPattern("/v1/getorderbooks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetOrderbooks_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetOrderbooks_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbooks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbooks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3798,20 +3814,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAccountInfo", runtime.WithHTTPPathPattern("/v1/getaccountinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAccountInfo", runtime.WithHTTPPathPattern("/v1/getaccountinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetAccountInfo_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetAccountInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAccountInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3822,20 +3839,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateAccountInfo", runtime.WithHTTPPathPattern("/v1/updateaccountinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateAccountInfo", runtime.WithHTTPPathPattern("/v1/updateaccountinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_UpdateAccountInfo_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_UpdateAccountInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpdateAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpdateAccountInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3853,20 +3871,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetConfig", runtime.WithHTTPPathPattern("/v1/getconfig")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetConfig", runtime.WithHTTPPathPattern("/v1/getconfig")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetConfig_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetConfig_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3877,20 +3896,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolio", runtime.WithHTTPPathPattern("/v1/getportfolio")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolio", runtime.WithHTTPPathPattern("/v1/getportfolio")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetPortfolio_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetPortfolio_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetPortfolio_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetPortfolio_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3901,20 +3921,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolioSummary", runtime.WithHTTPPathPattern("/v1/getportfoliosummary")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolioSummary", runtime.WithHTTPPathPattern("/v1/getportfoliosummary")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetPortfolioSummary_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetPortfolioSummary_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetPortfolioSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetPortfolioSummary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3925,20 +3946,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddPortfolioAddress", runtime.WithHTTPPathPattern("/v1/addportfolioaddress")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddPortfolioAddress", runtime.WithHTTPPathPattern("/v1/addportfolioaddress")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_AddPortfolioAddress_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_AddPortfolioAddress_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_AddPortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_AddPortfolioAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3949,20 +3971,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemovePortfolioAddress", runtime.WithHTTPPathPattern("/v1/removeportfolioaddress")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemovePortfolioAddress", runtime.WithHTTPPathPattern("/v1/removeportfolioaddress")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_RemovePortfolioAddress_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_RemovePortfolioAddress_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_RemovePortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_RemovePortfolioAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3973,20 +3996,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexProviders", runtime.WithHTTPPathPattern("/v1/getforexproviders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexProviders", runtime.WithHTTPPathPattern("/v1/getforexproviders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetForexProviders_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetForexProviders_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetForexProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetForexProviders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -3997,20 +4021,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexRates", runtime.WithHTTPPathPattern("/v1/getforexrates")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexRates", runtime.WithHTTPPathPattern("/v1/getforexrates")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetForexRates_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetForexRates_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetForexRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetForexRates_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4021,20 +4046,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrders", runtime.WithHTTPPathPattern("/v1/getorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrders", runtime.WithHTTPPathPattern("/v1/getorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetOrders_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetOrders_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4045,20 +4071,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrder", runtime.WithHTTPPathPattern("/v1/getorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrder", runtime.WithHTTPPathPattern("/v1/getorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetOrder_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetOrder_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4069,20 +4096,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SubmitOrder", runtime.WithHTTPPathPattern("/v1/submitorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SubmitOrder", runtime.WithHTTPPathPattern("/v1/submitorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SubmitOrder_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SubmitOrder_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SubmitOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SubmitOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4093,20 +4121,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SimulateOrder", runtime.WithHTTPPathPattern("/v1/simulateorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SimulateOrder", runtime.WithHTTPPathPattern("/v1/simulateorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SimulateOrder_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SimulateOrder_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SimulateOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SimulateOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4117,20 +4146,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WhaleBomb", runtime.WithHTTPPathPattern("/v1/whalebomb")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WhaleBomb", runtime.WithHTTPPathPattern("/v1/whalebomb")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WhaleBomb_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WhaleBomb_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WhaleBomb_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WhaleBomb_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4141,20 +4171,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelOrder", runtime.WithHTTPPathPattern("/v1/cancelorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelOrder", runtime.WithHTTPPathPattern("/v1/cancelorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CancelOrder_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CancelOrder_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CancelOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CancelOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4165,20 +4196,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelBatchOrders", runtime.WithHTTPPathPattern("/v1/cancelbatchorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelBatchOrders", runtime.WithHTTPPathPattern("/v1/cancelbatchorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CancelBatchOrders_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CancelBatchOrders_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CancelBatchOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CancelBatchOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4189,20 +4221,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelAllOrders", runtime.WithHTTPPathPattern("/v1/cancelallorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelAllOrders", runtime.WithHTTPPathPattern("/v1/cancelallorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CancelAllOrders_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CancelAllOrders_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CancelAllOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CancelAllOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4213,20 +4246,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetEvents", runtime.WithHTTPPathPattern("/v1/getevents")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetEvents", runtime.WithHTTPPathPattern("/v1/getevents")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetEvents_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetEvents_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetEvents_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetEvents_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4237,20 +4271,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddEvent", runtime.WithHTTPPathPattern("/v1/addevent")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddEvent", runtime.WithHTTPPathPattern("/v1/addevent")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_AddEvent_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_AddEvent_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_AddEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_AddEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4261,20 +4296,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemoveEvent", runtime.WithHTTPPathPattern("/v1/removeevent")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemoveEvent", runtime.WithHTTPPathPattern("/v1/removeevent")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_RemoveEvent_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_RemoveEvent_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_RemoveEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_RemoveEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4285,20 +4321,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddresses", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddresses")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddresses", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddresses")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4309,20 +4346,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddress", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddress")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddress", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddress")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4333,20 +4371,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAvailableTransferChains", runtime.WithHTTPPathPattern("/v1/getavailabletransferchains")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAvailableTransferChains", runtime.WithHTTPPathPattern("/v1/getavailabletransferchains")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetAvailableTransferChains_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetAvailableTransferChains_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAvailableTransferChains_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAvailableTransferChains_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4357,20 +4396,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawFiatFunds", runtime.WithHTTPPathPattern("/v1/withdrawfiatfunds")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawFiatFunds", runtime.WithHTTPPathPattern("/v1/withdrawfiatfunds")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WithdrawFiatFunds_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WithdrawFiatFunds_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawFiatFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawFiatFunds_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4381,20 +4421,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawCryptocurrencyFunds", runtime.WithHTTPPathPattern("/v1/withdrawithdrawcryptofundswfiatfunds")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawCryptocurrencyFunds", runtime.WithHTTPPathPattern("/v1/withdrawithdrawcryptofundswfiatfunds")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4405,20 +4446,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventByID", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyid")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventByID", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyid")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WithdrawalEventByID_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WithdrawalEventByID_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawalEventByID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawalEventByID_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4429,20 +4471,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByExchange", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyexchange")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByExchange", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyexchange")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WithdrawalEventsByExchange_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WithdrawalEventsByExchange_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawalEventsByExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawalEventsByExchange_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4453,20 +4496,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByDate", runtime.WithHTTPPathPattern("/v1/withdrawaleventbydate")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByDate", runtime.WithHTTPPathPattern("/v1/withdrawaleventbydate")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WithdrawalEventsByDate_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WithdrawalEventsByDate_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawalEventsByDate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawalEventsByDate_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4477,20 +4521,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetLoggerDetails", runtime.WithHTTPPathPattern("/v1/getloggerdetails")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetLoggerDetails", runtime.WithHTTPPathPattern("/v1/getloggerdetails")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetLoggerDetails_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetLoggerDetails_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetLoggerDetails_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4501,20 +4546,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetLoggerDetails", runtime.WithHTTPPathPattern("/v1/setloggerdetails")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetLoggerDetails", runtime.WithHTTPPathPattern("/v1/setloggerdetails")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SetLoggerDetails_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SetLoggerDetails_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetLoggerDetails_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4525,20 +4571,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangePairs", runtime.WithHTTPPathPattern("/v1/getexchangepairs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangePairs", runtime.WithHTTPPathPattern("/v1/getexchangepairs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetExchangePairs_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetExchangePairs_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangePairs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4549,20 +4596,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangePair", runtime.WithHTTPPathPattern("/v1/setexchangepair")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangePair", runtime.WithHTTPPathPattern("/v1/setexchangepair")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SetExchangePair_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SetExchangePair_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetExchangePair_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4601,20 +4649,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAuditEvent", runtime.WithHTTPPathPattern("/v1/getauditevent")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAuditEvent", runtime.WithHTTPPathPattern("/v1/getauditevent")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetAuditEvent_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetAuditEvent_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAuditEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAuditEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4625,20 +4674,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptExecute", runtime.WithHTTPPathPattern("/v1/gctscript/execute")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptExecute", runtime.WithHTTPPathPattern("/v1/gctscript/execute")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptExecute_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptExecute_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptExecute_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptExecute_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4649,20 +4699,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptUpload", runtime.WithHTTPPathPattern("/v1/gctscript/upload")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptUpload", runtime.WithHTTPPathPattern("/v1/gctscript/upload")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptUpload_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptUpload_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptUpload_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptUpload_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4673,20 +4724,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptReadScript", runtime.WithHTTPPathPattern("/v1/gctscript/read")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptReadScript", runtime.WithHTTPPathPattern("/v1/gctscript/read")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptReadScript_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptReadScript_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptReadScript_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptReadScript_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4697,20 +4749,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStatus", runtime.WithHTTPPathPattern("/v1/gctscript/status")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStatus", runtime.WithHTTPPathPattern("/v1/gctscript/status")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptStatus_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4721,20 +4774,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptQuery", runtime.WithHTTPPathPattern("/v1/gctscript/query")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptQuery", runtime.WithHTTPPathPattern("/v1/gctscript/query")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptQuery_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptQuery_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptQuery_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptQuery_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4745,20 +4799,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStop", runtime.WithHTTPPathPattern("/v1/gctscript/stop")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStop", runtime.WithHTTPPathPattern("/v1/gctscript/stop")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptStop_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptStop_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptStop_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptStop_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4769,20 +4824,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStopAll", runtime.WithHTTPPathPattern("/v1/gctscript/stopall")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStopAll", runtime.WithHTTPPathPattern("/v1/gctscript/stopall")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptStopAll_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptStopAll_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptStopAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptStopAll_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4793,20 +4849,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptListAll", runtime.WithHTTPPathPattern("/v1/gctscript/list")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptListAll", runtime.WithHTTPPathPattern("/v1/gctscript/list")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptListAll_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptListAll_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptListAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptListAll_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4817,20 +4874,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptAutoLoadToggle", runtime.WithHTTPPathPattern("/v1/gctscript/autoload")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptAutoLoadToggle", runtime.WithHTTPPathPattern("/v1/gctscript/autoload")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4841,20 +4899,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetHistoricCandles", runtime.WithHTTPPathPattern("/v1/gethistoriccandles")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetHistoricCandles", runtime.WithHTTPPathPattern("/v1/gethistoriccandles")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetHistoricCandles_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetHistoricCandles_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetHistoricCandles_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetHistoricCandles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4865,20 +4924,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeAsset", runtime.WithHTTPPathPattern("/v1/setexchangeasset")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeAsset", runtime.WithHTTPPathPattern("/v1/setexchangeasset")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SetExchangeAsset_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SetExchangeAsset_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetExchangeAsset_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetExchangeAsset_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4889,20 +4949,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetAllExchangePairs", runtime.WithHTTPPathPattern("/v1/setallexchangepairs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetAllExchangePairs", runtime.WithHTTPPathPattern("/v1/setallexchangepairs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SetAllExchangePairs_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SetAllExchangePairs_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetAllExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetAllExchangePairs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4913,20 +4974,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateExchangeSupportedPairs", runtime.WithHTTPPathPattern("/v1/updateexchangesupportedpairs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateExchangeSupportedPairs", runtime.WithHTTPPathPattern("/v1/updateexchangesupportedpairs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4937,20 +4999,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeAssets", runtime.WithHTTPPathPattern("/v1/getexchangeassets")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeAssets", runtime.WithHTTPPathPattern("/v1/getexchangeassets")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetExchangeAssets_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetExchangeAssets_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeAssets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeAssets_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4961,20 +5024,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetInfo", runtime.WithHTTPPathPattern("/v1/websocketgetinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetInfo", runtime.WithHTTPPathPattern("/v1/websocketgetinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WebsocketGetInfo_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WebsocketGetInfo_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketGetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketGetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -4985,20 +5049,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetEnabled", runtime.WithHTTPPathPattern("/v1/websocketsetenabled")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetEnabled", runtime.WithHTTPPathPattern("/v1/websocketsetenabled")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WebsocketSetEnabled_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WebsocketSetEnabled_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketSetEnabled_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketSetEnabled_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5009,20 +5074,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetSubscriptions", runtime.WithHTTPPathPattern("/v1/websocketgetsubscriptions")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetSubscriptions", runtime.WithHTTPPathPattern("/v1/websocketgetsubscriptions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WebsocketGetSubscriptions_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WebsocketGetSubscriptions_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketGetSubscriptions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketGetSubscriptions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5033,20 +5099,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetProxy", runtime.WithHTTPPathPattern("/v1/websocketsetproxy")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetProxy", runtime.WithHTTPPathPattern("/v1/websocketsetproxy")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WebsocketSetProxy_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WebsocketSetProxy_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketSetProxy_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketSetProxy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5057,20 +5124,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetURL", runtime.WithHTTPPathPattern("/v1/websocketseturl")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetURL", runtime.WithHTTPPathPattern("/v1/websocketseturl")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_WebsocketSetURL_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_WebsocketSetURL_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketSetURL_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketSetURL_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5081,20 +5149,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRecentTrades", runtime.WithHTTPPathPattern("/v1/getrecenttrades")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRecentTrades", runtime.WithHTTPPathPattern("/v1/getrecenttrades")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetRecentTrades_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetRecentTrades_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetRecentTrades_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetRecentTrades_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5112,20 +5181,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSavedTrades", runtime.WithHTTPPathPattern("/v1/getsavedtrades")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSavedTrades", runtime.WithHTTPPathPattern("/v1/getsavedtrades")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetSavedTrades_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetSavedTrades_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetSavedTrades_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetSavedTrades_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5136,20 +5206,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ConvertTradesToCandles", runtime.WithHTTPPathPattern("/v1/converttradestocandles")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ConvertTradesToCandles", runtime.WithHTTPPathPattern("/v1/converttradestocandles")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_ConvertTradesToCandles_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_ConvertTradesToCandles_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_ConvertTradesToCandles_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_ConvertTradesToCandles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5160,20 +5231,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedCandleIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedcandleintervals")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedCandleIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedcandleintervals")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5184,20 +5256,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedTradeIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedtradeintervals")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedTradeIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedtradeintervals")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5208,20 +5281,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeTradeProcessing", runtime.WithHTTPPathPattern("/v1/setexchangetradeprocessing")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeTradeProcessing", runtime.WithHTTPPathPattern("/v1/setexchangetradeprocessing")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SetExchangeTradeProcessing_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SetExchangeTradeProcessing_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetExchangeTradeProcessing_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetExchangeTradeProcessing_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5232,20 +5306,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpsertDataHistoryJob", runtime.WithHTTPPathPattern("/v1/upsertdatahistoryjob")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpsertDataHistoryJob", runtime.WithHTTPPathPattern("/v1/upsertdatahistoryjob")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_UpsertDataHistoryJob_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_UpsertDataHistoryJob_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpsertDataHistoryJob_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpsertDataHistoryJob_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5256,20 +5331,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobDetails", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobdetails")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobDetails", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobdetails")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetDataHistoryJobDetails_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetDataHistoryJobDetails_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetDataHistoryJobDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetDataHistoryJobDetails_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5280,20 +5356,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetActiveDataHistoryJobs", runtime.WithHTTPPathPattern("/v1/getactivedatahistoryjobs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetActiveDataHistoryJobs", runtime.WithHTTPPathPattern("/v1/getactivedatahistoryjobs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetActiveDataHistoryJobs_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetActiveDataHistoryJobs_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetActiveDataHistoryJobs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetActiveDataHistoryJobs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5304,20 +5381,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobsBetween", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsbetween")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobsBetween", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsbetween")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetDataHistoryJobsBetween_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetDataHistoryJobsBetween_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetDataHistoryJobsBetween_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetDataHistoryJobsBetween_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5328,20 +5406,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobSummary", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsummary")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobSummary", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsummary")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetDataHistoryJobSummary_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetDataHistoryJobSummary_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetDataHistoryJobSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetDataHistoryJobSummary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5352,20 +5431,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetDataHistoryJobStatus", runtime.WithHTTPPathPattern("/v1/setdatahistoryjobstatus")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetDataHistoryJobStatus", runtime.WithHTTPPathPattern("/v1/setdatahistoryjobstatus")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_SetDataHistoryJobStatus_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_SetDataHistoryJobStatus_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetDataHistoryJobStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetDataHistoryJobStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5376,20 +5456,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateDataHistoryJobPrerequisite", runtime.WithHTTPPathPattern("/v1/updatedatahistoryjobprerequisite")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateDataHistoryJobPrerequisite", runtime.WithHTTPPathPattern("/v1/updatedatahistoryjobprerequisite")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5400,20 +5481,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedOrders", runtime.WithHTTPPathPattern("/v1/getmanagedorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedOrders", runtime.WithHTTPPathPattern("/v1/getmanagedorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetManagedOrders_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetManagedOrders_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetManagedOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetManagedOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5424,20 +5506,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ModifyOrder", runtime.WithHTTPPathPattern("/v1/modifyorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ModifyOrder", runtime.WithHTTPPathPattern("/v1/modifyorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_ModifyOrder_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_ModifyOrder_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_ModifyOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_ModifyOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5448,20 +5531,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateGetAll", runtime.WithHTTPPathPattern("/v1/currencystategetall")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateGetAll", runtime.WithHTTPPathPattern("/v1/currencystategetall")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CurrencyStateGetAll_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CurrencyStateGetAll_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateGetAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateGetAll_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5472,20 +5556,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTrading", runtime.WithHTTPPathPattern("/v1/currencystatetrading")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTrading", runtime.WithHTTPPathPattern("/v1/currencystatetrading")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CurrencyStateTrading_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CurrencyStateTrading_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateTrading_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateTrading_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5496,20 +5581,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateDeposit", runtime.WithHTTPPathPattern("/v1/currencystatedeposit")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateDeposit", runtime.WithHTTPPathPattern("/v1/currencystatedeposit")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CurrencyStateDeposit_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CurrencyStateDeposit_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateDeposit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateDeposit_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5520,20 +5606,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateWithdraw", runtime.WithHTTPPathPattern("/v1/currencystatewithdraw")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateWithdraw", runtime.WithHTTPPathPattern("/v1/currencystatewithdraw")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CurrencyStateWithdraw_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CurrencyStateWithdraw_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateWithdraw_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateWithdraw_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5544,20 +5631,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTradingPair", runtime.WithHTTPPathPattern("/v1/currencystatetradingpair")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTradingPair", runtime.WithHTTPPathPattern("/v1/currencystatetradingpair")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_CurrencyStateTradingPair_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_CurrencyStateTradingPair_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateTradingPair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateTradingPair_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5568,20 +5656,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFuturesPositions", runtime.WithHTTPPathPattern("/v1/getfuturespositions")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFuturesPositions", runtime.WithHTTPPathPattern("/v1/getfuturespositions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetFuturesPositions_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetFuturesPositions_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetFuturesPositions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetFuturesPositions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5592,20 +5681,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCollateral", runtime.WithHTTPPathPattern("/v1/getcollateral")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCollateral", runtime.WithHTTPPathPattern("/v1/getcollateral")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetCollateral_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetCollateral_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCollateral_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCollateral_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5616,20 +5706,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/Shutdown", runtime.WithHTTPPathPattern("/v1/shutdown")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/Shutdown", runtime.WithHTTPPathPattern("/v1/shutdown")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_Shutdown_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_Shutdown_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_Shutdown_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_Shutdown_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5640,20 +5731,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTechnicalAnalysis", runtime.WithHTTPPathPattern("/v1/gettechnicalanalysis")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTechnicalAnalysis", runtime.WithHTTPPathPattern("/v1/gettechnicalanalysis")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetTechnicalAnalysis_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetTechnicalAnalysis_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetTechnicalAnalysis_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetTechnicalAnalysis_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5664,20 +5756,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetMarginRatesHistory", runtime.WithHTTPPathPattern("/v1/getmarginrateshistory")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetMarginRatesHistory", runtime.WithHTTPPathPattern("/v1/getmarginrateshistory")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetMarginRatesHistory_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetMarginRatesHistory_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetMarginRatesHistory_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetMarginRatesHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5688,20 +5781,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedPosition", runtime.WithHTTPPathPattern("/v1/getmanagedposition")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedPosition", runtime.WithHTTPPathPattern("/v1/getmanagedposition")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetManagedPosition_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetManagedPosition_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetManagedPosition_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetManagedPosition_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5712,20 +5806,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAllManagedPositions", runtime.WithHTTPPathPattern("/v1/getallmanagedpositions")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAllManagedPositions", runtime.WithHTTPPathPattern("/v1/getallmanagedpositions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetAllManagedPositions_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetAllManagedPositions_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAllManagedPositions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAllManagedPositions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5736,20 +5831,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFundingRates", runtime.WithHTTPPathPattern("/v1/getfundingrates")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFundingRates", runtime.WithHTTPPathPattern("/v1/getfundingrates")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetFundingRates_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetFundingRates_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetFundingRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetFundingRates_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5760,20 +5856,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookMovement", runtime.WithHTTPPathPattern("/v1/getorderbookmovement")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookMovement", runtime.WithHTTPPathPattern("/v1/getorderbookmovement")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetOrderbookMovement_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetOrderbookMovement_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbookMovement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbookMovement_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5784,20 +5881,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal", runtime.WithHTTPPathPattern("/v1/getorderbookamountbynominal")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal", runtime.WithHTTPPathPattern("/v1/getorderbookamountbynominal")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbookAmountByNominal_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5808,20 +5906,21 @@ func RegisterGoCryptoTraderServiceHandlerServer(ctx context.Context, mux *runtim ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact", runtime.WithHTTPPathPattern("/v1/getorderbookamountbyimpact")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact", runtime.WithHTTPPathPattern("/v1/getorderbookamountbyimpact")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbookAmountByImpact_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5871,19 +5970,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetInfo", runtime.WithHTTPPathPattern("/v1/getinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetInfo", runtime.WithHTTPPathPattern("/v1/getinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetInfo_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5892,19 +5992,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSubsystems", runtime.WithHTTPPathPattern("/v1/getsubsystems")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSubsystems", runtime.WithHTTPPathPattern("/v1/getsubsystems")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetSubsystems_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetSubsystems_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetSubsystems_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetSubsystems_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5913,19 +6014,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableSubsystem", runtime.WithHTTPPathPattern("/v1/enablesubsystem")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableSubsystem", runtime.WithHTTPPathPattern("/v1/enablesubsystem")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_EnableSubsystem_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_EnableSubsystem_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_EnableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_EnableSubsystem_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5934,19 +6036,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableSubsystem", runtime.WithHTTPPathPattern("/v1/disablesubsystem")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableSubsystem", runtime.WithHTTPPathPattern("/v1/disablesubsystem")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_DisableSubsystem_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_DisableSubsystem_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_DisableSubsystem_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_DisableSubsystem_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5955,19 +6058,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRPCEndpoints", runtime.WithHTTPPathPattern("/v1/getrpcendpoints")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRPCEndpoints", runtime.WithHTTPPathPattern("/v1/getrpcendpoints")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetRPCEndpoints_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetRPCEndpoints_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetRPCEndpoints_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetRPCEndpoints_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5976,19 +6080,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCommunicationRelayers", runtime.WithHTTPPathPattern("/v1/getcommunicationrelayers")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCommunicationRelayers", runtime.WithHTTPPathPattern("/v1/getcommunicationrelayers")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetCommunicationRelayers_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetCommunicationRelayers_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCommunicationRelayers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCommunicationRelayers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -5997,19 +6102,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchanges", runtime.WithHTTPPathPattern("/v1/getexchanges")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchanges", runtime.WithHTTPPathPattern("/v1/getexchanges")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchanges_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchanges_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchanges_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6018,19 +6124,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableExchange", runtime.WithHTTPPathPattern("/v1/disableexchange")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/DisableExchange", runtime.WithHTTPPathPattern("/v1/disableexchange")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_DisableExchange_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_DisableExchange_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_DisableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_DisableExchange_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6039,19 +6146,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeInfo", runtime.WithHTTPPathPattern("/v1/getexchangeinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeInfo", runtime.WithHTTPPathPattern("/v1/getexchangeinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchangeInfo_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchangeInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6060,19 +6168,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCode", runtime.WithHTTPPathPattern("/v1/getexchangeotp")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCode", runtime.WithHTTPPathPattern("/v1/getexchangeotp")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchangeOTPCode_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchangeOTPCode_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeOTPCode_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeOTPCode_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6081,19 +6190,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCodes", runtime.WithHTTPPathPattern("/v1/getexchangeotps")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOTPCodes", runtime.WithHTTPPathPattern("/v1/getexchangeotps")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchangeOTPCodes_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchangeOTPCodes_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeOTPCodes_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeOTPCodes_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6102,19 +6212,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableExchange", runtime.WithHTTPPathPattern("/v1/enableexchange")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/EnableExchange", runtime.WithHTTPPathPattern("/v1/enableexchange")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_EnableExchange_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_EnableExchange_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_EnableExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_EnableExchange_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6123,19 +6234,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTicker", runtime.WithHTTPPathPattern("/v1/getticker")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTicker", runtime.WithHTTPPathPattern("/v1/getticker")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetTicker_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetTicker_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetTicker_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetTicker_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6144,19 +6256,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTickers", runtime.WithHTTPPathPattern("/v1/gettickers")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTickers", runtime.WithHTTPPathPattern("/v1/gettickers")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetTickers_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetTickers_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetTickers_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetTickers_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6165,19 +6278,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbook", runtime.WithHTTPPathPattern("/v1/getorderbook")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbook", runtime.WithHTTPPathPattern("/v1/getorderbook")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrderbook_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrderbook_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbook_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbook_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6186,19 +6300,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbooks", runtime.WithHTTPPathPattern("/v1/getorderbooks")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbooks", runtime.WithHTTPPathPattern("/v1/getorderbooks")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrderbooks_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrderbooks_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbooks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbooks_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6207,19 +6322,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAccountInfo", runtime.WithHTTPPathPattern("/v1/getaccountinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAccountInfo", runtime.WithHTTPPathPattern("/v1/getaccountinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetAccountInfo_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetAccountInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAccountInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6228,19 +6344,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateAccountInfo", runtime.WithHTTPPathPattern("/v1/updateaccountinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateAccountInfo", runtime.WithHTTPPathPattern("/v1/updateaccountinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_UpdateAccountInfo_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_UpdateAccountInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpdateAccountInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpdateAccountInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6249,19 +6366,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAccountInfoStream", runtime.WithHTTPPathPattern("/v1/getaccountinfostream")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAccountInfoStream", runtime.WithHTTPPathPattern("/v1/getaccountinfostream")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetAccountInfoStream_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetAccountInfoStream_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAccountInfoStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAccountInfoStream_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) }) @@ -6270,19 +6388,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetConfig", runtime.WithHTTPPathPattern("/v1/getconfig")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetConfig", runtime.WithHTTPPathPattern("/v1/getconfig")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetConfig_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetConfig_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetConfig_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetConfig_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6291,19 +6410,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolio", runtime.WithHTTPPathPattern("/v1/getportfolio")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolio", runtime.WithHTTPPathPattern("/v1/getportfolio")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetPortfolio_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetPortfolio_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetPortfolio_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetPortfolio_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6312,19 +6432,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolioSummary", runtime.WithHTTPPathPattern("/v1/getportfoliosummary")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetPortfolioSummary", runtime.WithHTTPPathPattern("/v1/getportfoliosummary")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetPortfolioSummary_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetPortfolioSummary_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetPortfolioSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetPortfolioSummary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6333,19 +6454,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddPortfolioAddress", runtime.WithHTTPPathPattern("/v1/addportfolioaddress")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddPortfolioAddress", runtime.WithHTTPPathPattern("/v1/addportfolioaddress")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_AddPortfolioAddress_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_AddPortfolioAddress_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_AddPortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_AddPortfolioAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6354,19 +6476,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemovePortfolioAddress", runtime.WithHTTPPathPattern("/v1/removeportfolioaddress")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemovePortfolioAddress", runtime.WithHTTPPathPattern("/v1/removeportfolioaddress")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_RemovePortfolioAddress_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_RemovePortfolioAddress_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_RemovePortfolioAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_RemovePortfolioAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6375,19 +6498,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexProviders", runtime.WithHTTPPathPattern("/v1/getforexproviders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexProviders", runtime.WithHTTPPathPattern("/v1/getforexproviders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetForexProviders_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetForexProviders_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetForexProviders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetForexProviders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6396,19 +6520,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexRates", runtime.WithHTTPPathPattern("/v1/getforexrates")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetForexRates", runtime.WithHTTPPathPattern("/v1/getforexrates")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetForexRates_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetForexRates_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetForexRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetForexRates_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6417,19 +6542,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrders", runtime.WithHTTPPathPattern("/v1/getorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrders", runtime.WithHTTPPathPattern("/v1/getorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrders_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrders_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6438,19 +6564,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrder", runtime.WithHTTPPathPattern("/v1/getorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrder", runtime.WithHTTPPathPattern("/v1/getorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrder_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrder_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6459,19 +6586,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SubmitOrder", runtime.WithHTTPPathPattern("/v1/submitorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SubmitOrder", runtime.WithHTTPPathPattern("/v1/submitorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SubmitOrder_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SubmitOrder_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SubmitOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SubmitOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6480,19 +6608,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SimulateOrder", runtime.WithHTTPPathPattern("/v1/simulateorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SimulateOrder", runtime.WithHTTPPathPattern("/v1/simulateorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SimulateOrder_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SimulateOrder_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SimulateOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SimulateOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6501,19 +6630,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WhaleBomb", runtime.WithHTTPPathPattern("/v1/whalebomb")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WhaleBomb", runtime.WithHTTPPathPattern("/v1/whalebomb")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WhaleBomb_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WhaleBomb_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WhaleBomb_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WhaleBomb_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6522,19 +6652,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelOrder", runtime.WithHTTPPathPattern("/v1/cancelorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelOrder", runtime.WithHTTPPathPattern("/v1/cancelorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CancelOrder_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CancelOrder_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CancelOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CancelOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6543,19 +6674,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelBatchOrders", runtime.WithHTTPPathPattern("/v1/cancelbatchorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelBatchOrders", runtime.WithHTTPPathPattern("/v1/cancelbatchorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CancelBatchOrders_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CancelBatchOrders_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CancelBatchOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CancelBatchOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6564,19 +6696,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelAllOrders", runtime.WithHTTPPathPattern("/v1/cancelallorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CancelAllOrders", runtime.WithHTTPPathPattern("/v1/cancelallorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CancelAllOrders_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CancelAllOrders_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CancelAllOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CancelAllOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6585,19 +6718,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetEvents", runtime.WithHTTPPathPattern("/v1/getevents")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetEvents", runtime.WithHTTPPathPattern("/v1/getevents")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetEvents_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetEvents_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetEvents_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetEvents_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6606,19 +6740,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddEvent", runtime.WithHTTPPathPattern("/v1/addevent")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/AddEvent", runtime.WithHTTPPathPattern("/v1/addevent")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_AddEvent_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_AddEvent_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_AddEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_AddEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6627,19 +6762,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemoveEvent", runtime.WithHTTPPathPattern("/v1/removeevent")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/RemoveEvent", runtime.WithHTTPPathPattern("/v1/removeevent")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_RemoveEvent_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_RemoveEvent_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_RemoveEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_RemoveEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6648,19 +6784,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddresses", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddresses")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddresses", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddresses")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCryptocurrencyDepositAddresses_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6669,19 +6806,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddress", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddress")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCryptocurrencyDepositAddress", runtime.WithHTTPPathPattern("/v1/getcryptodepositaddress")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCryptocurrencyDepositAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6690,19 +6828,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAvailableTransferChains", runtime.WithHTTPPathPattern("/v1/getavailabletransferchains")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAvailableTransferChains", runtime.WithHTTPPathPattern("/v1/getavailabletransferchains")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetAvailableTransferChains_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetAvailableTransferChains_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAvailableTransferChains_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAvailableTransferChains_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6711,19 +6850,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawFiatFunds", runtime.WithHTTPPathPattern("/v1/withdrawfiatfunds")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawFiatFunds", runtime.WithHTTPPathPattern("/v1/withdrawfiatfunds")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WithdrawFiatFunds_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WithdrawFiatFunds_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawFiatFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawFiatFunds_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6732,19 +6872,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawCryptocurrencyFunds", runtime.WithHTTPPathPattern("/v1/withdrawithdrawcryptofundswfiatfunds")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawCryptocurrencyFunds", runtime.WithHTTPPathPattern("/v1/withdrawithdrawcryptofundswfiatfunds")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawCryptocurrencyFunds_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6753,19 +6894,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventByID", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyid")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventByID", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyid")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WithdrawalEventByID_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WithdrawalEventByID_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawalEventByID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawalEventByID_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6774,19 +6916,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByExchange", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyexchange")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByExchange", runtime.WithHTTPPathPattern("/v1/withdrawaleventbyexchange")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WithdrawalEventsByExchange_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WithdrawalEventsByExchange_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawalEventsByExchange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawalEventsByExchange_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6795,19 +6938,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByDate", runtime.WithHTTPPathPattern("/v1/withdrawaleventbydate")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WithdrawalEventsByDate", runtime.WithHTTPPathPattern("/v1/withdrawaleventbydate")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WithdrawalEventsByDate_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WithdrawalEventsByDate_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WithdrawalEventsByDate_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WithdrawalEventsByDate_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6816,19 +6960,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetLoggerDetails", runtime.WithHTTPPathPattern("/v1/getloggerdetails")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetLoggerDetails", runtime.WithHTTPPathPattern("/v1/getloggerdetails")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetLoggerDetails_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetLoggerDetails_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetLoggerDetails_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6837,19 +6982,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetLoggerDetails", runtime.WithHTTPPathPattern("/v1/setloggerdetails")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetLoggerDetails", runtime.WithHTTPPathPattern("/v1/setloggerdetails")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SetLoggerDetails_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SetLoggerDetails_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetLoggerDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetLoggerDetails_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6858,19 +7004,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangePairs", runtime.WithHTTPPathPattern("/v1/getexchangepairs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangePairs", runtime.WithHTTPPathPattern("/v1/getexchangepairs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchangePairs_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchangePairs_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangePairs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6879,19 +7026,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangePair", runtime.WithHTTPPathPattern("/v1/setexchangepair")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangePair", runtime.WithHTTPPathPattern("/v1/setexchangepair")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SetExchangePair_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SetExchangePair_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetExchangePair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetExchangePair_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -6900,19 +7048,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookStream", runtime.WithHTTPPathPattern("/v1/getorderbookstream")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookStream", runtime.WithHTTPPathPattern("/v1/getorderbookstream")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrderbookStream_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrderbookStream_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbookStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbookStream_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) }) @@ -6921,19 +7070,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOrderbookStream", runtime.WithHTTPPathPattern("/v1/getexchangeorderbookstream")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeOrderbookStream", runtime.WithHTTPPathPattern("/v1/getexchangeorderbookstream")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchangeOrderbookStream_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchangeOrderbookStream_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeOrderbookStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeOrderbookStream_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) }) @@ -6942,19 +7092,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTickerStream", runtime.WithHTTPPathPattern("/v1/gettickerstream")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTickerStream", runtime.WithHTTPPathPattern("/v1/gettickerstream")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetTickerStream_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetTickerStream_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetTickerStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetTickerStream_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) }) @@ -6963,19 +7114,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeTickerStream", runtime.WithHTTPPathPattern("/v1/getexchangetickerstream")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeTickerStream", runtime.WithHTTPPathPattern("/v1/getexchangetickerstream")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchangeTickerStream_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchangeTickerStream_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeTickerStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeTickerStream_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) }) @@ -6984,19 +7136,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAuditEvent", runtime.WithHTTPPathPattern("/v1/getauditevent")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAuditEvent", runtime.WithHTTPPathPattern("/v1/getauditevent")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetAuditEvent_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetAuditEvent_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAuditEvent_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAuditEvent_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7005,19 +7158,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptExecute", runtime.WithHTTPPathPattern("/v1/gctscript/execute")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptExecute", runtime.WithHTTPPathPattern("/v1/gctscript/execute")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptExecute_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptExecute_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptExecute_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptExecute_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7026,19 +7180,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptUpload", runtime.WithHTTPPathPattern("/v1/gctscript/upload")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptUpload", runtime.WithHTTPPathPattern("/v1/gctscript/upload")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptUpload_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptUpload_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptUpload_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptUpload_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7047,19 +7202,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptReadScript", runtime.WithHTTPPathPattern("/v1/gctscript/read")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptReadScript", runtime.WithHTTPPathPattern("/v1/gctscript/read")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptReadScript_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptReadScript_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptReadScript_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptReadScript_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7068,19 +7224,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStatus", runtime.WithHTTPPathPattern("/v1/gctscript/status")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStatus", runtime.WithHTTPPathPattern("/v1/gctscript/status")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptStatus_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7089,19 +7246,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptQuery", runtime.WithHTTPPathPattern("/v1/gctscript/query")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptQuery", runtime.WithHTTPPathPattern("/v1/gctscript/query")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptQuery_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptQuery_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptQuery_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptQuery_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7110,19 +7268,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStop", runtime.WithHTTPPathPattern("/v1/gctscript/stop")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStop", runtime.WithHTTPPathPattern("/v1/gctscript/stop")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptStop_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptStop_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptStop_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptStop_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7131,19 +7290,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStopAll", runtime.WithHTTPPathPattern("/v1/gctscript/stopall")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptStopAll", runtime.WithHTTPPathPattern("/v1/gctscript/stopall")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptStopAll_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptStopAll_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptStopAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptStopAll_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7152,19 +7312,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptListAll", runtime.WithHTTPPathPattern("/v1/gctscript/list")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptListAll", runtime.WithHTTPPathPattern("/v1/gctscript/list")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptListAll_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptListAll_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptListAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptListAll_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7173,19 +7334,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptAutoLoadToggle", runtime.WithHTTPPathPattern("/v1/gctscript/autoload")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GCTScriptAutoLoadToggle", runtime.WithHTTPPathPattern("/v1/gctscript/autoload")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GCTScriptAutoLoadToggle_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7194,19 +7356,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetHistoricCandles", runtime.WithHTTPPathPattern("/v1/gethistoriccandles")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetHistoricCandles", runtime.WithHTTPPathPattern("/v1/gethistoriccandles")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetHistoricCandles_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetHistoricCandles_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetHistoricCandles_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetHistoricCandles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7215,19 +7378,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeAsset", runtime.WithHTTPPathPattern("/v1/setexchangeasset")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeAsset", runtime.WithHTTPPathPattern("/v1/setexchangeasset")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SetExchangeAsset_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SetExchangeAsset_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetExchangeAsset_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetExchangeAsset_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7236,19 +7400,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetAllExchangePairs", runtime.WithHTTPPathPattern("/v1/setallexchangepairs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetAllExchangePairs", runtime.WithHTTPPathPattern("/v1/setallexchangepairs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SetAllExchangePairs_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SetAllExchangePairs_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetAllExchangePairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetAllExchangePairs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7257,19 +7422,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateExchangeSupportedPairs", runtime.WithHTTPPathPattern("/v1/updateexchangesupportedpairs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateExchangeSupportedPairs", runtime.WithHTTPPathPattern("/v1/updateexchangesupportedpairs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpdateExchangeSupportedPairs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7278,19 +7444,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeAssets", runtime.WithHTTPPathPattern("/v1/getexchangeassets")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetExchangeAssets", runtime.WithHTTPPathPattern("/v1/getexchangeassets")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetExchangeAssets_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetExchangeAssets_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetExchangeAssets_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetExchangeAssets_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7299,19 +7466,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetInfo", runtime.WithHTTPPathPattern("/v1/websocketgetinfo")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetInfo", runtime.WithHTTPPathPattern("/v1/websocketgetinfo")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WebsocketGetInfo_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WebsocketGetInfo_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketGetInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketGetInfo_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7320,19 +7488,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetEnabled", runtime.WithHTTPPathPattern("/v1/websocketsetenabled")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetEnabled", runtime.WithHTTPPathPattern("/v1/websocketsetenabled")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WebsocketSetEnabled_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WebsocketSetEnabled_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketSetEnabled_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketSetEnabled_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7341,19 +7510,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetSubscriptions", runtime.WithHTTPPathPattern("/v1/websocketgetsubscriptions")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketGetSubscriptions", runtime.WithHTTPPathPattern("/v1/websocketgetsubscriptions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WebsocketGetSubscriptions_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WebsocketGetSubscriptions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketGetSubscriptions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketGetSubscriptions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7362,19 +7532,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetProxy", runtime.WithHTTPPathPattern("/v1/websocketsetproxy")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetProxy", runtime.WithHTTPPathPattern("/v1/websocketsetproxy")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WebsocketSetProxy_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WebsocketSetProxy_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketSetProxy_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketSetProxy_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7383,19 +7554,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetURL", runtime.WithHTTPPathPattern("/v1/websocketseturl")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/WebsocketSetURL", runtime.WithHTTPPathPattern("/v1/websocketseturl")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_WebsocketSetURL_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_WebsocketSetURL_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_WebsocketSetURL_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_WebsocketSetURL_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7404,19 +7576,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRecentTrades", runtime.WithHTTPPathPattern("/v1/getrecenttrades")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetRecentTrades", runtime.WithHTTPPathPattern("/v1/getrecenttrades")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetRecentTrades_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetRecentTrades_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetRecentTrades_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetRecentTrades_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7425,19 +7598,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetHistoricTrades", runtime.WithHTTPPathPattern("/v1/gethistorictrades")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetHistoricTrades", runtime.WithHTTPPathPattern("/v1/gethistorictrades")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetHistoricTrades_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetHistoricTrades_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetHistoricTrades_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetHistoricTrades_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) }) @@ -7446,19 +7620,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSavedTrades", runtime.WithHTTPPathPattern("/v1/getsavedtrades")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetSavedTrades", runtime.WithHTTPPathPattern("/v1/getsavedtrades")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetSavedTrades_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetSavedTrades_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetSavedTrades_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetSavedTrades_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7467,19 +7642,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ConvertTradesToCandles", runtime.WithHTTPPathPattern("/v1/converttradestocandles")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ConvertTradesToCandles", runtime.WithHTTPPathPattern("/v1/converttradestocandles")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_ConvertTradesToCandles_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_ConvertTradesToCandles_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_ConvertTradesToCandles_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_ConvertTradesToCandles_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7488,19 +7664,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedCandleIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedcandleintervals")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedCandleIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedcandleintervals")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_FindMissingSavedCandleIntervals_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7509,19 +7686,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedTradeIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedtradeintervals")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/FindMissingSavedTradeIntervals", runtime.WithHTTPPathPattern("/v1/findmissingsavedtradeintervals")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_FindMissingSavedTradeIntervals_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7530,19 +7708,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeTradeProcessing", runtime.WithHTTPPathPattern("/v1/setexchangetradeprocessing")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetExchangeTradeProcessing", runtime.WithHTTPPathPattern("/v1/setexchangetradeprocessing")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SetExchangeTradeProcessing_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SetExchangeTradeProcessing_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetExchangeTradeProcessing_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetExchangeTradeProcessing_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7551,19 +7730,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpsertDataHistoryJob", runtime.WithHTTPPathPattern("/v1/upsertdatahistoryjob")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpsertDataHistoryJob", runtime.WithHTTPPathPattern("/v1/upsertdatahistoryjob")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_UpsertDataHistoryJob_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_UpsertDataHistoryJob_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpsertDataHistoryJob_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpsertDataHistoryJob_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7572,19 +7752,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobDetails", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobdetails")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobDetails", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobdetails")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetDataHistoryJobDetails_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetDataHistoryJobDetails_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetDataHistoryJobDetails_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetDataHistoryJobDetails_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7593,19 +7774,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetActiveDataHistoryJobs", runtime.WithHTTPPathPattern("/v1/getactivedatahistoryjobs")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetActiveDataHistoryJobs", runtime.WithHTTPPathPattern("/v1/getactivedatahistoryjobs")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetActiveDataHistoryJobs_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetActiveDataHistoryJobs_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetActiveDataHistoryJobs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetActiveDataHistoryJobs_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7614,19 +7796,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobsBetween", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsbetween")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobsBetween", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsbetween")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetDataHistoryJobsBetween_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetDataHistoryJobsBetween_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetDataHistoryJobsBetween_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetDataHistoryJobsBetween_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7635,19 +7818,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobSummary", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsummary")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetDataHistoryJobSummary", runtime.WithHTTPPathPattern("/v1/getdatahistoryjobsummary")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetDataHistoryJobSummary_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetDataHistoryJobSummary_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetDataHistoryJobSummary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetDataHistoryJobSummary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7656,19 +7840,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetDataHistoryJobStatus", runtime.WithHTTPPathPattern("/v1/setdatahistoryjobstatus")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/SetDataHistoryJobStatus", runtime.WithHTTPPathPattern("/v1/setdatahistoryjobstatus")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_SetDataHistoryJobStatus_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_SetDataHistoryJobStatus_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_SetDataHistoryJobStatus_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_SetDataHistoryJobStatus_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7677,19 +7862,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateDataHistoryJobPrerequisite", runtime.WithHTTPPathPattern("/v1/updatedatahistoryjobprerequisite")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/UpdateDataHistoryJobPrerequisite", runtime.WithHTTPPathPattern("/v1/updatedatahistoryjobprerequisite")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_UpdateDataHistoryJobPrerequisite_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7698,19 +7884,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedOrders", runtime.WithHTTPPathPattern("/v1/getmanagedorders")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedOrders", runtime.WithHTTPPathPattern("/v1/getmanagedorders")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetManagedOrders_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetManagedOrders_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetManagedOrders_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetManagedOrders_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7719,19 +7906,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ModifyOrder", runtime.WithHTTPPathPattern("/v1/modifyorder")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/ModifyOrder", runtime.WithHTTPPathPattern("/v1/modifyorder")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_ModifyOrder_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_ModifyOrder_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_ModifyOrder_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_ModifyOrder_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7740,19 +7928,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateGetAll", runtime.WithHTTPPathPattern("/v1/currencystategetall")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateGetAll", runtime.WithHTTPPathPattern("/v1/currencystategetall")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CurrencyStateGetAll_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CurrencyStateGetAll_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateGetAll_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateGetAll_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7761,19 +7950,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTrading", runtime.WithHTTPPathPattern("/v1/currencystatetrading")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTrading", runtime.WithHTTPPathPattern("/v1/currencystatetrading")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CurrencyStateTrading_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CurrencyStateTrading_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateTrading_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateTrading_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7782,19 +7972,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateDeposit", runtime.WithHTTPPathPattern("/v1/currencystatedeposit")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateDeposit", runtime.WithHTTPPathPattern("/v1/currencystatedeposit")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CurrencyStateDeposit_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CurrencyStateDeposit_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateDeposit_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateDeposit_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7803,19 +7994,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateWithdraw", runtime.WithHTTPPathPattern("/v1/currencystatewithdraw")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateWithdraw", runtime.WithHTTPPathPattern("/v1/currencystatewithdraw")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CurrencyStateWithdraw_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CurrencyStateWithdraw_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateWithdraw_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateWithdraw_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7824,19 +8016,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTradingPair", runtime.WithHTTPPathPattern("/v1/currencystatetradingpair")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/CurrencyStateTradingPair", runtime.WithHTTPPathPattern("/v1/currencystatetradingpair")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_CurrencyStateTradingPair_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_CurrencyStateTradingPair_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_CurrencyStateTradingPair_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_CurrencyStateTradingPair_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7845,19 +8038,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFuturesPositions", runtime.WithHTTPPathPattern("/v1/getfuturespositions")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFuturesPositions", runtime.WithHTTPPathPattern("/v1/getfuturespositions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetFuturesPositions_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetFuturesPositions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetFuturesPositions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetFuturesPositions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7866,19 +8060,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCollateral", runtime.WithHTTPPathPattern("/v1/getcollateral")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetCollateral", runtime.WithHTTPPathPattern("/v1/getcollateral")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetCollateral_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetCollateral_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetCollateral_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetCollateral_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7887,19 +8082,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/Shutdown", runtime.WithHTTPPathPattern("/v1/shutdown")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/Shutdown", runtime.WithHTTPPathPattern("/v1/shutdown")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_Shutdown_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_Shutdown_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_Shutdown_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_Shutdown_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7908,19 +8104,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTechnicalAnalysis", runtime.WithHTTPPathPattern("/v1/gettechnicalanalysis")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetTechnicalAnalysis", runtime.WithHTTPPathPattern("/v1/gettechnicalanalysis")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetTechnicalAnalysis_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetTechnicalAnalysis_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetTechnicalAnalysis_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetTechnicalAnalysis_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7929,19 +8126,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetMarginRatesHistory", runtime.WithHTTPPathPattern("/v1/getmarginrateshistory")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetMarginRatesHistory", runtime.WithHTTPPathPattern("/v1/getmarginrateshistory")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetMarginRatesHistory_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetMarginRatesHistory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetMarginRatesHistory_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetMarginRatesHistory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7950,19 +8148,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedPosition", runtime.WithHTTPPathPattern("/v1/getmanagedposition")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetManagedPosition", runtime.WithHTTPPathPattern("/v1/getmanagedposition")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetManagedPosition_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetManagedPosition_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetManagedPosition_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetManagedPosition_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7971,19 +8170,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAllManagedPositions", runtime.WithHTTPPathPattern("/v1/getallmanagedpositions")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetAllManagedPositions", runtime.WithHTTPPathPattern("/v1/getallmanagedpositions")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetAllManagedPositions_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetAllManagedPositions_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetAllManagedPositions_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetAllManagedPositions_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -7992,19 +8192,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFundingRates", runtime.WithHTTPPathPattern("/v1/getfundingrates")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetFundingRates", runtime.WithHTTPPathPattern("/v1/getfundingrates")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetFundingRates_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetFundingRates_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetFundingRates_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetFundingRates_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -8013,19 +8214,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookMovement", runtime.WithHTTPPathPattern("/v1/getorderbookmovement")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookMovement", runtime.WithHTTPPathPattern("/v1/getorderbookmovement")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrderbookMovement_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrderbookMovement_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbookMovement_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbookMovement_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -8034,19 +8236,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal", runtime.WithHTTPPathPattern("/v1/getorderbookamountbynominal")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByNominal", runtime.WithHTTPPathPattern("/v1/getorderbookamountbynominal")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrderbookAmountByNominal_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbookAmountByNominal_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbookAmountByNominal_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -8055,19 +8258,20 @@ func RegisterGoCryptoTraderServiceHandlerClient(ctx context.Context, mux *runtim defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error - ctx, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact", runtime.WithHTTPPathPattern("/v1/getorderbookamountbyimpact")) + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/gctrpc.GoCryptoTraderService/GetOrderbookAmountByImpact", runtime.WithHTTPPathPattern("/v1/getorderbookamountbyimpact")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) + resp, md, err := request_GoCryptoTraderService_GetOrderbookAmountByImpact_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_GoCryptoTraderService_GetOrderbookAmountByImpact_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_GoCryptoTraderService_GetOrderbookAmountByImpact_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) diff --git a/gctscript/modules/gct/exchange.go b/gctscript/modules/gct/exchange.go index e9ecb3fb..d5ca7256 100644 --- a/gctscript/modules/gct/exchange.go +++ b/gctscript/modules/gct/exchange.go @@ -273,7 +273,7 @@ func ExchangeAccountInfo(args ...objects.Object) (objects.Object, error) { for x := range rtnValue.Accounts { for y := range rtnValue.Accounts[x].Currencies { temp := make(map[string]objects.Object, 3) - temp["name"] = &objects.String{Value: rtnValue.Accounts[x].Currencies[y].CurrencyName.String()} + temp["name"] = &objects.String{Value: rtnValue.Accounts[x].Currencies[y].Currency.String()} temp["total"] = &objects.Float{Value: rtnValue.Accounts[x].Currencies[y].Total} temp["hold"] = &objects.Float{Value: rtnValue.Accounts[x].Currencies[y].Hold} funds.Value = append(funds.Value, &objects.Map{Value: temp}) diff --git a/gctscript/wrappers/validator/validator.go b/gctscript/wrappers/validator/validator.go index 0d5b9cc4..8b9c46a9 100644 --- a/gctscript/wrappers/validator/validator.go +++ b/gctscript/wrappers/validator/validator.go @@ -201,7 +201,7 @@ func (w Wrapper) AccountInformation(ctx context.Context, exch string, assetType AssetType: assetType, Currencies: []account.Balance{ { - CurrencyName: currency.Code{ + Currency: currency.Code{ Item: ¤cy.Item{ ID: 0, FullName: "Bitcoin", diff --git a/main.go b/main.go index cc4ff1ef..8ade9ab4 100644 --- a/main.go +++ b/main.go @@ -148,12 +148,12 @@ func main() { log.Fatalf("Unable to start bot engine. Error: %s\n", err) } - go waitForInterupt(settings.Shutdown) + go waitForInterrupt(settings.Shutdown) <-settings.Shutdown engine.Bot.Stop() } -func waitForInterupt(waiter chan<- struct{}) { +func waitForInterrupt(waiter chan<- struct{}) { interrupt := signaler.WaitForInterrupt() gctlog.Infof(gctlog.Global, "Captured %v, shutdown requested.\n", interrupt) waiter <- struct{}{}