mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-18 23:16:49 +00:00
Backtester: Live trading upgrades (#1023)
* Modifications for a smoother live run * Fixes data appending * Successfully allows multi-currency live trading. Adds multiple currencies to live DCA strategy * Attempting to get cash and carry working * Poor attempts at sorting out data and appending it properly with USD in mind * =designs new live data handler * Updates cash and carry strat to work * adds test coverage. begins closeallpositions function * Updates cash and carry to work live * New kline.Event type. Cancels orders on close. Rn types * =Fixes USD funding issue * =fixes tests * fixes tests AGAIN * adds coverage to close all orders * crummy tests, should override * more tests * more tests * more coverage * removes scourge of currency.Pair maps. More tests * missed currency stuff * Fixes USD data issue & collateral issue. Needs to close ALL orders * Now triggers updates on the very first data entry * All my problems are solved now???? * fixes tests, extends coverage * there is some really funky candle stuff going on * my brain is melting * better shutdown management, fixes freezing bug * fixes data duplication issues, adds retries to requests * reduces logging, adds verbose options * expands coverage over all new functionality * fixes fun bug from curr == curr to curr.Equal(curr) * fixes setup issues and tests * starts adding external wallet amounts for funding * more setup for assets * setup live fund calcs and placing orders * successfully performs automated cash and carry * merge fixes * funding properly set at all times * fixes some bugs, need to address currencystatistics still * adds 'appeneded' trait, attempts to fix some stats * fixes stat bugs, adds cool new fetchfees feature * fixes terrible processing bugs * tightens realorder stats, sadly loses some live stats * this actually sets everything correctly for bothcd ..cd ..cd ..cd ..cd ..! * fix tests * coverage * beautiful new test coverage * docs * adds new fee getter delayer * commits from the correct directory * Lint * adds verbose to fund manager * Fix bug in t2b2 strat. Update dca live config. Docs * go mod tidy * update buf * buf + test improvement * Post merge fixes * fixes surprise offset bug * fix sizing restrictions for cash and carry * fix server lints * merge fixes * test fixesss * lintle fixles * slowloris * rn run to task, bug fixes, close all on close * rpc lint and fixes * bugfix: order manager not processing orders properly * somewhat addresses nits * absolutely broken end of day commit * absolutely massive knockon effects from nits * massive knockon effects continue * fixes things * address remaining nits * jk now fixes things * addresses the easier nits * more nit fixers * more niterinos addressederinos * refactors holdings and does some nits * so buf * addresses some nits, fixes holdings bugs * cleanup * attempts to fix alert chans to prevent many chans waiting? * terrible code, will revert * to be reviewed in detail tomorrow * Fixes up channel system * smashes those nits * fixes extra candles, fixes collateral bug, tests * fixes data races, introduces reflection * more checks n tests * Fixes cash and carry issues. Fixes more cool bugs * fixes ~typer~ typo * replace spot strats from ftx to binance * fixes all the tests I just destroyed * removes example path, rm verbose * 1) what 2) removes FTX references from the Backtester * renamed, non-working strategies * Removes FTX references almost as fast as sbf removes funds * regen docs, add contrib names,sort contrib names * fixes merge renamings * Addresses nits. Fixes setting API credentials. Fixes Binance limit retrieval * Fixes live order bugs with real orders and without * Apply suggestions from code review Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update backtester/engine/live.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update backtester/engine/live.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * Update backtester/config/strategyconfigbuilder/main.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * updates docs * even better docs Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,6 +15,7 @@ vendor/
|
||||
|
||||
# Binaries for programs and plugins
|
||||
gocryptotrader
|
||||
/backtester/backtester
|
||||
cmd/gctcli/gctcli
|
||||
backtester/backtester
|
||||
backtester/btcli/btcli
|
||||
|
||||
26
CONTRIBUTORS
26
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
|
||||
|
||||
2
LICENSE
2
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
|
||||
|
||||
34
README.md
34
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 |
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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: "<id>",
|
||||
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: "<id>",
|
||||
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: "<id>",
|
||||
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 {
|
||||
|
||||
@@ -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())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
)
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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{},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -67,5 +67,6 @@ func GenerateDefaultConfig() (*BacktesterConfig, error) {
|
||||
Warn: common.CMDColours.Warn,
|
||||
Error: common.CMDColours.Error,
|
||||
},
|
||||
StopAllTasksOnClose: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# GoCryptoTrader Backtester: Examples package
|
||||
# GoCryptoTrader Backtester: Strategyexamples package
|
||||
|
||||
<img src="/backtester/common/backtester.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/config/examples)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/config/strategyexamples)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](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
|
||||
|
||||
43
backtester/config/strategyexamples/ftx-cash-carry.strat → backtester/config/strategyexamples/binance-cash-and-carry.strat
Normal file → Executable file
43
backtester/config/strategyexamples/ftx-cash-carry.strat → backtester/config/strategyexamples/binance-cash-and-carry.strat
Normal file → Executable file
@@ -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"
|
||||
118
backtester/config/strategyexamples/binance-live-cash-and-carry.strat
Executable file
118
backtester/config/strategyexamples/binance-live-cash-and-carry.strat
Executable file
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
|
||||
338
backtester/engine/fakeinterfaces_test.go
Normal file
338
backtester/engine/fakeinterfaces_test.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
}
|
||||
}
|
||||
|
||||
107
backtester/engine/live_types.go
Normal file
107
backtester/engine/live_types.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
216
backtester/engine/taskmanager.go
Normal file
216
backtester/engine/taskmanager.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{},
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# GoCryptoTrader Backtester: Ftxcashandcarry package
|
||||
# GoCryptoTrader Backtester: Binancecashandcarry package
|
||||
|
||||
<img src="/backtester/common/backtester.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
|
||||
[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/ftxcashandcarry)
|
||||
[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/binancecashandcarry)
|
||||
[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
|
||||
[](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:
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user