mirror of
https://github.com/c9s/bbgo.git
synced 2024-09-20 00:01:09 +00:00
Compare commits
106 Commits
b167e57960
...
de5bdfeda9
Author | SHA1 | Date | |
---|---|---|---|
|
de5bdfeda9 | ||
|
37106c35b7 | ||
|
8265ada5a0 | ||
|
744ca57c71 | ||
|
17d3097e06 | ||
|
a0c41f89f2 | ||
|
35a6639530 | ||
|
26b1fd2ae7 | ||
|
80430fec46 | ||
|
1f8b2b3710 | ||
|
25a2203000 | ||
|
aca2c32442 | ||
|
df915d6ee8 | ||
|
0d6b7b29d5 | ||
|
2784ef4687 | ||
|
ea8f3a5485 | ||
|
52f32e0ad0 | ||
|
50cdf617f2 | ||
|
de0d11b511 | ||
|
789bb1e53e | ||
|
a9b71adce9 | ||
|
f83491af26 | ||
|
82d07a0098 | ||
|
619cce53f6 | ||
|
643ecde2e9 | ||
|
d7ddc9c462 | ||
|
bd19b63c7b | ||
|
83ed9b0811 | ||
|
34ef50d889 | ||
|
52925c5643 | ||
|
b4f2748892 | ||
|
ceda1e06b9 | ||
|
bc1715f8ad | ||
|
f361b19564 | ||
|
f44486447e | ||
|
a2eca66af5 | ||
|
129e2c438e | ||
|
90749f4873 | ||
|
77dfe213e5 | ||
|
960ea89d8c | ||
|
f24a96c8c3 | ||
|
6ad16b7488 | ||
|
e14f09a914 | ||
|
3cc96ff6ad | ||
|
6ea996bec4 | ||
|
ef935f8ca0 | ||
|
a282654c02 | ||
|
336dd7a108 | ||
|
f2a443a499 | ||
|
63a58e1b12 | ||
|
1b40118bba | ||
|
c75a685cc0 | ||
|
50262f2a84 | ||
|
9fc3a1b44a | ||
|
656112de45 | ||
|
ba73eeaad1 | ||
|
2527c0c7b7 | ||
|
a2f8fe5f72 | ||
|
ed51eff242 | ||
|
f6865f664c | ||
|
24de49860f | ||
|
83dc981c92 | ||
|
ec68e3c5f6 | ||
|
699164484b | ||
|
f27afac77b | ||
|
d404b20bd1 | ||
|
1b8d7bd805 | ||
|
7d034d1ba8 | ||
|
7135895006 | ||
|
ba913ce4de | ||
|
f12ba1adb9 | ||
|
294e529a98 | ||
|
f30aca1b5a | ||
|
f9b9832fff | ||
|
2bf1072977 | ||
|
01f8b78008 | ||
|
4d1c357c3d | ||
|
a4833524cf | ||
|
ed073264f1 | ||
|
ad6056834e | ||
|
8b1306a6a6 | ||
|
d85da78e17 | ||
|
9d581adc04 | ||
|
cff7103ece | ||
|
d501e8ff4d | ||
|
b87213827e | ||
|
ec80cbfd9f | ||
|
04bed165d0 | ||
|
7c4b3e81df | ||
|
cc820d3df0 | ||
|
371db8e7d1 | ||
|
b8abc065de | ||
|
9ebab4f4f7 | ||
|
d9fb9ff3e0 | ||
|
88d7783843 | ||
|
86e464b1bc | ||
|
5316bc5e3a | ||
|
3c861e3782 | ||
|
cb8ca56afc | ||
|
5b64195797 | ||
|
0d7990fc18 | ||
|
f7d54291f2 | ||
|
539e35d290 | ||
|
93c619c32a | ||
|
0c2f4aef9d | ||
|
f19bbbc5aa |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
|
@ -90,7 +90,7 @@ jobs:
|
|||
sed -i -e '/_requestgen.go/d' coverage_dnum.txt
|
||||
|
||||
- name: Revive Check
|
||||
uses: morphy2k/revive-action@v2.5.9 # https://github.com/mgechev/revive/issues/956
|
||||
uses: morphy2k/revive-action@v2.5.10 # https://github.com/mgechev/revive/issues/956
|
||||
with:
|
||||
reporter: github-pr-review
|
||||
fail_on_error: true
|
||||
|
|
|
@ -58,4 +58,4 @@ bbgo [flags]
|
|||
* [bbgo userdatastream](bbgo_userdatastream.md) - Listen to session events (orderUpdate, tradeUpdate, balanceUpdate, balanceSnapshot)
|
||||
* [bbgo version](bbgo_version.md) - show version name
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo account [--session SESSION] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -50,4 +50,4 @@ bbgo backtest [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo balances [--session SESSION] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -39,4 +39,4 @@ bbgo build [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -49,4 +49,4 @@ bbgo cancel-order [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo deposits [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -9,8 +9,10 @@ bbgo execute-order --session SESSION --symbol SYMBOL --side SIDE --target-quanti
|
|||
### Options
|
||||
|
||||
```
|
||||
--deadline duration deadline of the order execution
|
||||
--deadline duration deadline duration of the order execution, e.g. 1h
|
||||
--delay-interval duration order delay time after filled (default 3s)
|
||||
-h, --help help for execute-order
|
||||
--order-update-rate-limit string order update rate limit, syntax: 1+1/1m (default "1s")
|
||||
--price-ticks int the number of price tick for the jump spread, default to 0
|
||||
--session string the exchange session name for sync
|
||||
--side string the trading side: buy or sell
|
||||
|
@ -48,4 +50,4 @@ bbgo execute-order --session SESSION --symbol SYMBOL --side SIDE --target-quanti
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo get-order --session SESSION --order-id ORDER_ID [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -45,4 +45,4 @@ bbgo hoptimize [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo kline [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo list-orders open|closed --session SESSION --symbol SYMBOL [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -38,4 +38,4 @@ margin related history
|
|||
* [bbgo margin loans](bbgo_margin_loans.md) - query loans history
|
||||
* [bbgo margin repays](bbgo_margin_repays.md) - query repay history
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo margin interests --session=SESSION_NAME --asset=ASSET [flags]
|
|||
|
||||
* [bbgo margin](bbgo_margin.md) - margin related history
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo margin loans --session=SESSION_NAME --asset=ASSET [flags]
|
|||
|
||||
* [bbgo margin](bbgo_margin.md) - margin related history
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -41,4 +41,4 @@ bbgo margin repays --session=SESSION_NAME --asset=ASSET [flags]
|
|||
|
||||
* [bbgo margin](bbgo_margin.md) - margin related history
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo market [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -44,4 +44,4 @@ bbgo optimize [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo orderbook --session=[exchange_name] --symbol=[pair_name] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo orderupdate [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -49,4 +49,4 @@ bbgo pnl [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -51,4 +51,4 @@ bbgo run [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -46,4 +46,4 @@ bbgo submit-order --session SESSION --symbol SYMBOL --side SIDE --quantity QUANT
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo sync [--session=[exchange_name]] [--symbol=[pair_name]] [[--since=yyyy/mm/d
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo trades --session=[exchange_name] --symbol=[pair_name] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo tradeupdate --session=[exchange_name] [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -42,4 +42,4 @@ bbgo transfer-history [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -40,4 +40,4 @@ bbgo userdatastream [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -39,4 +39,4 @@ bbgo version [flags]
|
|||
|
||||
* [bbgo](bbgo.md) - bbgo is a crypto trading bot
|
||||
|
||||
###### Auto generated by spf13/cobra on 21-Aug-2024
|
||||
###### Auto generated by spf13/cobra on 16-Sep-2024
|
||||
|
|
|
@ -17,7 +17,7 @@ TELEGRAM_BOT_TOKEN=347374838:ABFTjfiweajfiawoejfiaojfeijoaef
|
|||
```
|
||||
|
||||
For the telegram chat authentication (your bot needs to verify it's you), if you only need a fixed authentication token,
|
||||
you can set `TELEGRAM_AUTH_TOKEN` in the `.env.local` file, e.g.,
|
||||
you can set `TELEGRAM_BOT_AUTH_TOKEN` in the `.env.local` file, e.g.,
|
||||
|
||||
```sh
|
||||
TELEGRAM_BOT_AUTH_TOKEN=itsme55667788
|
||||
|
|
35
doc/release/v1.60.1.md
Normal file
35
doc/release/v1.60.1.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
## Fixes
|
||||
|
||||
- fixed xmaker bugs
|
||||
- updated helm chart for sync cronjob
|
||||
- fixed max deposits api
|
||||
|
||||
[Full Changelog](https://github.com/c9s/bbgo/compare/v1.60.0...main)
|
||||
|
||||
- [#1727](https://github.com/c9s/bbgo/pull/1727): FIX: update timeInForce for binance margin order
|
||||
- [#1729](https://github.com/c9s/bbgo/pull/1729): FIX: [max] fix v3 deposit state conversion
|
||||
- [#1723](https://github.com/c9s/bbgo/pull/1723): FIX: [xmaker] avoid calculate margin from 0.0 signal
|
||||
- [#1721](https://github.com/c9s/bbgo/pull/1721): FIX: [xmaker] fix aggregatePrice method
|
||||
- [#1725](https://github.com/c9s/bbgo/pull/1725): IMPROVE: [xmaker] improve hedge margin account leverage calculation
|
||||
- [#1722](https://github.com/c9s/bbgo/pull/1722): FEATURE: [xmaker] add signals
|
||||
- [#1720](https://github.com/c9s/bbgo/pull/1720): FEATURE: [xmaker] margin credit improvement
|
||||
- [#1718](https://github.com/c9s/bbgo/pull/1718): FEATURE: [xmaker] add more config metrics
|
||||
- [#1719](https://github.com/c9s/bbgo/pull/1719): IMPROVE: [xmaker] fix bollinger band price calculation
|
||||
- [#1709](https://github.com/c9s/bbgo/pull/1709): IMPROVE: [xmaker] improve profit stats ticker and integrate rate limiter
|
||||
- [#1708](https://github.com/c9s/bbgo/pull/1708): FEATURE: [xmaker] integrate circuit breaker
|
||||
- [#1712](https://github.com/c9s/bbgo/pull/1712): FEATURE: [xmaker] add profit fixer
|
||||
- [#1710](https://github.com/c9s/bbgo/pull/1710): IMPROVE: [xmaker] improve stability
|
||||
- [#1717](https://github.com/c9s/bbgo/pull/1717): REFACTOR: [xmaker] refactor hedge worker and quote worker
|
||||
- [#1716](https://github.com/c9s/bbgo/pull/1716): FIX: [xmaker] profit object can be nil
|
||||
- [#1707](https://github.com/c9s/bbgo/pull/1707): FIX: [xmaker] position metrics missing label
|
||||
- [#1715](https://github.com/c9s/bbgo/pull/1715): UPGRADE: [go] upgrade packages that are too old
|
||||
- [#1713](https://github.com/c9s/bbgo/pull/1713): FEATURE: [chart] add env vars section
|
||||
- [#1711](https://github.com/c9s/bbgo/pull/1711): FEATURE: [binance] add new margin order side effect AUTO_BORROW_REPAY
|
||||
- [#1705](https://github.com/c9s/bbgo/pull/1705): FIX: [k8s] fix sync.enabled option
|
||||
- [#1704](https://github.com/c9s/bbgo/pull/1704): FEATURE: [k8s] add cronjob for sync
|
||||
- [#1700](https://github.com/c9s/bbgo/pull/1700): Fix: [autobuy] fix error when bollinger settings is not set
|
||||
- [#1703](https://github.com/c9s/bbgo/pull/1703): FEATURE: [core] add position metrics
|
||||
- [#1702](https://github.com/c9s/bbgo/pull/1702): IMPROVE: improve balance related metrics
|
||||
- [#1699](https://github.com/c9s/bbgo/pull/1699): REFACTOR: [twap] upgrade twap command and add optional order update rate limiter
|
||||
- [#1701](https://github.com/c9s/bbgo/pull/1701): RELEASE: v1.60.0
|
||||
- [#1714](https://github.com/c9s/bbgo/pull/1714): dep: bump actions/setup-node from 2 to 4
|
18
doc/release/v1.60.2.md
Normal file
18
doc/release/v1.60.2.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
[Full Changelog](https://github.com/c9s/bbgo/compare/v1.60.1...main)
|
||||
|
||||
- [#1739](https://github.com/c9s/bbgo/pull/1739): FEATURE: [dca2] set exchange fee rate for round position
|
||||
- [#1738](https://github.com/c9s/bbgo/pull/1738): FEATURE: [okx] update symbols to latest
|
||||
- [#1737](https://github.com/c9s/bbgo/pull/1737): FEATURE: [xmaker] implement tryArbitrage
|
||||
- [#1730](https://github.com/c9s/bbgo/pull/1730): FEATURE: [xmaker] add market trade signal
|
||||
- [#1734](https://github.com/c9s/bbgo/pull/1734): REFACTOR: [xmaker] refactor for supporting ioc arb [part1]
|
||||
- [#1736](https://github.com/c9s/bbgo/pull/1736): MINOR: [session] remove environment nil validation log
|
||||
- [#1742](https://github.com/c9s/bbgo/pull/1742): FIX: types/stream: change errorf to warnf
|
||||
- [#1741](https://github.com/c9s/bbgo/pull/1741): FIX: upgrade github.com/c9s/requestgen to 1.4.3
|
||||
- [#1740](https://github.com/c9s/bbgo/pull/1740): FIX: upgrade requestgen and re-generate max cancel order request files
|
||||
- [#1726](https://github.com/c9s/bbgo/pull/1726): dep: bump morphy2k/revive-action from 2.5.9 to 2.5.10
|
||||
- [#1735](https://github.com/c9s/bbgo/pull/1735): FIX: configure environment
|
||||
- [#1724](https://github.com/c9s/bbgo/pull/1724): FIX: fix slice init length
|
||||
- [#1733](https://github.com/c9s/bbgo/pull/1733): FIX: [bbgo] fix the defaults / initialize steps
|
||||
- [#1732](https://github.com/c9s/bbgo/pull/1732): FIX: fix env name
|
||||
- [#1728](https://github.com/c9s/bbgo/pull/1728): FIX: [core] fix memory leak
|
||||
- [#1731](https://github.com/c9s/bbgo/pull/1731): FIX: fix json tag
|
4
doc/release/v1.60.3.md
Normal file
4
doc/release/v1.60.3.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
[Full Changelog](https://github.com/c9s/bbgo/compare/v1.60.2...main)
|
||||
|
||||
- FIX: fix xmaker default price
|
||||
- [#1744](https://github.com/c9s/bbgo/pull/1744): call b.EmitNew() when new order is added into activeorderbook
|
8
go.mod
8
go.mod
|
@ -11,7 +11,7 @@ require (
|
|||
github.com/Masterminds/squirrel v1.5.3
|
||||
github.com/adshao/go-binance/v2 v2.6.0
|
||||
github.com/c-bata/goptuna v0.8.1
|
||||
github.com/c9s/requestgen v1.3.6
|
||||
github.com/c9s/requestgen v1.4.3
|
||||
github.com/c9s/rockhopper/v2 v2.0.4
|
||||
github.com/cenkalti/backoff/v4 v4.2.0
|
||||
github.com/cheggaaa/pb/v3 v3.0.8
|
||||
|
@ -119,7 +119,7 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.23 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
|
@ -153,12 +153,12 @@ require (
|
|||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
|
||||
golang.org/x/image v0.5.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
|
16
go.sum
16
go.sum
|
@ -86,8 +86,8 @@ github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP
|
|||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/c-bata/goptuna v0.8.1 h1:25+n1MLv0yvCsD56xv4nqIus3oLHL9GuPAZDLIqmX1U=
|
||||
github.com/c-bata/goptuna v0.8.1/go.mod h1:knmS8+Iyq5PPy1YUeIEq0pMFR4Y6x7z/CySc9HlZTCY=
|
||||
github.com/c9s/requestgen v1.3.6 h1:ul7dZ2uwGYjNBjreooRfSY10WTXvQmQSjZsHebz6QfE=
|
||||
github.com/c9s/requestgen v1.3.6/go.mod h1:QwkZudcv84kJ8g9+E0RDTj+13btFXbTvv2aI+zbuLbc=
|
||||
github.com/c9s/requestgen v1.4.3 h1:0QZ27RVBLb9QuBKfiSBTOB5zSUuasrJm2p6/GZZHZZw=
|
||||
github.com/c9s/requestgen v1.4.3/go.mod h1:3gk1M2ihvNU2wWl7WLUc09myp7XpHMP33Dx96+Vr8A0=
|
||||
github.com/c9s/rockhopper/v2 v2.0.4 h1:1cQEzU7rzCSz09B2RYdyPWwBW9gZ/DoFqD1b2xLLmAk=
|
||||
github.com/c9s/rockhopper/v2 v2.0.4/go.mod h1:x0XuYI2Su3kS/74UYu/3Cqc9m5Dtzqh7j7JZarczfss=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||
|
@ -472,8 +472,8 @@ github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lL
|
|||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
||||
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
|
@ -759,8 +759,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
|
|||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -951,8 +951,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
|||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
10
migrations/mysql/20240918132534_add_position_index.sql
Normal file
10
migrations/mysql/20240918132534_add_position_index.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
-- +up
|
||||
-- +begin
|
||||
CREATE INDEX positions_traded_at ON positions (traded_at, profit);
|
||||
-- +end
|
||||
|
||||
-- +down
|
||||
|
||||
-- +begin
|
||||
DROP INDEX positions_traded_at ON positions;
|
||||
-- +end
|
10
migrations/sqlite3/20240918132534_add_position_index.sql
Normal file
10
migrations/sqlite3/20240918132534_add_position_index.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
-- +up
|
||||
-- +begin
|
||||
CREATE INDEX positions_traded_at ON positions (traded_at, profit);
|
||||
-- +end
|
||||
|
||||
-- +down
|
||||
|
||||
-- +begin
|
||||
DROP INDEX positions_traded_at;
|
||||
-- +end
|
|
@ -79,7 +79,35 @@ type SessionSymbolReport struct {
|
|||
InitialBalances types.BalanceMap `json:"initialBalances,omitempty"`
|
||||
FinalBalances types.BalanceMap `json:"finalBalances,omitempty"`
|
||||
Manifests Manifests `json:"manifests,omitempty"`
|
||||
TradeCount fixedpoint.Value `json:"tradeCount,omitempty"`
|
||||
RoundTurnCount fixedpoint.Value `json:"roundTurnCount,omitempty"`
|
||||
TotalNetProfit fixedpoint.Value `json:"totalNetProfit,omitempty"`
|
||||
AvgNetProfit fixedpoint.Value `json:"avgNetProfit,omitempty"`
|
||||
GrossProfit fixedpoint.Value `json:"grossProfit,omitempty"`
|
||||
GrossLoss fixedpoint.Value `json:"grossLoss,omitempty"`
|
||||
PRR fixedpoint.Value `json:"prr,omitempty"`
|
||||
PercentProfitable fixedpoint.Value `json:"percentProfitable,omitempty"`
|
||||
MaxDrawdown fixedpoint.Value `json:"maxDrawdown,omitempty"`
|
||||
AverageDrawdown fixedpoint.Value `json:"avgDrawdown,omitempty"`
|
||||
MaxProfit fixedpoint.Value `json:"maxProfit,omitempty"`
|
||||
MaxLoss fixedpoint.Value `json:"maxLoss,omitempty"`
|
||||
AvgProfit fixedpoint.Value `json:"avgProfit,omitempty"`
|
||||
AvgLoss fixedpoint.Value `json:"avgLoss,omitempty"`
|
||||
TotalTimeInMarketSec int64 `json:"totalTimeInMarketSec,omitempty"`
|
||||
AvgHoldSec int64 `json:"avgHoldSec,omitempty"`
|
||||
WinningCount int `json:"winningCount,omitempty"`
|
||||
LosingCount int `json:"losingCount,omitempty"`
|
||||
MaxLossStreak int `json:"maxLossStreak,omitempty"`
|
||||
Sharpe fixedpoint.Value `json:"sharpeRatio"`
|
||||
AnnualHistoricVolatility fixedpoint.Value `json:"annualHistoricVolatility,omitempty"`
|
||||
CAGR fixedpoint.Value `json:"cagr,omitempty"`
|
||||
Calmar fixedpoint.Value `json:"calmar,omitempty"`
|
||||
Sterling fixedpoint.Value `json:"sterling,omitempty"`
|
||||
Burke fixedpoint.Value `json:"burke,omitempty"`
|
||||
Kelly fixedpoint.Value `json:"kelly,omitempty"`
|
||||
OptimalF fixedpoint.Value `json:"optimalF,omitempty"`
|
||||
StatN fixedpoint.Value `json:"statN,omitempty"`
|
||||
StdErr fixedpoint.Value `json:"statNStdErr,omitempty"`
|
||||
Sortino fixedpoint.Value `json:"sortinoRatio"`
|
||||
ProfitFactor fixedpoint.Value `json:"profitFactor"`
|
||||
WinningRatio fixedpoint.Value `json:"winningRatio"`
|
||||
|
|
|
@ -359,6 +359,7 @@ func (b *ActiveOrderBook) Add(orders ...types.Order) {
|
|||
}
|
||||
|
||||
b.add(order)
|
||||
b.EmitNew(order)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -466,6 +467,12 @@ func (b *ActiveOrderBook) Lookup(f func(o types.Order) bool) *types.Order {
|
|||
|
||||
func (b *ActiveOrderBook) filterExistingOrders(orders []types.Order) (existingOrders types.OrderSlice) {
|
||||
for _, o := range orders {
|
||||
// skip market order
|
||||
// this prevents if someone added a market order to the active order book
|
||||
if o.Type == types.OrderTypeMarket {
|
||||
continue
|
||||
}
|
||||
|
||||
if b.Exists(o) {
|
||||
existingOrders.Add(o)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import "github.com/c9s/bbgo/pkg/types"
|
|||
const MaxNumOfKLines = 5_000
|
||||
const MaxNumOfKLinesTruncate = 100
|
||||
|
||||
const CapacityOfKLineWindowLimit = 5_000
|
||||
|
||||
// MarketDataStore receives and maintain the public market data of a single symbol
|
||||
//go:generate callbackgen -type MarketDataStore
|
||||
type MarketDataStore struct {
|
||||
|
@ -57,10 +59,20 @@ func (store *MarketDataStore) AddKLine(k types.KLine) {
|
|||
}
|
||||
window.Add(k)
|
||||
|
||||
if len(*window) > MaxNumOfKLines {
|
||||
*window = (*window)[MaxNumOfKLinesTruncate-1:]
|
||||
}
|
||||
truncateKLineWindowIfNeeded(window)
|
||||
|
||||
store.EmitKLineClosed(k)
|
||||
store.EmitKLineWindowUpdate(k.Interval, *window)
|
||||
}
|
||||
|
||||
func truncateKLineWindowIfNeeded(window *types.KLineWindow) {
|
||||
lenOfWindow := len(*window)
|
||||
capOfWindow := cap(*window)
|
||||
|
||||
if lenOfWindow == capOfWindow && capOfWindow > CapacityOfKLineWindowLimit {
|
||||
size := CapacityOfKLineWindowLimit / 2
|
||||
start := lenOfWindow - size
|
||||
copy(*window, (*window)[start:])
|
||||
*window = (*window)[:size]
|
||||
}
|
||||
}
|
||||
|
|
45
pkg/bbgo/marketdatastore_test.go
Normal file
45
pkg/bbgo/marketdatastore_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package bbgo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMarketDataStore_AddKLineAndTruncateWindow(t *testing.T) {
|
||||
store := NewMarketDataStore("BTCUSD")
|
||||
|
||||
interval := types.Interval1s
|
||||
|
||||
var maxCap int = 0
|
||||
capFixed := false
|
||||
|
||||
var gid uint64 = 0
|
||||
// insert 1.5 * CapacityOfKLineWindowLimit KLine into window
|
||||
for ; gid < CapacityOfKLineWindowLimit+(CapacityOfKLineWindowLimit/2); gid++ {
|
||||
store.AddKLine(types.KLine{
|
||||
Interval: interval,
|
||||
GID: gid,
|
||||
})
|
||||
|
||||
// if the capacity is > CapacityOfKLineWindowLimit, the capacity should be fixed. We use this if expression to verify it then.
|
||||
if !capFixed && cap(*store.KLineWindows[interval]) > CapacityOfKLineWindowLimit {
|
||||
maxCap = cap(*store.KLineWindows[interval])
|
||||
capFixed = true
|
||||
}
|
||||
}
|
||||
|
||||
window := store.KLineWindows[interval]
|
||||
|
||||
// make sure the capacity is fixed
|
||||
assert.Equal(t, maxCap, cap(*window))
|
||||
|
||||
// after truncate, it will remain (CapacityOfKLineWindowLimit / 2) KLine in the window
|
||||
// so the first GIC will be the maxCap - (CapacityOfKLineWindowLimit / 2)
|
||||
truncatedGID := uint64(maxCap - (CapacityOfKLineWindowLimit / 2))
|
||||
for _, kline := range *window {
|
||||
assert.Equal(t, truncatedGID, kline.GID)
|
||||
truncatedGID++
|
||||
}
|
||||
}
|
|
@ -12,12 +12,16 @@ type Quota struct {
|
|||
Locked fixedpoint.Value
|
||||
}
|
||||
|
||||
// Add adds the fund to the available quota
|
||||
func (q *Quota) Add(fund fixedpoint.Value) {
|
||||
q.mu.Lock()
|
||||
q.Available = q.Available.Add(fund)
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// Lock locks the fund from the available quota
|
||||
// returns true if the fund is locked successfully
|
||||
// returns false if the fund is not enough
|
||||
func (q *Quota) Lock(fund fixedpoint.Value) bool {
|
||||
if fund.Compare(q.Available) > 0 {
|
||||
return false
|
||||
|
@ -31,12 +35,15 @@ func (q *Quota) Lock(fund fixedpoint.Value) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Commit commits the locked fund
|
||||
func (q *Quota) Commit() {
|
||||
q.mu.Lock()
|
||||
q.Locked = fixedpoint.Zero
|
||||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
// Rollback rolls back the locked fund
|
||||
// this will move the locked fund to the available quota
|
||||
func (q *Quota) Rollback() {
|
||||
q.mu.Lock()
|
||||
q.Available = q.Available.Add(q.Locked)
|
||||
|
@ -44,12 +51,21 @@ func (q *Quota) Rollback() {
|
|||
q.mu.Unlock()
|
||||
}
|
||||
|
||||
func (q *Quota) String() string {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
return q.Locked.String() + "/" + q.Available.String()
|
||||
}
|
||||
|
||||
// QuotaTransaction is a transactional quota manager
|
||||
type QuotaTransaction struct {
|
||||
mu sync.Mutex
|
||||
BaseAsset Quota
|
||||
QuoteAsset Quota
|
||||
}
|
||||
|
||||
// Commit commits the transaction
|
||||
func (m *QuotaTransaction) Commit() bool {
|
||||
m.mu.Lock()
|
||||
m.BaseAsset.Commit()
|
||||
|
@ -58,6 +74,7 @@ func (m *QuotaTransaction) Commit() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Rollback rolls back the transaction
|
||||
func (m *QuotaTransaction) Rollback() bool {
|
||||
m.mu.Lock()
|
||||
m.BaseAsset.Rollback()
|
||||
|
|
|
@ -405,6 +405,8 @@ func (session *ExchangeSession) initSymbol(ctx context.Context, environ *Environ
|
|||
return fmt.Errorf("market %s is not defined", symbol)
|
||||
}
|
||||
|
||||
session.logger.Infof("environment config: %+v", environ.environmentConfig)
|
||||
|
||||
disableMarketDataStore := environ.environmentConfig != nil && environ.environmentConfig.DisableMarketDataStore
|
||||
disableSessionTradeBuffer := environ.environmentConfig != nil && environ.environmentConfig.DisableSessionTradeBuffer
|
||||
maxSessionTradeBufferSize := 0
|
||||
|
|
|
@ -16,8 +16,18 @@ import (
|
|||
)
|
||||
|
||||
// Strategy method calls:
|
||||
// -> Initialize() (optional method)
|
||||
// -> Defaults() (optional method)
|
||||
//
|
||||
// setup default static values from constants
|
||||
//
|
||||
// -> Initialize() (optional method)
|
||||
//
|
||||
// initialize dynamic runtime objects
|
||||
//
|
||||
// -> Subscribe()
|
||||
//
|
||||
// register the subscriptions
|
||||
//
|
||||
// -> Validate() (optional method)
|
||||
// -> Run() (optional method)
|
||||
// -> Shutdown(shutdownCtx context.Context, wg *sync.WaitGroup)
|
||||
|
@ -112,6 +122,12 @@ func (trader *Trader) DisableLogging() {
|
|||
}
|
||||
|
||||
func (trader *Trader) Configure(userConfig *Config) error {
|
||||
// config environment
|
||||
if userConfig.Environment != nil && trader.environment != nil {
|
||||
trader.environment.environmentConfig = userConfig.Environment
|
||||
}
|
||||
|
||||
// config risk control
|
||||
if userConfig.RiskControls != nil {
|
||||
trader.SetRiskControls(userConfig.RiskControls)
|
||||
}
|
||||
|
@ -171,12 +187,6 @@ func (trader *Trader) SetRiskControls(riskControls *RiskControls) {
|
|||
func (trader *Trader) RunSingleExchangeStrategy(
|
||||
ctx context.Context, strategy SingleExchangeStrategy, session *ExchangeSession, orderExecutor OrderExecutor,
|
||||
) error {
|
||||
if v, ok := strategy.(StrategyValidator); ok {
|
||||
if err := v.Validate(); err != nil {
|
||||
return fmt.Errorf("failed to validate the config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if shutdown, ok := strategy.(StrategyShutdown); ok {
|
||||
trader.gracefulShutdown.OnShutdown(shutdown.Shutdown)
|
||||
}
|
||||
|
@ -238,12 +248,6 @@ func (trader *Trader) injectFieldsAndSubscribe(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if defaulter, ok := strategy.(StrategyDefaulter); ok {
|
||||
if err := defaulter.Defaults(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if subscriber, ok := strategy.(ExchangeSessionSubscriber); ok {
|
||||
subscriber.Subscribe(session)
|
||||
} else {
|
||||
|
@ -304,12 +308,6 @@ func (trader *Trader) injectFieldsAndSubscribe(ctx context.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
if initializer, ok := strategy.(StrategyInitializer); ok {
|
||||
if err := initializer.Initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if subscriber, ok := strategy.(CrossExchangeSessionSubscriber); ok {
|
||||
subscriber.CrossSubscribe(trader.environment.sessions)
|
||||
} else {
|
||||
|
@ -356,8 +354,23 @@ func (trader *Trader) Run(ctx context.Context) error {
|
|||
return trader.environment.Connect(ctx)
|
||||
}
|
||||
|
||||
// Initialize initializes the strategies, this method is called before the Run method.
|
||||
// It sets the default values and validates the strategy configurations.
|
||||
// And calls the Initialize method if the strategy implements the Initialize method.
|
||||
func (trader *Trader) Initialize(ctx context.Context) error {
|
||||
return trader.IterateStrategies(func(strategy StrategyID) error {
|
||||
if defaulter, ok := strategy.(StrategyDefaulter); ok {
|
||||
if err := defaulter.Defaults(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := strategy.(StrategyValidator); ok {
|
||||
if err := v.Validate(); err != nil {
|
||||
return fmt.Errorf("found invalid strategy config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if initializer, ok := strategy.(StrategyInitializer); ok {
|
||||
return initializer.Initialize()
|
||||
}
|
||||
|
|
|
@ -12,12 +12,6 @@ import (
|
|||
|
||||
"github.com/fatih/color"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
|
||||
"github.com/c9s/bbgo/pkg/core"
|
||||
"github.com/c9s/bbgo/pkg/data/tsv"
|
||||
"github.com/c9s/bbgo/pkg/util"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -26,10 +20,14 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/accounting/pnl"
|
||||
"github.com/c9s/bbgo/pkg/backtest"
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/cmd/cmdutil"
|
||||
"github.com/c9s/bbgo/pkg/core"
|
||||
"github.com/c9s/bbgo/pkg/data/tsv"
|
||||
"github.com/c9s/bbgo/pkg/exchange"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/service"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/c9s/bbgo/pkg/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -547,12 +545,9 @@ var BacktestCmd = &cobra.Command{
|
|||
continue
|
||||
}
|
||||
|
||||
tradeState := sessionTradeStats[session.Name][symbol]
|
||||
profitFactor := tradeState.ProfitFactor
|
||||
winningRatio := tradeState.WinningRatio
|
||||
intervalProfits := tradeState.IntervalProfits[types.Interval1d]
|
||||
tradeStats := sessionTradeStats[session.Name][symbol]
|
||||
|
||||
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Copy(), intervalProfits, profitFactor, winningRatio)
|
||||
symbolReport, err := createSymbolReport(userConfig, session, symbol, trades.Copy(), tradeStats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -563,8 +558,8 @@ var BacktestCmd = &cobra.Command{
|
|||
summaryReport.TotalUnrealizedProfit = symbolReport.PnL.UnrealizedProfit
|
||||
summaryReport.InitialEquityValue = summaryReport.InitialEquityValue.Add(symbolReport.InitialEquityValue())
|
||||
summaryReport.FinalEquityValue = summaryReport.FinalEquityValue.Add(symbolReport.FinalEquityValue())
|
||||
summaryReport.TotalGrossProfit.Add(symbolReport.PnL.GrossProfit)
|
||||
summaryReport.TotalGrossLoss.Add(symbolReport.PnL.GrossLoss)
|
||||
summaryReport.TotalGrossProfit = summaryReport.TotalGrossProfit.Add(symbolReport.PnL.GrossProfit)
|
||||
summaryReport.TotalGrossLoss = summaryReport.TotalGrossLoss.Add(symbolReport.PnL.GrossLoss)
|
||||
|
||||
// write report to a file
|
||||
if generatingReport {
|
||||
|
@ -617,14 +612,12 @@ var BacktestCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
func createSymbolReport(
|
||||
userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade,
|
||||
intervalProfit *types.IntervalProfitCollector,
|
||||
profitFactor, winningRatio fixedpoint.Value,
|
||||
) (
|
||||
func createSymbolReport(userConfig *bbgo.Config, session *bbgo.ExchangeSession, symbol string, trades []types.Trade, tradeStats *types.TradeStats) (
|
||||
*backtest.SessionSymbolReport,
|
||||
error,
|
||||
) {
|
||||
intervalProfit := tradeStats.IntervalProfits[types.Interval1d]
|
||||
|
||||
backtestExchange, ok := session.Exchange.(*backtest.Exchange)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected error, exchange instance is not a backtest exchange")
|
||||
|
@ -634,6 +627,11 @@ func createSymbolReport(
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("market not found: %s, %s", symbol, session.Exchange.Name())
|
||||
}
|
||||
tStart, tEnd := trades[0].Time, trades[len(trades)-1].Time
|
||||
|
||||
periodStart := tStart.Time()
|
||||
periodEnd := tEnd.Time()
|
||||
period := periodEnd.Sub(periodStart)
|
||||
|
||||
startPrice, ok := session.StartPrice(symbol)
|
||||
if !ok {
|
||||
|
@ -650,29 +648,81 @@ func createSymbolReport(
|
|||
Market: market,
|
||||
}
|
||||
|
||||
sharpeRatio := fixedpoint.NewFromFloat(intervalProfit.GetSharpe())
|
||||
sortinoRatio := fixedpoint.NewFromFloat(intervalProfit.GetSortino())
|
||||
|
||||
report := calculator.Calculate(symbol, trades, lastPrice)
|
||||
accountConfig := userConfig.Backtest.GetAccount(session.Exchange.Name().String())
|
||||
initBalances := accountConfig.Balances.BalanceMap()
|
||||
finalBalances := session.GetAccount().Balances()
|
||||
maxProfit := n(intervalProfit.Profits.Max())
|
||||
maxLoss := n(intervalProfit.Profits.Min())
|
||||
drawdown := types.Drawdown(intervalProfit.Profits)
|
||||
maxDrawdown := drawdown.Max()
|
||||
avgDrawdown := drawdown.Average()
|
||||
roundTurnCount := n(float64(tradeStats.NumOfProfitTrade + tradeStats.NumOfLossTrade))
|
||||
roundTurnLength := n(float64(intervalProfit.Profits.Length()))
|
||||
winningCount := n(float64(tradeStats.NumOfProfitTrade))
|
||||
loosingCount := n(float64(tradeStats.NumOfLossTrade))
|
||||
avgProfit := tradeStats.GrossProfit.Div(n(types.NNZ(float64(tradeStats.NumOfProfitTrade), 1)))
|
||||
avgLoss := tradeStats.GrossLoss.Div(n(types.NNZ(float64(tradeStats.NumOfLossTrade), 1)))
|
||||
|
||||
winningPct := winningCount.Div(roundTurnCount)
|
||||
// losingPct := fixedpoint.One.Sub(winningPct)
|
||||
|
||||
sharpeRatio := n(intervalProfit.GetSharpe())
|
||||
sortinoRatio := n(intervalProfit.GetSortino())
|
||||
annVolHis := n(types.AnnualHistoricVolatility(intervalProfit.Profits))
|
||||
totalTimeInMarketSec, avgHoldSec := intervalProfit.GetTimeInMarket()
|
||||
statn, stdErr := types.StatN(intervalProfit.Profits)
|
||||
symbolReport := backtest.SessionSymbolReport{
|
||||
Exchange: session.Exchange.Name(),
|
||||
Symbol: symbol,
|
||||
Market: market,
|
||||
LastPrice: lastPrice,
|
||||
StartPrice: startPrice,
|
||||
PnL: report,
|
||||
InitialBalances: initBalances,
|
||||
FinalBalances: finalBalances,
|
||||
// Manifests: manifests,
|
||||
TradeCount: fixedpoint.NewFromInt(int64(len(trades))),
|
||||
GrossLoss: tradeStats.GrossLoss,
|
||||
GrossProfit: tradeStats.GrossProfit,
|
||||
WinningCount: tradeStats.NumOfProfitTrade,
|
||||
LosingCount: tradeStats.NumOfLossTrade,
|
||||
RoundTurnCount: roundTurnCount,
|
||||
WinningRatio: tradeStats.WinningRatio,
|
||||
PercentProfitable: winningPct,
|
||||
ProfitFactor: tradeStats.ProfitFactor,
|
||||
MaxDrawdown: n(maxDrawdown),
|
||||
AverageDrawdown: n(avgDrawdown),
|
||||
MaxProfit: maxProfit,
|
||||
MaxLoss: maxLoss,
|
||||
MaxLossStreak: tradeStats.MaximumConsecutiveLosses,
|
||||
TotalTimeInMarketSec: totalTimeInMarketSec,
|
||||
AvgHoldSec: avgHoldSec,
|
||||
AvgProfit: avgProfit,
|
||||
AvgLoss: avgLoss,
|
||||
AvgNetProfit: tradeStats.TotalNetProfit.Div(roundTurnLength),
|
||||
TotalNetProfit: tradeStats.TotalNetProfit,
|
||||
AnnualHistoricVolatility: annVolHis,
|
||||
PnL: report,
|
||||
PRR: types.PRR(tradeStats.GrossProfit, tradeStats.GrossLoss, winningCount, loosingCount),
|
||||
Kelly: types.KellyCriterion(tradeStats.ProfitFactor, winningPct),
|
||||
OptimalF: types.OptimalF(intervalProfit.Profits),
|
||||
StatN: statn,
|
||||
StdErr: stdErr,
|
||||
Sharpe: sharpeRatio,
|
||||
Sortino: sortinoRatio,
|
||||
ProfitFactor: profitFactor,
|
||||
WinningRatio: winningRatio,
|
||||
}
|
||||
|
||||
cagr := types.NN(
|
||||
types.CAGR(
|
||||
symbolReport.InitialEquityValue().Float64(),
|
||||
symbolReport.FinalEquityValue().Float64(),
|
||||
int(period.Hours())/24,
|
||||
), 0)
|
||||
|
||||
symbolReport.CAGR = n(cagr)
|
||||
symbolReport.Calmar = n(types.CalmarRatio(cagr, maxDrawdown))
|
||||
symbolReport.Sterling = n(types.SterlingRatio(cagr, avgDrawdown))
|
||||
symbolReport.Burke = n(types.BurkeRatio(cagr, drawdown.AverageSquared()))
|
||||
|
||||
for _, s := range session.Subscriptions {
|
||||
symbolReport.Subscriptions = append(symbolReport.Subscriptions, s)
|
||||
}
|
||||
|
@ -691,6 +741,10 @@ func createSymbolReport(
|
|||
return &symbolReport, nil
|
||||
}
|
||||
|
||||
func n(v float64) fixedpoint.Value {
|
||||
return fixedpoint.NewFromFloat(v)
|
||||
}
|
||||
|
||||
func verify(
|
||||
userConfig *bbgo.Config, backtestService *service.BacktestService,
|
||||
sourceExchanges map[types.ExchangeName]types.Exchange, startTime, endTime time.Time,
|
||||
|
|
|
@ -112,6 +112,18 @@ func (s Slice) Average() float64 {
|
|||
return total / float64(len(s))
|
||||
}
|
||||
|
||||
func (s Slice) AverageSquared() float64 {
|
||||
if len(s) == 0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
total := 0.0
|
||||
for _, value := range s {
|
||||
total += math.Pow(value, 2)
|
||||
}
|
||||
return total / float64(len(s))
|
||||
}
|
||||
|
||||
func (s Slice) Diff() (values Slice) {
|
||||
for i, v := range s {
|
||||
if i == 0 {
|
||||
|
|
|
@ -1005,16 +1005,13 @@ func (e *Exchange) submitMarginOrder(ctx context.Context, order types.SubmitOrde
|
|||
}
|
||||
}
|
||||
|
||||
// could be IOC or FOK
|
||||
switch order.Type {
|
||||
case types.OrderTypeLimit, types.OrderTypeStopLimit:
|
||||
req.TimeInForce(binance.TimeInForceTypeGTC)
|
||||
case types.OrderTypeLimitMaker:
|
||||
// do not set TimeInForce for LimitMaker
|
||||
default:
|
||||
if len(order.TimeInForce) > 0 {
|
||||
// TODO: check the TimeInForce value
|
||||
req.TimeInForce(binance.TimeInForceType(order.TimeInForce))
|
||||
} else {
|
||||
switch order.Type {
|
||||
case types.OrderTypeLimit, types.OrderTypeStopLimit:
|
||||
req.TimeInForce(binance.TimeInForceTypeGTC)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -247,29 +247,6 @@ func toGlobalTradeV3(t v3.Trade) ([]types.Trade, error) {
|
|||
return trades, nil
|
||||
}
|
||||
|
||||
func toGlobalTradeV2(t max.Trade) (*types.Trade, error) {
|
||||
isMargin := t.WalletType == max.WalletTypeMargin
|
||||
side := toGlobalSideType(t.Side)
|
||||
return &types.Trade{
|
||||
ID: t.ID,
|
||||
OrderID: t.OrderID,
|
||||
Price: t.Price,
|
||||
Symbol: toGlobalSymbol(t.Market),
|
||||
Exchange: types.ExchangeMax,
|
||||
Quantity: t.Volume,
|
||||
Side: side,
|
||||
IsBuyer: t.IsBuyer(),
|
||||
IsMaker: t.IsMaker(),
|
||||
Fee: t.Fee,
|
||||
FeeCurrency: toGlobalCurrency(t.FeeCurrency),
|
||||
QuoteQuantity: t.Funds,
|
||||
Time: types.Time(t.CreatedAt),
|
||||
IsMargin: isMargin,
|
||||
IsIsolated: false,
|
||||
IsFutures: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toGlobalDepositStatus(a max.DepositState) types.DepositStatus {
|
||||
switch a {
|
||||
|
||||
|
@ -284,11 +261,21 @@ func toGlobalDepositStatus(a max.DepositState) types.DepositStatus {
|
|||
|
||||
case max.DepositStateAccepted:
|
||||
return types.DepositSuccess
|
||||
|
||||
case max.DepositStateFailed: // v3 state
|
||||
return types.DepositRejected
|
||||
|
||||
case max.DepositStateProcessing: // v3 states
|
||||
return types.DepositPending
|
||||
|
||||
case max.DepositStateDone: // v3 states
|
||||
return types.DepositSuccess
|
||||
|
||||
}
|
||||
|
||||
// other states goes to this
|
||||
// max.DepositStateSuspect, max.DepositStateSuspended
|
||||
log.Warnf("unsupported deposit state %q from max exchange", a)
|
||||
log.Errorf("unsupported deposit state %q from max exchange", a)
|
||||
return types.DepositStatus(a)
|
||||
}
|
||||
|
||||
|
|
|
@ -116,6 +116,11 @@ const (
|
|||
DepositStateSuspended DepositState = "suspended"
|
||||
DepositStateAccepted DepositState = "accepted"
|
||||
DepositStateChecking DepositState = "checking"
|
||||
|
||||
// v3 states
|
||||
DepositStateProcessing DepositState = "processing"
|
||||
DepositStateFailed DepositState = "failed"
|
||||
DepositStateDone DepositState = "done"
|
||||
)
|
||||
|
||||
type Deposit struct {
|
||||
|
|
|
@ -136,6 +136,12 @@ func (c *CancelOrderRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (c *CancelOrderRequest) GetPath() string {
|
||||
return "/api/v3/order"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (c *CancelOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
||||
|
||||
params, err := c.GetParameters()
|
||||
|
@ -144,7 +150,9 @@ func (c *CancelOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
|||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/v3/order"
|
||||
var apiURL string
|
||||
|
||||
apiURL = c.GetPath()
|
||||
|
||||
req, err := c.client.NewAuthenticatedRequest(ctx, "DELETE", apiURL, query, params)
|
||||
if err != nil {
|
||||
|
@ -157,8 +165,32 @@ func (c *CancelOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
|||
}
|
||||
|
||||
var apiResponse max.Order
|
||||
|
||||
type responseUnmarshaler interface {
|
||||
Unmarshal(data []byte) error
|
||||
}
|
||||
|
||||
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||
// Hence, this is commented for backward compatibility
|
||||
// response.IsJSON()
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &apiResponse, nil
|
||||
}
|
||||
|
|
|
@ -6,11 +6,10 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/c9s/bbgo/pkg/exchange/max/maxapi"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
max "github.com/c9s/bbgo/pkg/exchange/max/maxapi"
|
||||
)
|
||||
|
||||
func (c *CancelWalletOrderAllRequest) Side(side string) *CancelWalletOrderAllRequest {
|
||||
|
@ -166,6 +165,12 @@ func (c *CancelWalletOrderAllRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (c *CancelWalletOrderAllRequest) GetPath() string {
|
||||
return "/api/v3/wallet/:walletType/orders"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (c *CancelWalletOrderAllRequest) Do(ctx context.Context) ([]OrderCancelResponse, error) {
|
||||
|
||||
params, err := c.GetParameters()
|
||||
|
@ -174,7 +179,9 @@ func (c *CancelWalletOrderAllRequest) Do(ctx context.Context) ([]OrderCancelResp
|
|||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/v3/wallet/:walletType/orders"
|
||||
var apiURL string
|
||||
|
||||
apiURL = c.GetPath()
|
||||
slugs, err := c.GetSlugsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -193,8 +200,32 @@ func (c *CancelWalletOrderAllRequest) Do(ctx context.Context) ([]OrderCancelResp
|
|||
}
|
||||
|
||||
var apiResponse []OrderCancelResponse
|
||||
|
||||
type responseUnmarshaler interface {
|
||||
Unmarshal(data []byte) error
|
||||
}
|
||||
|
||||
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||
// Hence, this is commented for backward compatibility
|
||||
// response.IsJSON()
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
|
|
@ -236,6 +236,12 @@ func (c *CreateWalletOrderRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (c *CreateWalletOrderRequest) GetPath() string {
|
||||
return "/api/v3/wallet/:walletType/order"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (c *CreateWalletOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
||||
|
||||
params, err := c.GetParameters()
|
||||
|
@ -244,7 +250,9 @@ func (c *CreateWalletOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
|||
}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/v3/wallet/:walletType/order"
|
||||
var apiURL string
|
||||
|
||||
apiURL = c.GetPath()
|
||||
slugs, err := c.GetSlugsMap()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -263,8 +271,32 @@ func (c *CreateWalletOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
|||
}
|
||||
|
||||
var apiResponse max.Order
|
||||
|
||||
type responseUnmarshaler interface {
|
||||
Unmarshal(data []byte) error
|
||||
}
|
||||
|
||||
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||
// Hence, this is commented for backward compatibility
|
||||
// response.IsJSON()
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &apiResponse, nil
|
||||
}
|
||||
|
|
|
@ -109,13 +109,21 @@ func (g *GetMarginADRatioRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (g *GetMarginADRatioRequest) GetPath() string {
|
||||
return "/api/v3/wallet/m/ad_ratio"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (g *GetMarginADRatioRequest) Do(ctx context.Context) (*ADRatio, error) {
|
||||
|
||||
// no body params
|
||||
var params interface{}
|
||||
query := url.Values{}
|
||||
|
||||
apiURL := "/api/v3/wallet/m/ad_ratio"
|
||||
var apiURL string
|
||||
|
||||
apiURL = g.GetPath()
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
|
@ -128,8 +136,32 @@ func (g *GetMarginADRatioRequest) Do(ctx context.Context) (*ADRatio, error) {
|
|||
}
|
||||
|
||||
var apiResponse ADRatio
|
||||
|
||||
type responseUnmarshaler interface {
|
||||
Unmarshal(data []byte) error
|
||||
}
|
||||
|
||||
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||
// Hence, this is commented for backward compatibility
|
||||
// response.IsJSON()
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &apiResponse, nil
|
||||
}
|
||||
|
|
|
@ -136,6 +136,12 @@ func (g *GetOrderRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (g *GetOrderRequest) GetPath() string {
|
||||
return "/api/v3/order"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (g *GetOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
||||
|
||||
// empty params for GET operation
|
||||
|
@ -145,7 +151,9 @@ func (g *GetOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/api/v3/order"
|
||||
var apiURL string
|
||||
|
||||
apiURL = g.GetPath()
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
|
@ -158,8 +166,32 @@ func (g *GetOrderRequest) Do(ctx context.Context) (*max.Order, error) {
|
|||
}
|
||||
|
||||
var apiResponse max.Order
|
||||
|
||||
type responseUnmarshaler interface {
|
||||
Unmarshal(data []byte) error
|
||||
}
|
||||
|
||||
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||
// Hence, this is commented for backward compatibility
|
||||
// response.IsJSON()
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &apiResponse, nil
|
||||
}
|
||||
|
|
|
@ -135,6 +135,12 @@ func (g *GetOrderTradesRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (g *GetOrderTradesRequest) GetPath() string {
|
||||
return "/api/v3/order/trades"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (g *GetOrderTradesRequest) Do(ctx context.Context) ([]Trade, error) {
|
||||
|
||||
// empty params for GET operation
|
||||
|
@ -144,7 +150,9 @@ func (g *GetOrderTradesRequest) Do(ctx context.Context) ([]Trade, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/api/v3/order/trades"
|
||||
var apiURL string
|
||||
|
||||
apiURL = g.GetPath()
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
|
@ -157,8 +165,32 @@ func (g *GetOrderTradesRequest) Do(ctx context.Context) ([]Trade, error) {
|
|||
}
|
||||
|
||||
var apiResponse []Trade
|
||||
|
||||
type responseUnmarshaler interface {
|
||||
Unmarshal(data []byte) error
|
||||
}
|
||||
|
||||
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||
// Hence, this is commented for backward compatibility
|
||||
// response.IsJSON()
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
|
|
@ -5,45 +5,42 @@ var spotSymbolMap = map[string]string{
|
|||
"1INCHEUR": "1INCH-EUR",
|
||||
"1INCHUSDC": "1INCH-USDC",
|
||||
"1INCHUSDT": "1INCH-USDT",
|
||||
"AAVEBTC": "AAVE-BTC",
|
||||
"AAVEEUR": "AAVE-EUR",
|
||||
"AAVEUSDC": "AAVE-USDC",
|
||||
"AAVEUSDT": "AAVE-USDT",
|
||||
"ACAUSDC": "ACA-USDC",
|
||||
"ACAUSDT": "ACA-USDT",
|
||||
"ACEUSDC": "ACE-USDC",
|
||||
"ACEUSDT": "ACE-USDT",
|
||||
"ACHUSDT": "ACH-USDT",
|
||||
"ADABTC": "ADA-BTC",
|
||||
"ADAETH": "ADA-ETH",
|
||||
"ADAEUR": "ADA-EUR",
|
||||
"ADAUSDC": "ADA-USDC",
|
||||
"ADAUSDT": "ADA-USDT",
|
||||
"AERGOUSDT": "AERGO-USDT",
|
||||
"AEVOUSDT": "AEVO-USDT",
|
||||
"AGIXUSDC": "AGIX-USDC",
|
||||
"AGIXUSDT": "AGIX-USDT",
|
||||
"AGLDUSDC": "AGLD-USDC",
|
||||
"AGLDUSDT": "AGLD-USDT",
|
||||
"AIDOGEUSDT": "AIDOGE-USDT",
|
||||
"AKITAUSDT": "AKITA-USDT",
|
||||
"ALCXUSDC": "ALCX-USDC",
|
||||
"ALCXUSDT": "ALCX-USDT",
|
||||
"ALGOEUR": "ALGO-EUR",
|
||||
"ALGOTRY": "ALGO-TRY",
|
||||
"ALGOUSDC": "ALGO-USDC",
|
||||
"ALGOUSDT": "ALGO-USDT",
|
||||
"ALPHAUSDC": "ALPHA-USDC",
|
||||
"ALPHAUSDT": "ALPHA-USDT",
|
||||
"ANTUSDT": "ANT-USDT",
|
||||
"APEEUR": "APE-EUR",
|
||||
"APEUSDC": "APE-USDC",
|
||||
"APEUSDT": "APE-USDT",
|
||||
"API3USDC": "API3-USDC",
|
||||
"API3USDT": "API3-USDT",
|
||||
"APMUSDT": "APM-USDT",
|
||||
"APTEUR": "APT-EUR",
|
||||
"APTUSDC": "APT-USDC",
|
||||
"APTUSDT": "APT-USDT",
|
||||
"ARBEUR": "ARB-EUR",
|
||||
"ARBUSDC": "ARB-USDC",
|
||||
"ARBUSDT": "ARB-USDT",
|
||||
"ARGUSDT": "ARG-USDT",
|
||||
"ARKMUSDT": "ARKM-USDT",
|
||||
"ARTYUSDT": "ARTY-USDT",
|
||||
"ARUSDC": "AR-USDC",
|
||||
"ARUSDT": "AR-USDT",
|
||||
|
@ -51,54 +48,57 @@ var spotSymbolMap = map[string]string{
|
|||
"ASTRUSDC": "ASTR-USDC",
|
||||
"ASTRUSDT": "ASTR-USDT",
|
||||
"ASTUSDT": "AST-USDT",
|
||||
"ATOMBTC": "ATOM-BTC",
|
||||
"ATOMETH": "ATOM-ETH",
|
||||
"ATHUSDT": "ATH-USDT",
|
||||
"ATOMEUR": "ATOM-EUR",
|
||||
"ATOMUSDC": "ATOM-USDC",
|
||||
"ATOMUSDT": "ATOM-USDT",
|
||||
"AUCTIONUSDC": "AUCTION-USDC",
|
||||
"AUCTIONUSDT": "AUCTION-USDT",
|
||||
"AVAXBTC": "AVAX-BTC",
|
||||
"AVAXEUR": "AVAX-EUR",
|
||||
"AVAXUSDC": "AVAX-USDC",
|
||||
"AVAXUSDT": "AVAX-USDT",
|
||||
"AVIVEUSDT": "AVIVE-USDT",
|
||||
"AXSEUR": "AXS-EUR",
|
||||
"AXSUSDC": "AXS-USDC",
|
||||
"AXSUSDT": "AXS-USDT",
|
||||
"AZYUSDT": "AZY-USDT",
|
||||
"BABYDOGEUSDT": "BABYDOGE-USDT",
|
||||
"BADGERUSDC": "BADGER-USDC",
|
||||
"BADGERUSDT": "BADGER-USDT",
|
||||
"BALEUR": "BAL-EUR",
|
||||
"BALUSDC": "BAL-USDC",
|
||||
"BALUSDT": "BAL-USDT",
|
||||
"BANDUSDC": "BAND-USDC",
|
||||
"BANDUSDT": "BAND-USDT",
|
||||
"BATEUR": "BAT-EUR",
|
||||
"BATUSDC": "BAT-USDC",
|
||||
"BATUSDT": "BAT-USDT",
|
||||
"BCHBTC": "BCH-BTC",
|
||||
"BCHUSDC": "BCH-USDC",
|
||||
"BCHUSDT": "BCH-USDT",
|
||||
"BETHETH": "BETH-ETH",
|
||||
"BETHUSDT": "BETH-USDT",
|
||||
"BICOUSDC": "BICO-USDC",
|
||||
"BICOUSDT": "BICO-USDT",
|
||||
"BIGTIMEUSDT": "BIGTIME-USDT",
|
||||
"BLOCKUSDT": "BLOCK-USDT",
|
||||
"BLOKUSDT": "BLOK-USDT",
|
||||
"BLURUSDC": "BLUR-USDC",
|
||||
"BLURUSDT": "BLUR-USDT",
|
||||
"BNBUSDC": "BNB-USDC",
|
||||
"BNBUSDT": "BNB-USDT",
|
||||
"BNTUSDC": "BNT-USDC",
|
||||
"BNTUSDT": "BNT-USDT",
|
||||
"BOMEUSDT": "BOME-USDT",
|
||||
"BONEUSDT": "BONE-USDT",
|
||||
"BONKUSDT": "BONK-USDT",
|
||||
"BORAUSDT": "BORA-USDT",
|
||||
"BORINGUSDT": "BORING-USDT",
|
||||
"BRWLUSDT": "BRWL-USDT",
|
||||
"BSVBTC": "BSV-BTC",
|
||||
"BSVUSDC": "BSV-USDC",
|
||||
"BSVUSDT": "BSV-USDT",
|
||||
"BTCAUD": "BTC-AUD",
|
||||
"BTCBRL": "BTC-BRL",
|
||||
"BTCDAI": "BTC-DAI",
|
||||
"BTCEUR": "BTC-EUR",
|
||||
"BTCEURT": "BTC-EURT",
|
||||
"BTCTRY": "BTC-TRY",
|
||||
"BTCUSDC": "BTC-USDC",
|
||||
"BTCUSDT": "BTC-USDT",
|
||||
|
@ -106,16 +106,14 @@ var spotSymbolMap = map[string]string{
|
|||
"BZZUSDT": "BZZ-USDT",
|
||||
"CEEKUSDT": "CEEK-USDT",
|
||||
"CELOUSDT": "CELO-USDT",
|
||||
"CELRUSDC": "CELR-USDC",
|
||||
"CELRUSDT": "CELR-USDT",
|
||||
"CELUSDC": "CEL-USDC",
|
||||
"CELUSDT": "CEL-USDT",
|
||||
"CETUSUSDC": "CETUS-USDC",
|
||||
"CETUSUSDT": "CETUS-USDT",
|
||||
"CFGUSDC": "CFG-USDC",
|
||||
"CFGUSDT": "CFG-USDT",
|
||||
"CFXUSDC": "CFX-USDC",
|
||||
"CFXUSDT": "CFX-USDT",
|
||||
"CGLUSDT": "CGL-USDT",
|
||||
"CHZBTC": "CHZ-BTC",
|
||||
"CHZEUR": "CHZ-EUR",
|
||||
"CHZUSDC": "CHZ-USDC",
|
||||
"CHZUSDT": "CHZ-USDT",
|
||||
|
@ -124,40 +122,37 @@ var spotSymbolMap = map[string]string{
|
|||
"COMPEUR": "COMP-EUR",
|
||||
"COMPUSDC": "COMP-USDC",
|
||||
"COMPUSDT": "COMP-USDT",
|
||||
"CONVUSDT": "CONV-USDT",
|
||||
"CORETRY": "CORE-TRY",
|
||||
"COREUSDT": "CORE-USDT",
|
||||
"CQTUSDT": "CQT-USDT",
|
||||
"CROBTC": "CRO-BTC",
|
||||
"CROEUR": "CRO-EUR",
|
||||
"CROUSDC": "CRO-USDC",
|
||||
"CROUSDT": "CRO-USDT",
|
||||
"CRVBTC": "CRV-BTC",
|
||||
"CRVEUR": "CRV-EUR",
|
||||
"CRVUSDC": "CRV-USDC",
|
||||
"CRVUSDT": "CRV-USDT",
|
||||
"CSPRUSDC": "CSPR-USDC",
|
||||
"CSPRUSDT": "CSPR-USDT",
|
||||
"CTCUSDC": "CTC-USDC",
|
||||
"CTCUSDT": "CTC-USDT",
|
||||
"CTXCUSDC": "CTXC-USDC",
|
||||
"CTXCUSDT": "CTXC-USDT",
|
||||
"CVCUSDC": "CVC-USDC",
|
||||
"CVCUSDT": "CVC-USDT",
|
||||
"CVXUSDC": "CVX-USDC",
|
||||
"CVXUSDT": "CVX-USDT",
|
||||
"CXTUSDT": "CXT-USDT",
|
||||
"DAIUSDC": "DAI-USDC",
|
||||
"DAIUSDT": "DAI-USDT",
|
||||
"DAOUSDT": "DAO-USDT",
|
||||
"DCRUSDT": "DCR-USDT",
|
||||
"DEGENUSDT": "DEGEN-USDT",
|
||||
"DEPUSDT": "DEP-USDT",
|
||||
"DGBUSDT": "DGB-USDT",
|
||||
"DIAUSDT": "DIA-USDT",
|
||||
"DMAILUSDT": "DMAIL-USDT",
|
||||
"DOGEBTC": "DOGE-BTC",
|
||||
"DOGEETH": "DOGE-ETH",
|
||||
"DOGEEUR": "DOGE-EUR",
|
||||
"DOGEUSDC": "DOGE-USDC",
|
||||
"DOGEUSDT": "DOGE-USDT",
|
||||
"DOGSUSDT": "DOGS-USDT",
|
||||
"DORAUSDT": "DORA-USDT",
|
||||
"DOSEUSDT": "DOSE-USDT",
|
||||
"DOTBTC": "DOT-BTC",
|
||||
"DOTEUR": "DOT-EUR",
|
||||
"DOTUSDC": "DOT-USDC",
|
||||
"DOTUSDT": "DOT-USDT",
|
||||
|
@ -169,47 +164,43 @@ var spotSymbolMap = map[string]string{
|
|||
"EGLDUSDT": "EGLD-USDT",
|
||||
"ELFUSDT": "ELF-USDT",
|
||||
"ELONUSDT": "ELON-USDT",
|
||||
"EMUSDT": "EM-USDT",
|
||||
"ENJUSDT": "ENJ-USDT",
|
||||
"ENSUSDC": "ENS-USDC",
|
||||
"ENSUSDT": "ENS-USDT",
|
||||
"EOSBTC": "EOS-BTC",
|
||||
"EOSETH": "EOS-ETH",
|
||||
"EOSEUR": "EOS-EUR",
|
||||
"EOSUSDC": "EOS-USDC",
|
||||
"EOSUSDT": "EOS-USDT",
|
||||
"ERNUSDT": "ERN-USDT",
|
||||
"ETCBTC": "ETC-BTC",
|
||||
"ETCUSDC": "ETC-USDC",
|
||||
"ETCUSDT": "ETC-USDT",
|
||||
"ETHAUD": "ETH-AUD",
|
||||
"ETHBRL": "ETH-BRL",
|
||||
"ETHBTC": "ETH-BTC",
|
||||
"ETHDAI": "ETH-DAI",
|
||||
"ETHEUR": "ETH-EUR",
|
||||
"ETHEURT": "ETH-EURT",
|
||||
"ETHFIUSDT": "ETHFI-USDT",
|
||||
"ETHTRY": "ETH-TRY",
|
||||
"ETHUSDC": "ETH-USDC",
|
||||
"ETHUSDT": "ETH-USDT",
|
||||
"ETHWUSDC": "ETHW-USDC",
|
||||
"ETHWUSDT": "ETHW-USDT",
|
||||
"EURTUSDT": "EURT-USDT",
|
||||
"FETEUR": "FET-EUR",
|
||||
"FETUSDT": "FET-USDT",
|
||||
"FILBTC": "FIL-BTC",
|
||||
"FILETH": "FIL-ETH",
|
||||
"FILUSDC": "FIL-USDC",
|
||||
"FILUSDT": "FIL-USDT",
|
||||
"FITFIUSDT": "FITFI-USDT",
|
||||
"FLMUSDT": "FLM-USDT",
|
||||
"FLOKIUSDC": "FLOKI-USDC",
|
||||
"FLOKIUSDT": "FLOKI-USDT",
|
||||
"FLOWEUR": "FLOW-EUR",
|
||||
"FLOWUSDC": "FLOW-USDC",
|
||||
"FLOWUSDT": "FLOW-USDT",
|
||||
"FLREUR": "FLR-EUR",
|
||||
"FLRUSDC": "FLR-USDC",
|
||||
"FLRUSDT": "FLR-USDT",
|
||||
"FORTHUSDC": "FORTH-USDC",
|
||||
"FORTHUSDT": "FORTH-USDT",
|
||||
"FOXYUSDT": "FOXY-USDT",
|
||||
"FRONTUSDC": "FRONT-USDC",
|
||||
"FRONTUSDT": "FRONT-USDT",
|
||||
"FTMEUR": "FTM-EUR",
|
||||
"FTMUSDC": "FTM-USDC",
|
||||
|
@ -218,28 +209,29 @@ var spotSymbolMap = map[string]string{
|
|||
"FXSUSDT": "FXS-USDT",
|
||||
"GALAUSDC": "GALA-USDC",
|
||||
"GALAUSDT": "GALA-USDT",
|
||||
"GALEUR": "GAL-EUR",
|
||||
"GALFTUSDT": "GALFT-USDT",
|
||||
"GALUSDT": "GAL-USDT",
|
||||
"GARIUSDT": "GARI-USDT",
|
||||
"GASUSDT": "GAS-USDT",
|
||||
"GEARUSDT": "GEAR-USDT",
|
||||
"GFTUSDC": "GFT-USDC",
|
||||
"GFTUSDT": "GFT-USDT",
|
||||
"GHSTUSDC": "GHST-USDC",
|
||||
"GHSTUSDT": "GHST-USDT",
|
||||
"GLMRUSDC": "GLMR-USDC",
|
||||
"GLMRUSDT": "GLMR-USDT",
|
||||
"GLMUSDC": "GLM-USDC",
|
||||
"GLMUSDT": "GLM-USDT",
|
||||
"GMTUSDC": "GMT-USDC",
|
||||
"GMTUSDT": "GMT-USDT",
|
||||
"GMXUSDT": "GMX-USDT",
|
||||
"GOALUSDT": "GOAL-USDT",
|
||||
"GODSUSDT": "GODS-USDT",
|
||||
"GOGUSDT": "GOG-USDT",
|
||||
"GPTUSDT": "GPT-USDT",
|
||||
"GRTBTC": "GRT-BTC",
|
||||
"GRTEUR": "GRT-EUR",
|
||||
"GRTUSDC": "GRT-USDC",
|
||||
"GRTUSDT": "GRT-USDT",
|
||||
"HBARBTC": "HBAR-BTC",
|
||||
"GUSDC": "G-USDC",
|
||||
"GUSDT": "G-USDT",
|
||||
"HBAREUR": "HBAR-EUR",
|
||||
"HBARUSDC": "HBAR-USDC",
|
||||
"HBARUSDT": "HBAR-USDT",
|
||||
|
@ -247,11 +239,12 @@ var spotSymbolMap = map[string]string{
|
|||
"ICPEUR": "ICP-EUR",
|
||||
"ICPUSDC": "ICP-USDC",
|
||||
"ICPUSDT": "ICP-USDT",
|
||||
"ICXUSDC": "ICX-USDC",
|
||||
"ICXUSDT": "ICX-USDT",
|
||||
"IDUSDT": "ID-USDT",
|
||||
"IGUUSDT": "IGU-USDT",
|
||||
"ILVUSDT": "ILV-USDT",
|
||||
"IMXEUR": "IMX-EUR",
|
||||
"IMXUSDC": "IMX-USDC",
|
||||
"IMXUSDT": "IMX-USDT",
|
||||
"INJEUR": "INJ-EUR",
|
||||
"INJUSDT": "INJ-USDT",
|
||||
|
@ -267,44 +260,44 @@ var spotSymbolMap = map[string]string{
|
|||
"JTOUSDT": "JTO-USDT",
|
||||
"JUPUSDT": "JUP-USDT",
|
||||
"KANUSDT": "KAN-USDT",
|
||||
"KCALUSDT": "KCAL-USDT",
|
||||
"KDAUSDC": "KDA-USDC",
|
||||
"KDAUSDT": "KDA-USDT",
|
||||
"KINEUSDT": "KINE-USDT",
|
||||
"KISHUUSDT": "KISHU-USDT",
|
||||
"KLAYUSDC": "KLAY-USDC",
|
||||
"KLAYUSDT": "KLAY-USDT",
|
||||
"KNCUSDC": "KNC-USDC",
|
||||
"KNCUSDT": "KNC-USDT",
|
||||
"KP3RUSDC": "KP3R-USDC",
|
||||
"KP3RUSDT": "KP3R-USDT",
|
||||
"KSMUSDC": "KSM-USDC",
|
||||
"KSMUSDT": "KSM-USDT",
|
||||
"LAMBUSDT": "LAMB-USDT",
|
||||
"LATUSDT": "LAT-USDT",
|
||||
"LBRUSDT": "LBR-USDT",
|
||||
"LDOEUR": "LDO-EUR",
|
||||
"LDOUSDC": "LDO-USDC",
|
||||
"LDOUSDT": "LDO-USDT",
|
||||
"LEASHUSDT": "LEASH-USDT",
|
||||
"LEOUSDT": "LEO-USDT",
|
||||
"LETUSDT": "LET-USDT",
|
||||
"LHINUUSDT": "LHINU-USDT",
|
||||
"LINGUSDT": "LING-USDT",
|
||||
"LINKBTC": "LINK-BTC",
|
||||
"LINKETH": "LINK-ETH",
|
||||
"LINKEUR": "LINK-EUR",
|
||||
"LINKUSDC": "LINK-USDC",
|
||||
"LINKUSDT": "LINK-USDT",
|
||||
"LITHUSDC": "LITH-USDC",
|
||||
"LITHUSDT": "LITH-USDT",
|
||||
"LONUSDT": "LON-USDT",
|
||||
"LOOKSUSDC": "LOOKS-USDC",
|
||||
"LOOKSUSDT": "LOOKS-USDT",
|
||||
"LPTUSDC": "LPT-USDC",
|
||||
"LPTUSDT": "LPT-USDT",
|
||||
"LQTYUSDC": "LQTY-USDC",
|
||||
"LQTYUSDT": "LQTY-USDT",
|
||||
"LRCUSDC": "LRC-USDC",
|
||||
"LRCUSDT": "LRC-USDT",
|
||||
"LSKUSDC": "LSK-USDC",
|
||||
"LSKUSDT": "LSK-USDT",
|
||||
"LTCBTC": "LTC-BTC",
|
||||
"LTCETH": "LTC-ETH",
|
||||
"LTCEUR": "LTC-EUR",
|
||||
"LTCTRY": "LTC-TRY",
|
||||
"LTCUSDC": "LTC-USDC",
|
||||
"LTCUSDT": "LTC-USDT",
|
||||
"LUNAUSDC": "LUNA-USDC",
|
||||
|
@ -314,19 +307,16 @@ var spotSymbolMap = map[string]string{
|
|||
"LUNCUSDT": "LUNC-USDT",
|
||||
"MAGICUSDC": "MAGIC-USDC",
|
||||
"MAGICUSDT": "MAGIC-USDT",
|
||||
"MANABTC": "MANA-BTC",
|
||||
"MANAEUR": "MANA-EUR",
|
||||
"MANAUSDC": "MANA-USDC",
|
||||
"MANAUSDT": "MANA-USDT",
|
||||
"MASKUSDC": "MASK-USDC",
|
||||
"MASKUSDT": "MASK-USDT",
|
||||
"MATICBTC": "MATIC-BTC",
|
||||
"MATICEUR": "MATIC-EUR",
|
||||
"MATICUSDC": "MATIC-USDC",
|
||||
"MATICUSDT": "MATIC-USDT",
|
||||
"MAXUSDT": "MAX-USDT",
|
||||
"MDTUSDT": "MDT-USDT",
|
||||
"MEMEUSDT": "MEME-USDT",
|
||||
"MENGOUSDT": "MENGO-USDT",
|
||||
"MERLUSDC": "MERL-USDC",
|
||||
"MERLUSDT": "MERL-USDT",
|
||||
"METISUSDC": "METIS-USDC",
|
||||
"METISUSDT": "METIS-USDT",
|
||||
|
@ -335,41 +325,35 @@ var spotSymbolMap = map[string]string{
|
|||
"MINAEUR": "MINA-EUR",
|
||||
"MINAUSDC": "MINA-USDC",
|
||||
"MINAUSDT": "MINA-USDT",
|
||||
"MKRBTC": "MKR-BTC",
|
||||
"MKREUR": "MKR-EUR",
|
||||
"MKRUSDC": "MKR-USDC",
|
||||
"MKRUSDT": "MKR-USDT",
|
||||
"MLNUSDC": "MLN-USDC",
|
||||
"MLNUSDT": "MLN-USDT",
|
||||
"MOVEZUSDT": "MOVEZ-USDT",
|
||||
"MOVRUSDC": "MOVR-USDC",
|
||||
"MOVRUSDT": "MOVR-USDT",
|
||||
"MRSTUSDT": "MRST-USDT",
|
||||
"MSNUSDT": "MSN-USDT",
|
||||
"MXCUSDT": "MXC-USDT",
|
||||
"MYRIAUSDT": "MYRIA-USDT",
|
||||
"NEARBTC": "NEAR-BTC",
|
||||
"NEARUSDC": "NEAR-USDC",
|
||||
"NEARUSDT": "NEAR-USDT",
|
||||
"NEOBTC": "NEO-BTC",
|
||||
"NEOUSDC": "NEO-USDC",
|
||||
"NEOUSDT": "NEO-USDT",
|
||||
"NFTUSDT": "NFT-USDT",
|
||||
"NMRUSDC": "NMR-USDC",
|
||||
"NMRUSDT": "NMR-USDT",
|
||||
"NOTUSDT": "NOT-USDT",
|
||||
"NULSUSDT": "NULS-USDT",
|
||||
"NYMUSDT": "NYM-USDT",
|
||||
"OASUSDT": "OAS-USDT",
|
||||
"OKBBTC": "OKB-BTC",
|
||||
"OKBETH": "OKB-ETH",
|
||||
"OKBUSDC": "OKB-USDC",
|
||||
"OKBUSDT": "OKB-USDT",
|
||||
"OKTBTC": "OKT-BTC",
|
||||
"OKTETH": "OKT-ETH",
|
||||
"OKTUSDC": "OKT-USDC",
|
||||
"OKTUSDT": "OKT-USDT",
|
||||
"OMGUSDT": "OMG-USDT",
|
||||
"OMIUSDT": "OMI-USDT",
|
||||
"OMNUSDT": "OMN-USDT",
|
||||
"OMUSDC": "OM-USDC",
|
||||
"OMUSDT": "OM-USDT",
|
||||
"ONDOUSDC": "ONDO-USDC",
|
||||
"ONDOUSDT": "ONDO-USDT",
|
||||
"ONEUSDT": "ONE-USDT",
|
||||
"ONTUSDT": "ONT-USDT",
|
||||
"OPEUR": "OP-EUR",
|
||||
|
@ -379,34 +363,41 @@ var spotSymbolMap = map[string]string{
|
|||
"ORBUSDT": "ORB-USDT",
|
||||
"ORDIUSDC": "ORDI-USDC",
|
||||
"ORDIUSDT": "ORDI-USDT",
|
||||
"OXTUSDC": "OXT-USDC",
|
||||
"OXTUSDT": "OXT-USDT",
|
||||
"PCIUSDT": "PCI-USDT",
|
||||
"PEOPLEUSDC": "PEOPLE-USDC",
|
||||
"PEOPLEUSDT": "PEOPLE-USDT",
|
||||
"PEPEBRL": "PEPE-BRL",
|
||||
"PEPEUSDC": "PEPE-USDC",
|
||||
"PEPEUSDT": "PEPE-USDT",
|
||||
"PERPUSDC": "PERP-USDC",
|
||||
"PERPUSDT": "PERP-USDT",
|
||||
"PHAUSDC": "PHA-USDC",
|
||||
"PHAUSDT": "PHA-USDT",
|
||||
"PITUSDT": "PIT-USDT",
|
||||
"POLSUSDT": "POLS-USDT",
|
||||
"PIXELUSDT": "PIXEL-USDT",
|
||||
"POLUSDC": "POL-USDC",
|
||||
"POLUSDT": "POL-USDT",
|
||||
"POLYDOGEUSDT": "POLYDOGE-USDT",
|
||||
"PORUSDT": "POR-USDT",
|
||||
"PRCLUSDT": "PRCL-USDT",
|
||||
"PRQUSDT": "PRQ-USDT",
|
||||
"PSTAKEUSDC": "PSTAKE-USDC",
|
||||
"PSTAKEUSDT": "PSTAKE-USDT",
|
||||
"PYTHUSDC": "PYTH-USDC",
|
||||
"PYTHUSDT": "PYTH-USDT",
|
||||
"QTUMBTC": "QTUM-BTC",
|
||||
"QTUMUSDC": "QTUM-USDC",
|
||||
"QTUMUSDT": "QTUM-USDT",
|
||||
"RACAUSDT": "RACA-USDT",
|
||||
"RADARUSDT": "RADAR-USDT",
|
||||
"RAYUSDT": "RAY-USDT",
|
||||
"RDNTUSDC": "RDNT-USDC",
|
||||
"RDNTUSDT": "RDNT-USDT",
|
||||
"RENDERUSDC": "RENDER-USDC",
|
||||
"RENDERUSDT": "RENDER-USDT",
|
||||
"RENUSDC": "REN-USDC",
|
||||
"RENUSDT": "REN-USDT",
|
||||
"REPUSDT": "REP-USDT",
|
||||
"REVVUSDT": "REVV-USDT",
|
||||
"RIOUSDT": "RIO-USDT",
|
||||
"RNDRUSDT": "RNDR-USDT",
|
||||
"RONUSDC": "RON-USDC",
|
||||
"RONUSDT": "RON-USDT",
|
||||
"RPLUSDC": "RPL-USDC",
|
||||
|
@ -421,17 +412,17 @@ var spotSymbolMap = map[string]string{
|
|||
"SANDEUR": "SAND-EUR",
|
||||
"SANDUSDC": "SAND-USDC",
|
||||
"SANDUSDT": "SAND-USDT",
|
||||
"SATSUSDC": "SATS-USDC",
|
||||
"SATSUSDT": "SATS-USDT",
|
||||
"SCUSDC": "SC-USDC",
|
||||
"SCUSDT": "SC-USDT",
|
||||
"SDUSDT": "SD-USDT",
|
||||
"SHIBBTC": "SHIB-BTC",
|
||||
"SHIBEUR": "SHIB-EUR",
|
||||
"SHIBUSDC": "SHIB-USDC",
|
||||
"SHIBUSDT": "SHIB-USDT",
|
||||
"SISUSDT": "SIS-USDT",
|
||||
"SKEBUSDT": "SKEB-USDT",
|
||||
"SKLUSDT": "SKL-USDT",
|
||||
"SLNUSDT": "SLN-USDT",
|
||||
"SLERFUSDT": "SLERF-USDT",
|
||||
"SLPUSDT": "SLP-USDT",
|
||||
"SNTUSDT": "SNT-USDT",
|
||||
"SNXEUR": "SNX-EUR",
|
||||
|
@ -442,11 +433,9 @@ var spotSymbolMap = map[string]string{
|
|||
"SOLEUR": "SOL-EUR",
|
||||
"SOLUSDC": "SOL-USDC",
|
||||
"SOLUSDT": "SOL-USDT",
|
||||
"SPELLUSDT": "SPELL-USDT",
|
||||
"SPURSUSDT": "SPURS-USDT",
|
||||
"SSVUSDT": "SSV-USDT",
|
||||
"SSWPUSDT": "SSWP-USDT",
|
||||
"STARLUSDT": "STARL-USDT",
|
||||
"STCUSDT": "STC-USDT",
|
||||
"STETHETH": "STETH-ETH",
|
||||
"STETHUSDT": "STETH-USDT",
|
||||
|
@ -461,7 +450,6 @@ var spotSymbolMap = map[string]string{
|
|||
"SUIEUR": "SUI-EUR",
|
||||
"SUIUSDC": "SUI-USDC",
|
||||
"SUIUSDT": "SUI-USDT",
|
||||
"SUNUSDT": "SUN-USDT",
|
||||
"SUSHIEUR": "SUSHI-EUR",
|
||||
"SUSHIUSDC": "SUSHI-USDC",
|
||||
"SUSHIUSDT": "SUSHI-USDT",
|
||||
|
@ -469,9 +457,8 @@ var spotSymbolMap = map[string]string{
|
|||
"SWEATUSDT": "SWEAT-USDT",
|
||||
"SWFTCUSDT": "SWFTC-USDT",
|
||||
"TAKIUSDT": "TAKI-USDT",
|
||||
"TAMAUSDT": "TAMA-USDT",
|
||||
"THETAUSDT": "THETA-USDT",
|
||||
"THGUSDT": "THG-USDT",
|
||||
"TIAUSDC": "TIA-USDC",
|
||||
"TIAUSDT": "TIA-USDT",
|
||||
"TNSRUSDT": "TNSR-USDT",
|
||||
"TONEUR": "TON-EUR",
|
||||
|
@ -479,28 +466,30 @@ var spotSymbolMap = map[string]string{
|
|||
"TONUSDT": "TON-USDT",
|
||||
"TRAUSDT": "TRA-USDT",
|
||||
"TRBUSDT": "TRB-USDT",
|
||||
"TRXBTC": "TRX-BTC",
|
||||
"TRXETH": "TRX-ETH",
|
||||
"TRXEUR": "TRX-EUR",
|
||||
"TRXUSDC": "TRX-USDC",
|
||||
"TRXUSDT": "TRX-USDT",
|
||||
"TUPUSDT": "TUP-USDT",
|
||||
"TURBOUSDT": "TURBO-USDT",
|
||||
"TUSDC": "T-USDC",
|
||||
"TUSDT": "T-USDT",
|
||||
"ULTIUSDT": "ULTI-USDT",
|
||||
"UMAUSDC": "UMA-USDC",
|
||||
"UMAUSDT": "UMA-USDT",
|
||||
"UNIBTC": "UNI-BTC",
|
||||
"UNIEUR": "UNI-EUR",
|
||||
"UNIUSDC": "UNI-USDC",
|
||||
"UNIUSDT": "UNI-USDT",
|
||||
"USDCAUD": "USDC-AUD",
|
||||
"USDCBRL": "USDC-BRL",
|
||||
"USDCEUR": "USDC-EUR",
|
||||
"USDCUSDT": "USDC-USDT",
|
||||
"USDTAUD": "USDT-AUD",
|
||||
"USDTBRL": "USDT-BRL",
|
||||
"USDTEUR": "USDT-EUR",
|
||||
"USDTTRY": "USDT-TRY",
|
||||
"USDTUSDC": "USDT-USDC",
|
||||
"USTCUSDT": "USTC-USDT",
|
||||
"UTKUSDT": "UTK-USDT",
|
||||
"UXLINKUSDT": "UXLINK-USDT",
|
||||
"VELAUSDT": "VELA-USDT",
|
||||
"VELODROMEUSDT": "VELODROME-USDT",
|
||||
"VELOUSDC": "VELO-USDC",
|
||||
|
@ -511,50 +500,54 @@ var spotSymbolMap = map[string]string{
|
|||
"WAXPUSDC": "WAXP-USDC",
|
||||
"WAXPUSDT": "WAXP-USDT",
|
||||
"WBTCBTC": "WBTC-BTC",
|
||||
"WBTCUSDC": "WBTC-USDC",
|
||||
"WBTCUSDT": "WBTC-USDT",
|
||||
"WIFEUR": "WIF-EUR",
|
||||
"WIFIUSDT": "WIFI-USDT",
|
||||
"WIFUSDC": "WIF-USDC",
|
||||
"WIFUSDT": "WIF-USDT",
|
||||
"WINUSDT": "WIN-USDT",
|
||||
"WLDUSDC": "WLD-USDC",
|
||||
"WLDUSDT": "WLD-USDT",
|
||||
"WNCGUSDT": "WNCG-USDT",
|
||||
"WOOEUR": "WOO-EUR",
|
||||
"WOOUSDT": "WOO-USDT",
|
||||
"WSMUSDT": "WSM-USDT",
|
||||
"WUSDC": "W-USDC",
|
||||
"WUSDT": "W-USDT",
|
||||
"WXTUSDT": "WXT-USDT",
|
||||
"XAUTUSDT": "XAUT-USDT",
|
||||
"XCHBTC": "XCH-BTC",
|
||||
"XCHUSDT": "XCH-USDT",
|
||||
"XECUSDT": "XEC-USDT",
|
||||
"XEMUSDT": "XEM-USDT",
|
||||
"XLMBTC": "XLM-BTC",
|
||||
"XLMEUR": "XLM-EUR",
|
||||
"XLMUSDC": "XLM-USDC",
|
||||
"XLMUSDT": "XLM-USDT",
|
||||
"XNOUSDC": "XNO-USDC",
|
||||
"XNOUSDT": "XNO-USDT",
|
||||
"XPRUSDT": "XPR-USDT",
|
||||
"XRPBTC": "XRP-BTC",
|
||||
"XRPETH": "XRP-ETH",
|
||||
"XRPEUR": "XRP-EUR",
|
||||
"XRPTRY": "XRP-TRY",
|
||||
"XRPUSDC": "XRP-USDC",
|
||||
"XRPUSDT": "XRP-USDT",
|
||||
"XRUSDT": "XR-USDT",
|
||||
"XTZEUR": "XTZ-EUR",
|
||||
"XTZUSDC": "XTZ-USDC",
|
||||
"XTZUSDT": "XTZ-USDT",
|
||||
"YFIUSDC": "YFI-USDC",
|
||||
"YFIUSDT": "YFI-USDT",
|
||||
"YGGEUR": "YGG-EUR",
|
||||
"YGGUSDC": "YGG-USDC",
|
||||
"YGGUSDT": "YGG-USDT",
|
||||
"ZBCNUSDT": "ZBCN-USDT",
|
||||
"ZENTUSDC": "ZENT-USDC",
|
||||
"ZENTUSDT": "ZENT-USDT",
|
||||
"ZEROUSDC": "ZERO-USDC",
|
||||
"ZEROUSDT": "ZERO-USDT",
|
||||
"ZETAUSDT": "ZETA-USDT",
|
||||
"ZEUSUSDT": "ZEUS-USDT",
|
||||
"ZILUSDC": "ZIL-USDC",
|
||||
"ZILUSDT": "ZIL-USDT",
|
||||
"ZKJUSDT": "ZKJ-USDT",
|
||||
"ZKUSDC": "ZK-USDC",
|
||||
"ZKUSDT": "ZK-USDT",
|
||||
"ZROUSDC": "ZRO-USDC",
|
||||
"ZROUSDT": "ZRO-USDT",
|
||||
"ZRXUSDC": "ZRX-USDC",
|
||||
"ZRXUSDT": "ZRX-USDT",
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/c9s/rockhopper/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddMigration("main", up_main_addPositionIndex, down_main_addPositionIndex)
|
||||
}
|
||||
|
||||
func up_main_addPositionIndex(ctx context.Context, tx rockhopper.SQLExecutor) (err error) {
|
||||
// This code is executed when the migration is applied.
|
||||
_, err = tx.ExecContext(ctx, "CREATE INDEX positions_traded_at ON positions (traded_at, profit);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func down_main_addPositionIndex(ctx context.Context, tx rockhopper.SQLExecutor) (err error) {
|
||||
// This code is executed when the migration is rolled back.
|
||||
_, err = tx.ExecContext(ctx, "DROP INDEX positions_traded_at ON positions;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package sqlite3
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/c9s/rockhopper/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddMigration("main", up_main_addPositionIndex, down_main_addPositionIndex)
|
||||
}
|
||||
|
||||
func up_main_addPositionIndex(ctx context.Context, tx rockhopper.SQLExecutor) (err error) {
|
||||
// This code is executed when the migration is applied.
|
||||
_, err = tx.ExecContext(ctx, "CREATE INDEX positions_traded_at ON positions (traded_at, profit);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func down_main_addPositionIndex(ctx context.Context, tx rockhopper.SQLExecutor) (err error) {
|
||||
// This code is executed when the migration is rolled back.
|
||||
_, err = tx.ExecContext(ctx, "DROP INDEX positions_traded_at;")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -27,6 +27,10 @@ func (s *Strategy) placeTakeProfitOrders(ctx context.Context) error {
|
|||
}
|
||||
|
||||
roundPosition := types.NewPositionFromMarket(s.Market)
|
||||
roundPosition.SetExchangeFeeRate(s.ExchangeSession.ExchangeName, types.ExchangeFee{
|
||||
MakerFeeRate: s.ExchangeSession.MakerFeeRate,
|
||||
TakerFeeRate: s.ExchangeSession.TakerFeeRate,
|
||||
})
|
||||
|
||||
for _, trade := range trades {
|
||||
s.logger.Infof("add trade into the position of this round %s", trade.String())
|
||||
|
|
|
@ -236,7 +236,7 @@ func (s *Strategy) scanDepositHistory(ctx context.Context, asset string, duratio
|
|||
s.watchingDeposits[deposit.TransactionID] = deposit
|
||||
}
|
||||
} else {
|
||||
// ignore all initial deposit history that are already success
|
||||
// ignore all initial deposits that are already in success status
|
||||
logger.Infof("ignored succeess deposit: %s %+v", deposit.TransactionID, deposit)
|
||||
}
|
||||
|
||||
|
|
|
@ -440,9 +440,9 @@ for t in 1 .. n:
|
|||
return argmax(alpha[t,si] over si)
|
||||
*/
|
||||
func hmm(y_t []float64, x_t []float64, l int) float64 {
|
||||
al := make([]float64, l)
|
||||
an := make([]float64, l)
|
||||
as := make([]float64, l)
|
||||
al := make([]float64, 0, l)
|
||||
an := make([]float64, 0, l)
|
||||
as := make([]float64, 0, l)
|
||||
long := 0.
|
||||
neut := 0.
|
||||
short := 0.
|
||||
|
@ -453,9 +453,9 @@ func hmm(y_t []float64, x_t []float64, l int) float64 {
|
|||
sin := make([]float64, 3)
|
||||
sis := make([]float64, 3)
|
||||
for i := -1; i <= 1; i++ {
|
||||
sil = append(sil, x_t[n-1-1]*transitProbability(i, j))
|
||||
sin = append(sin, x_t[n-1-1]*transitProbability(i, j))
|
||||
sis = append(sis, x_t[n-1-1]*transitProbability(i, j))
|
||||
sil = append(sil, 0, x_t[n-1-1]*transitProbability(i, j))
|
||||
sin = append(sin, 0, x_t[n-1-1]*transitProbability(i, j))
|
||||
sis = append(sis, 0, x_t[n-1-1]*transitProbability(i, j))
|
||||
}
|
||||
if j > 0 {
|
||||
_, longArr := floats.MinMax(sil, 3)
|
||||
|
|
|
@ -38,6 +38,12 @@ var askMarginMetrics = prometheus.NewGaugeVec(
|
|||
Help: "the current ask margin (dynamic)",
|
||||
}, []string{"strategy_type", "strategy_id", "exchange", "symbol"})
|
||||
|
||||
var aggregatedSignalMetrics = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "xmaker_aggregated_signal",
|
||||
Help: "",
|
||||
}, []string{"strategy_type", "strategy_id", "exchange", "symbol"})
|
||||
|
||||
var configNumOfLayersMetrics = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "xmaker_config_num_of_layers",
|
||||
|
@ -70,6 +76,7 @@ func init() {
|
|||
makerBestAskPriceMetrics,
|
||||
bidMarginMetrics,
|
||||
askMarginMetrics,
|
||||
aggregatedSignalMetrics,
|
||||
configNumOfLayersMetrics,
|
||||
configMaxExposureMetrics,
|
||||
configBidMarginMetrics,
|
||||
|
|
87
pkg/strategy/xmaker/signal_boll.go
Normal file
87
pkg/strategy/xmaker/signal_boll.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package xmaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/indicator/v2"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
var bollingerBandSignalMetrics = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "xmaker_bollinger_band_signal",
|
||||
Help: "",
|
||||
}, []string{"symbol"})
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(bollingerBandSignalMetrics)
|
||||
}
|
||||
|
||||
type BollingerBandTrendSignal struct {
|
||||
types.IntervalWindow
|
||||
MinBandWidth float64 `json:"minBandWidth"`
|
||||
MaxBandWidth float64 `json:"maxBandWidth"`
|
||||
|
||||
indicator *indicatorv2.BOLLStream
|
||||
symbol string
|
||||
lastK *types.KLine
|
||||
}
|
||||
|
||||
func (s *BollingerBandTrendSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error {
|
||||
if s.MaxBandWidth == 0.0 {
|
||||
s.MaxBandWidth = 2.0
|
||||
}
|
||||
|
||||
if s.MinBandWidth == 0.0 {
|
||||
s.MinBandWidth = 1.0
|
||||
}
|
||||
|
||||
s.symbol = symbol
|
||||
s.indicator = session.Indicators(symbol).BOLL(s.IntervalWindow, s.MinBandWidth)
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(types.KLineWith(s.symbol, s.IntervalWindow.Interval, func(kline types.KLine) {
|
||||
s.lastK = &kline
|
||||
}))
|
||||
|
||||
bollingerBandSignalMetrics.WithLabelValues(s.symbol).Set(0.0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *BollingerBandTrendSignal) CalculateSignal(ctx context.Context) (float64, error) {
|
||||
if s.lastK == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
closePrice := s.lastK.Close
|
||||
|
||||
// when bid price is lower than the down band, then it's in the downtrend
|
||||
// when ask price is higher than the up band, then it's in the uptrend
|
||||
lastDownBand := fixedpoint.NewFromFloat(s.indicator.DownBand.Last(0))
|
||||
lastUpBand := fixedpoint.NewFromFloat(s.indicator.UpBand.Last(0))
|
||||
|
||||
maxBandWidth := s.indicator.StdDev.Last(0) * s.MaxBandWidth
|
||||
|
||||
signal := 0.0
|
||||
|
||||
// if the price is inside the band, do not vote
|
||||
if closePrice.Compare(lastDownBand) > 0 && closePrice.Compare(lastUpBand) < 0 {
|
||||
signal = 0.0
|
||||
} else if closePrice.Compare(lastDownBand) < 0 {
|
||||
signal = lastDownBand.Sub(closePrice).Float64() / maxBandWidth * -2.0
|
||||
} else if closePrice.Compare(lastUpBand) > 0 {
|
||||
signal = closePrice.Sub(lastUpBand).Float64() / maxBandWidth * 2.0
|
||||
}
|
||||
|
||||
log.Infof("[BollingerBandTrendSignal] %f up/down = %f/%f, close price = %f",
|
||||
signal,
|
||||
lastUpBand.Float64(),
|
||||
lastDownBand.Float64(),
|
||||
closePrice.Float64())
|
||||
|
||||
bollingerBandSignalMetrics.WithLabelValues(s.symbol).Set(signal)
|
||||
return signal, nil
|
||||
}
|
68
pkg/strategy/xmaker/signal_book.go
Normal file
68
pkg/strategy/xmaker/signal_book.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package xmaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
var orderBookSignalMetrics = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "xmaker_order_book_signal",
|
||||
Help: "",
|
||||
}, []string{"symbol"})
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(orderBookSignalMetrics)
|
||||
}
|
||||
|
||||
type OrderBookBestPriceVolumeSignal struct {
|
||||
RatioThreshold fixedpoint.Value `json:"ratioThreshold"`
|
||||
MinVolume fixedpoint.Value `json:"minVolume"`
|
||||
|
||||
symbol string
|
||||
book *types.StreamOrderBook
|
||||
}
|
||||
|
||||
func (s *OrderBookBestPriceVolumeSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error {
|
||||
if s.book == nil {
|
||||
return errors.New("s.book can not be nil")
|
||||
}
|
||||
|
||||
s.symbol = symbol
|
||||
orderBookSignalMetrics.WithLabelValues(s.symbol).Set(0.0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrderBookBestPriceVolumeSignal) CalculateSignal(ctx context.Context) (float64, error) {
|
||||
bid, ask, ok := s.book.BestBidAndAsk()
|
||||
if !ok {
|
||||
return 0.0, nil
|
||||
}
|
||||
|
||||
// TODO: may use scale to define this
|
||||
sumVol := bid.Volume.Add(ask.Volume)
|
||||
bidRatio := bid.Volume.Div(sumVol)
|
||||
askRatio := ask.Volume.Div(sumVol)
|
||||
denominator := fixedpoint.One.Sub(s.RatioThreshold)
|
||||
signal := 0.0
|
||||
if bid.Volume.Compare(s.MinVolume) < 0 && ask.Volume.Compare(s.MinVolume) < 0 {
|
||||
signal = 0.0
|
||||
} else if bidRatio.Compare(s.RatioThreshold) >= 0 {
|
||||
numerator := bidRatio.Sub(s.RatioThreshold)
|
||||
signal = numerator.Div(denominator).Float64()
|
||||
} else if askRatio.Compare(s.RatioThreshold) >= 0 {
|
||||
numerator := askRatio.Sub(s.RatioThreshold)
|
||||
signal = -numerator.Div(denominator).Float64()
|
||||
}
|
||||
|
||||
log.Infof("[OrderBookBestPriceVolumeSignal] %f bid/ask = %f/%f", signal, bid.Volume.Float64(), ask.Volume.Float64())
|
||||
|
||||
orderBookSignalMetrics.WithLabelValues(s.symbol).Set(signal)
|
||||
return signal, nil
|
||||
}
|
111
pkg/strategy/xmaker/signal_trade.go
Normal file
111
pkg/strategy/xmaker/signal_trade.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package xmaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
var tradeVolumeWindowSignalMetrics = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "xmaker_trade_volume_window_signal",
|
||||
Help: "",
|
||||
}, []string{"symbol"})
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(tradeVolumeWindowSignalMetrics)
|
||||
}
|
||||
|
||||
type TradeVolumeWindowSignal struct {
|
||||
Threshold fixedpoint.Value `json:"threshold"`
|
||||
Window types.Duration `json:"window"`
|
||||
|
||||
trades []types.Trade
|
||||
symbol string
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (s *TradeVolumeWindowSignal) handleTrade(trade types.Trade) {
|
||||
s.mu.Lock()
|
||||
s.trades = append(s.trades, trade)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *TradeVolumeWindowSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error {
|
||||
s.symbol = symbol
|
||||
|
||||
if s.Window == 0 {
|
||||
s.Window = types.Duration(time.Minute)
|
||||
}
|
||||
|
||||
if s.Threshold.IsZero() {
|
||||
s.Threshold = fixedpoint.NewFromFloat(0.7)
|
||||
}
|
||||
|
||||
session.MarketDataStream.OnMarketTrade(s.handleTrade)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TradeVolumeWindowSignal) filterTrades(now time.Time) []types.Trade {
|
||||
startTime := now.Add(-time.Duration(s.Window))
|
||||
startIdx := 0
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
for idx, td := range s.trades {
|
||||
// skip trades before the start time
|
||||
if td.Time.Before(startTime) {
|
||||
continue
|
||||
}
|
||||
|
||||
startIdx = idx
|
||||
break
|
||||
}
|
||||
|
||||
trades := s.trades[startIdx:]
|
||||
s.trades = trades
|
||||
return trades
|
||||
}
|
||||
|
||||
func (s *TradeVolumeWindowSignal) aggTradeVolume(trades []types.Trade) (buyVolume, sellVolume float64) {
|
||||
for _, td := range trades {
|
||||
if td.IsBuyer {
|
||||
buyVolume += td.Quantity.Float64()
|
||||
} else {
|
||||
sellVolume += td.Quantity.Float64()
|
||||
}
|
||||
}
|
||||
|
||||
return buyVolume, sellVolume
|
||||
}
|
||||
|
||||
func (s *TradeVolumeWindowSignal) CalculateSignal(_ context.Context) (float64, error) {
|
||||
now := time.Now()
|
||||
trades := s.filterTrades(now)
|
||||
buyVolume, sellVolume := s.aggTradeVolume(trades)
|
||||
totalVolume := buyVolume + sellVolume
|
||||
|
||||
threshold := s.Threshold.Float64()
|
||||
buyRatio := buyVolume / totalVolume
|
||||
sellRatio := sellVolume / totalVolume
|
||||
|
||||
sig := 0.0
|
||||
if buyRatio > threshold {
|
||||
sig = (buyRatio - threshold) / 2.0
|
||||
} else if sellRatio > threshold {
|
||||
sig = -(sellRatio - threshold) / 2.0
|
||||
}
|
||||
|
||||
log.Infof("[TradeVolumeWindowSignal] %f buy/sell = %f/%f", sig, buyVolume, sellVolume)
|
||||
|
||||
tradeVolumeWindowSignalMetrics.WithLabelValues(s.symbol).Set(sig)
|
||||
return sig, nil
|
||||
}
|
55
pkg/strategy/xmaker/signal_trade_test.go
Normal file
55
pkg/strategy/xmaker/signal_trade_test.go
Normal file
|
@ -0,0 +1,55 @@
|
|||
package xmaker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
||||
. "github.com/c9s/bbgo/pkg/testing/testhelper"
|
||||
)
|
||||
|
||||
var tradeId = 0
|
||||
|
||||
func Trade(symbol string, side types.SideType, price, quantity fixedpoint.Value, t time.Time) types.Trade {
|
||||
tradeId++
|
||||
return types.Trade{
|
||||
ID: uint64(tradeId),
|
||||
Symbol: symbol,
|
||||
Side: side,
|
||||
Price: price,
|
||||
IsBuyer: side == types.SideTypeBuy,
|
||||
Quantity: quantity,
|
||||
Time: types.Time(t),
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarketTradeWindowSignal(t *testing.T) {
|
||||
now := time.Now()
|
||||
symbol := "BTCUSDT"
|
||||
sig := &TradeVolumeWindowSignal{
|
||||
symbol: symbol,
|
||||
Threshold: fixedpoint.NewFromFloat(0.65),
|
||||
Window: types.Duration(time.Minute),
|
||||
}
|
||||
|
||||
sig.trades = []types.Trade{
|
||||
Trade(symbol, types.SideTypeBuy, Number(18000.0), Number(1.0), now.Add(-2*time.Minute)),
|
||||
Trade(symbol, types.SideTypeSell, Number(18000.0), Number(0.5), now.Add(-2*time.Second)),
|
||||
Trade(symbol, types.SideTypeBuy, Number(18000.0), Number(1.0), now.Add(-1*time.Second)),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
sigNum, err := sig.CalculateSignal(ctx)
|
||||
if assert.NoError(t, err) {
|
||||
// buy ratio: 1/1.5 = 0.6666666666666666
|
||||
// sell ratio: 0.5/1.5 = 0.3333333333333333
|
||||
assert.InDelta(t, 0.0083333, sigNum, 0.0001)
|
||||
}
|
||||
|
||||
assert.Len(t, sig.trades, 2)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -2,28 +2,89 @@ package xmaker
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
. "github.com/c9s/bbgo/pkg/testing/testhelper"
|
||||
)
|
||||
|
||||
func Test_aggregatePrice(t *testing.T) {
|
||||
bids := types.PriceVolumeSlice{
|
||||
{
|
||||
Price: fixedpoint.NewFromFloat(1000.0),
|
||||
Volume: fixedpoint.NewFromFloat(1.0),
|
||||
},
|
||||
{
|
||||
Price: fixedpoint.NewFromFloat(1200.0),
|
||||
Volume: fixedpoint.NewFromFloat(1.0),
|
||||
},
|
||||
{
|
||||
Price: fixedpoint.NewFromFloat(1400.0),
|
||||
Volume: fixedpoint.NewFromFloat(1.0),
|
||||
},
|
||||
func TestStrategy_getLayerPrice(t *testing.T) {
|
||||
symbol := "BTCUSDT"
|
||||
market := Market(symbol)
|
||||
|
||||
s := &Strategy{
|
||||
UseDepthPrice: true,
|
||||
DepthQuantity: Number(3.0),
|
||||
makerMarket: market,
|
||||
}
|
||||
|
||||
sourceBook := types.NewStreamBook(symbol, types.ExchangeBinance)
|
||||
sourceBook.Load(types.SliceOrderBook{
|
||||
Symbol: symbol,
|
||||
Bids: PriceVolumeSlice(
|
||||
Number(1300.0), Number(1.0),
|
||||
Number(1200.0), Number(2.0),
|
||||
Number(1100.0), Number(3.0),
|
||||
),
|
||||
Asks: PriceVolumeSlice(
|
||||
Number(1301.0), Number(1.0),
|
||||
Number(1400.0), Number(2.0),
|
||||
Number(1500.0), Number(3.0),
|
||||
),
|
||||
Time: time.Time{},
|
||||
LastUpdateId: 1,
|
||||
})
|
||||
|
||||
quote := &Quote{
|
||||
BestBidPrice: Number(1300.0),
|
||||
BestAskPrice: Number(1301.0),
|
||||
BidMargin: Number(0.001),
|
||||
AskMargin: Number(0.001),
|
||||
BidLayerPips: Number(100.0),
|
||||
AskLayerPips: Number(100.0),
|
||||
}
|
||||
|
||||
t.Run("depthPrice bid price at 0", func(t *testing.T) {
|
||||
price := s.getLayerPrice(0, types.SideTypeBuy, sourceBook, quote, s.DepthQuantity)
|
||||
|
||||
// (1300 + 1200*2)/3 * (1 - 0.001)
|
||||
assert.InDelta(t, 1232.10, price.Float64(), 0.01)
|
||||
})
|
||||
|
||||
t.Run("depthPrice bid price at 1", func(t *testing.T) {
|
||||
price := s.getLayerPrice(1, types.SideTypeBuy, sourceBook, quote, s.DepthQuantity)
|
||||
|
||||
// (1300 + 1200*2)/3 * (1 - 0.001) - 100 * 0.01
|
||||
assert.InDelta(t, 1231.10, price.Float64(), 0.01)
|
||||
})
|
||||
|
||||
t.Run("depthPrice ask price at 0", func(t *testing.T) {
|
||||
price := s.getLayerPrice(0, types.SideTypeSell, sourceBook, quote, s.DepthQuantity)
|
||||
|
||||
// (1301 + 1400*2)/3 * (1 + 0.001)
|
||||
assert.InDelta(t, 1368.367, price.Float64(), 0.01)
|
||||
})
|
||||
|
||||
t.Run("depthPrice ask price at 1", func(t *testing.T) {
|
||||
price := s.getLayerPrice(1, types.SideTypeSell, sourceBook, quote, s.DepthQuantity)
|
||||
|
||||
// (1301 + 1400*2)/3 * (1 + 0.001) + 100 * 0.01
|
||||
assert.InDelta(t, 1369.367, price.Float64(), 0.01)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_aggregatePrice(t *testing.T) {
|
||||
bids := PriceVolumeSliceFromText(`
|
||||
1000.0, 1.0
|
||||
1200.0, 1.0
|
||||
1400.0, 1.0
|
||||
`)
|
||||
|
||||
aggregatedPrice1 := aggregatePrice(bids, fixedpoint.NewFromFloat(0.5))
|
||||
assert.Equal(t, fixedpoint.NewFromFloat(1000.0), aggregatedPrice1)
|
||||
|
||||
|
|
43
pkg/testing/testhelper/market.go
Normal file
43
pkg/testing/testhelper/market.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package testhelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
var markets = map[string]types.Market{
|
||||
"BTCUSDT": {
|
||||
Symbol: "BTCUSDT",
|
||||
PricePrecision: 2,
|
||||
VolumePrecision: 8,
|
||||
QuoteCurrency: "USDT",
|
||||
BaseCurrency: "BTC",
|
||||
MinNotional: fixedpoint.MustNewFromString("0.001"),
|
||||
MinAmount: fixedpoint.MustNewFromString("10.0"),
|
||||
MinQuantity: fixedpoint.MustNewFromString("0.001"),
|
||||
TickSize: fixedpoint.MustNewFromString("0.01"),
|
||||
},
|
||||
|
||||
"ETHUSDT": {
|
||||
Symbol: "ETH",
|
||||
PricePrecision: 2,
|
||||
VolumePrecision: 8,
|
||||
QuoteCurrency: "USDT",
|
||||
BaseCurrency: "ETH",
|
||||
MinNotional: fixedpoint.MustNewFromString("0.005"),
|
||||
MinAmount: fixedpoint.MustNewFromString("10.0"),
|
||||
MinQuantity: fixedpoint.MustNewFromString("0.001"),
|
||||
TickSize: fixedpoint.MustNewFromString("0.01"),
|
||||
},
|
||||
}
|
||||
|
||||
func Market(symbol string) types.Market {
|
||||
market, ok := markets[symbol]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("%s market not found, valid markets: %+v", symbol, markets))
|
||||
}
|
||||
|
||||
return market
|
||||
}
|
49
pkg/testing/testhelper/pricevolumeslice.go
Normal file
49
pkg/testing/testhelper/pricevolumeslice.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package testhelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func PriceVolumeSliceFromText(str string) (slice types.PriceVolumeSlice) {
|
||||
lines := strings.Split(str, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
cols := strings.SplitN(line, ",", 2)
|
||||
if len(cols) < 2 {
|
||||
panic(fmt.Errorf("column length should be 2, got %d", len(cols)))
|
||||
}
|
||||
|
||||
price := fixedpoint.MustNewFromString(strings.TrimSpace(cols[0]))
|
||||
volume := fixedpoint.MustNewFromString(strings.TrimSpace(cols[1]))
|
||||
slice = append(slice, types.PriceVolume{
|
||||
Price: price,
|
||||
Volume: volume,
|
||||
})
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
func PriceVolumeSlice(values ...fixedpoint.Value) (slice types.PriceVolumeSlice) {
|
||||
if len(values)%2 != 0 {
|
||||
panic("values should be paired")
|
||||
}
|
||||
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
slice = append(slice, types.PriceVolume{
|
||||
Price: values[i],
|
||||
Volume: values[i+1],
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
return slice
|
||||
}
|
|
@ -2,8 +2,9 @@ package types
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKLineWindow_Tail(t *testing.T) {
|
||||
|
|
|
@ -656,6 +656,12 @@ func (p *Position) AddTrade(td Trade) (profit fixedpoint.Value, netProfit fixedp
|
|||
return fixedpoint.Zero, fixedpoint.Zero, false
|
||||
}
|
||||
|
||||
func (p *Position) UpdateMetrics() {
|
||||
p.Lock()
|
||||
p.updateMetrics()
|
||||
p.Unlock()
|
||||
}
|
||||
|
||||
func (p *Position) updateMetrics() {
|
||||
// update the position metrics only if the position defines the strategy ID
|
||||
if p.StrategyInstanceID == "" || p.Strategy == "" {
|
||||
|
|
|
@ -13,6 +13,13 @@ type PriceVolume struct {
|
|||
Price, Volume fixedpoint.Value
|
||||
}
|
||||
|
||||
func NewPriceVolume(p, v fixedpoint.Value) PriceVolume {
|
||||
return PriceVolume{
|
||||
Price: p,
|
||||
Volume: v,
|
||||
}
|
||||
}
|
||||
|
||||
func (p PriceVolume) InQuote() fixedpoint.Value {
|
||||
return p.Price.Mul(p.Volume)
|
||||
}
|
||||
|
|
|
@ -249,7 +249,7 @@ func (s *StandardStream) Read(ctx context.Context, conn *websocket.Conn, cancel
|
|||
|
||||
default:
|
||||
if err := conn.SetReadDeadline(time.Now().Add(readTimeout)); err != nil {
|
||||
log.WithError(err).Errorf("set read deadline error: %s", err.Error())
|
||||
log.WithError(err).Errorf("unable to set read deadline: %v", err)
|
||||
}
|
||||
|
||||
mt, message, err := conn.ReadMessage()
|
||||
|
@ -300,7 +300,7 @@ func (s *StandardStream) Read(ctx context.Context, conn *websocket.Conn, cancel
|
|||
var e interface{}
|
||||
e, err = s.parser(message)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("websocket event parse error, message: %s", message)
|
||||
log.WithError(err).Errorf("unable to parse the websocket message. err: %v, message: %s", err, message)
|
||||
// emit raw message even if occurs error, because we want anything can be detected
|
||||
s.EmitRawMessage(message)
|
||||
continue
|
||||
|
@ -352,7 +352,7 @@ func (s *StandardStream) ping(
|
|||
}
|
||||
|
||||
if err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(writeTimeout)); err != nil {
|
||||
log.WithError(err).Error("ping error", err)
|
||||
log.WithError(err).Warnf("unable to write ws control message, ping error: %v", err)
|
||||
s.Reconnect()
|
||||
return
|
||||
}
|
||||
|
@ -439,7 +439,7 @@ func (s *StandardStream) reconnector(ctx context.Context) {
|
|||
|
||||
log.Warnf("re-connecting...")
|
||||
if err := s.DialAndConnect(ctx); err != nil {
|
||||
log.WithError(err).Errorf("re-connect error, try to reconnect later")
|
||||
log.WithError(err).Warnf("re-connect error: %v, will reconnect again later...", err)
|
||||
|
||||
// re-emit the re-connect signal if error
|
||||
s.Reconnect()
|
||||
|
|
151
pkg/types/trade_stat.go
Normal file
151
pkg/types/trade_stat.go
Normal file
|
@ -0,0 +1,151 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"gonum.org/v1/gonum/stat"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype/floats"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
const (
|
||||
// DailyToAnnualFactor is the factor to scale daily observations to annual.
|
||||
// Commonly defined as the number of public market trading days in a year.
|
||||
DailyToAnnualFactor = 252 // todo does this apply to crypto at all?
|
||||
)
|
||||
|
||||
// AnnualHistoricVolatility is the historic volatility of the equity curve as annualized std dev.
|
||||
func AnnualHistoricVolatility(data Series) float64 {
|
||||
var sd = Stdev(data, data.Length(), 1)
|
||||
return sd * math.Sqrt(DailyToAnnualFactor)
|
||||
}
|
||||
|
||||
// CAGR is the Compound Annual Growth Rate of the equity curve.
|
||||
func CAGR(initial, final float64, days int) float64 {
|
||||
var (
|
||||
growthRate = (final - initial) / initial
|
||||
x = 1 + growthRate
|
||||
y = 365.0 / float64(days)
|
||||
)
|
||||
return math.Pow(x, y) - 1
|
||||
}
|
||||
|
||||
// measures of risk-adjusted return based on drawdown risk
|
||||
|
||||
// calmar ratio - discounts expected excess return of a portfolio by the
|
||||
// worst expected maximum draw down for that portfolio
|
||||
// CR = E(re)/MD1 = (E(r) - rf) / MD1
|
||||
func CalmarRatio(cagr, maxDrawdown float64) float64 {
|
||||
return cagr / maxDrawdown
|
||||
}
|
||||
|
||||
// Sterling ratio
|
||||
// discounts the expected excess return of a portfolio by the average of the N worst
|
||||
// expected maximum drawdowns for that portfolio
|
||||
// CR = E(re) / (1/N)(sum MDi)
|
||||
func SterlingRatio(cagr, avgDrawdown float64) float64 {
|
||||
return cagr / avgDrawdown
|
||||
}
|
||||
|
||||
// Burke Ratio
|
||||
// similar to sterling, but less sensitive to outliers
|
||||
// discounts the expected excess return of a portfolio by the square root of the average
|
||||
// of the N worst expected maximum drawdowns for that portfolio
|
||||
// BR = E(re) / ((1/N)(sum MD^2))^0.5 ---> smoothing, can take roots, logs etc
|
||||
func BurkeRatio(cagr, avgDrawdownSquared float64) float64 {
|
||||
return cagr / math.Sqrt(avgDrawdownSquared)
|
||||
}
|
||||
|
||||
// KellyCriterion the famous method for trade sizing.
|
||||
func KellyCriterion(profitFactor, winP fixedpoint.Value) fixedpoint.Value {
|
||||
return profitFactor.Mul(winP).Sub(fixedpoint.One.Sub(winP)).Div(profitFactor)
|
||||
}
|
||||
|
||||
// PRR (Pessimistic Return Ratio) is the profit factor with a penalty for a lower number of roundturns.
|
||||
func PRR(profit, loss, winningN, losingN fixedpoint.Value) fixedpoint.Value {
|
||||
var (
|
||||
winF = 1 / math.Sqrt(1+winningN.Float64())
|
||||
loseF = 1 / math.Sqrt(1+losingN.Float64())
|
||||
)
|
||||
return fixedpoint.NewFromFloat((1 - winF) / (1 + loseF) * (1 + profit.Float64()) / (1 + loss.Float64()))
|
||||
}
|
||||
|
||||
// StatN returns the statistically significant number of samples required based on the distribution of a series.
|
||||
// From: https://www.elitetrader.com/et/threads/minimum-number-of-roundturns-required-for-backtesting-results-to-be-trusted.356588/page-2
|
||||
func StatN(xs floats.Slice) (sn, se fixedpoint.Value) {
|
||||
var (
|
||||
sd = Stdev(xs, xs.Length(), 1)
|
||||
m = Mean(xs)
|
||||
statn = math.Pow(4*(sd/m), 2)
|
||||
stdErr = stat.StdErr(sd, float64(xs.Length()))
|
||||
)
|
||||
return fixedpoint.NewFromFloat(statn), fixedpoint.NewFromFloat(stdErr)
|
||||
}
|
||||
|
||||
// OptimalF is a function that returns the 'OptimalF' for a series of trade returns as defined by Ralph Vince.
|
||||
// It is a method for sizing positions to maximize geometric return whilst accounting for biggest trading loss.
|
||||
// See: https://www.investopedia.com/terms/o/optimalf.asp
|
||||
// Param roundturns is the series of profits (-ve amount for losses) for each trade
|
||||
func OptimalF(roundturns floats.Slice) fixedpoint.Value {
|
||||
var (
|
||||
maxTWR, optimalF float64
|
||||
maxLoss = roundturns.Min()
|
||||
)
|
||||
for i := 1.0; i <= 100.0; i++ {
|
||||
twr := 1.0
|
||||
f := i / 100
|
||||
for j := range roundturns {
|
||||
if roundturns[j] == 0 {
|
||||
continue
|
||||
}
|
||||
hpr := 1 + f*(-roundturns[j]/maxLoss)
|
||||
twr *= hpr
|
||||
}
|
||||
if twr > maxTWR {
|
||||
maxTWR = twr
|
||||
optimalF = f
|
||||
}
|
||||
}
|
||||
|
||||
return fixedpoint.NewFromFloat(optimalF)
|
||||
}
|
||||
|
||||
// NN (Not Number) returns y if x is NaN or Inf.
|
||||
func NN(x, y float64) float64 {
|
||||
if math.IsNaN(x) || math.IsInf(x, 0) {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// NNZ (Not Number or Zero) returns y if x is NaN or Inf or Zero.
|
||||
func NNZ(x, y float64) float64 {
|
||||
if NN(x, y) == y || x == 0 {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Compute the drawdown function associated to a portfolio equity curve,
|
||||
// also called the portfolio underwater equity curve.
|
||||
// Portfolio Optimization with Drawdown Constraints, Chekhlov et al., 2000
|
||||
// http://papers.ssrn.com/sol3/papers.cfm?abstract_id=223323
|
||||
func Drawdown(equityCurve floats.Slice) floats.Slice {
|
||||
// Initialize highWaterMark
|
||||
highWaterMark := math.Inf(-1)
|
||||
|
||||
// Create ddVector with the same length as equityCurve
|
||||
ddVector := make([]float64, len(equityCurve))
|
||||
|
||||
// Loop over all the values to compute the drawdown vector
|
||||
for i := 0; i < len(equityCurve); i++ {
|
||||
if equityCurve[i] > highWaterMark {
|
||||
highWaterMark = equityCurve[i]
|
||||
}
|
||||
|
||||
ddVector[i] = (highWaterMark - equityCurve[i]) / highWaterMark
|
||||
}
|
||||
|
||||
return ddVector
|
||||
}
|
56
pkg/types/trade_stat_test.go
Normal file
56
pkg/types/trade_stat_test.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype/floats"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
func TestCAGR(t *testing.T) {
|
||||
giveInitial := 1000.0
|
||||
giveFinal := 2500.0
|
||||
giveDays := 190
|
||||
want := 4.81
|
||||
act := CAGR(giveInitial, giveFinal, giveDays)
|
||||
assert.InDelta(t, want, act, 0.01)
|
||||
}
|
||||
|
||||
func TestKellyCriterion(t *testing.T) {
|
||||
var (
|
||||
giveProfitFactor = fixedpoint.NewFromFloat(1.6)
|
||||
giveWinP = fixedpoint.NewFromFloat(0.7)
|
||||
want = 0.51
|
||||
act = KellyCriterion(giveProfitFactor, giveWinP)
|
||||
)
|
||||
assert.InDelta(t, want, act.Float64(), 0.01)
|
||||
}
|
||||
|
||||
func TestAnnualHistoricVolatility(t *testing.T) {
|
||||
var (
|
||||
give = floats.Slice{0.1, 0.2, -0.15, 0.1, 0.8, -0.3, 0.2}
|
||||
want = 5.51
|
||||
act = AnnualHistoricVolatility(give)
|
||||
)
|
||||
assert.InDelta(t, want, act, 0.01)
|
||||
}
|
||||
|
||||
func TestOptimalF(t *testing.T) {
|
||||
roundturns := floats.Slice{10, 20, 50, -10, 40, -40}
|
||||
f := OptimalF(roundturns)
|
||||
assert.EqualValues(t, 0.45, f.Float64())
|
||||
}
|
||||
|
||||
func TestDrawdown(t *testing.T) {
|
||||
roundturns := floats.Slice{100, 50, 100}
|
||||
expected := []float64{.0, .5, .0}
|
||||
drawdown := Drawdown(roundturns)
|
||||
assert.EqualValues(t, 0.5, drawdown.Max())
|
||||
assert.EqualValues(t, 0.16666666666666666, drawdown.Average())
|
||||
assert.EqualValues(t, 0.08333333333333333, drawdown.AverageSquared())
|
||||
for i, v := range expected {
|
||||
assert.EqualValues(t, v, drawdown[i])
|
||||
}
|
||||
}
|
|
@ -8,45 +8,16 @@ import (
|
|||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/datatype/floats"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
type IntervalProfitCollector struct {
|
||||
Interval Interval `json:"interval"`
|
||||
Profits *floats.Slice `json:"profits"`
|
||||
Timestamp *floats.Slice `json:"timestamp"`
|
||||
tmpTime time.Time `json:"tmpTime"`
|
||||
}
|
||||
|
||||
func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector {
|
||||
return &IntervalProfitCollector{Interval: i, tmpTime: startTime, Profits: &floats.Slice{1.}, Timestamp: &floats.Slice{float64(startTime.Unix())}}
|
||||
}
|
||||
|
||||
// Update the collector by every traded profit
|
||||
func (s *IntervalProfitCollector) Update(profit *Profit) {
|
||||
if s.tmpTime.IsZero() {
|
||||
panic("No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?")
|
||||
} else {
|
||||
duration := s.Interval.Duration()
|
||||
if profit.TradedAt.Before(s.tmpTime.Add(duration)) {
|
||||
(*s.Profits)[len(*s.Profits)-1] *= 1. + profit.NetProfitMargin.Float64()
|
||||
} else {
|
||||
for {
|
||||
s.Profits.Update(1.)
|
||||
s.tmpTime = s.tmpTime.Add(duration)
|
||||
s.Timestamp.Update(float64(s.tmpTime.Unix()))
|
||||
if profit.TradedAt.Before(s.tmpTime.Add(duration)) {
|
||||
(*s.Profits)[len(*s.Profits)-1] *= 1. + profit.NetProfitMargin.Float64()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const (
|
||||
ErrStartTimeNotValid = "No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?"
|
||||
ErrProfitArrEmpty = "profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?"
|
||||
)
|
||||
|
||||
type ProfitReport struct {
|
||||
StartTime time.Time `json:"startTime"`
|
||||
|
@ -62,6 +33,55 @@ func (s ProfitReport) String() string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
type IntervalProfitCollector struct {
|
||||
Interval Interval `json:"interval"`
|
||||
Profits floats.Slice `json:"profits"`
|
||||
TimeInMarket []time.Duration `json:"timeInMarket"`
|
||||
Timestamp floats.Slice `json:"timestamp"`
|
||||
tmpTime time.Time `json:"tmpTime"`
|
||||
}
|
||||
|
||||
func NewIntervalProfitCollector(i Interval, startTime time.Time) *IntervalProfitCollector {
|
||||
return &IntervalProfitCollector{Interval: i, tmpTime: startTime, Profits: floats.Slice{1.}, Timestamp: floats.Slice{float64(startTime.Unix())}}
|
||||
}
|
||||
|
||||
// Update the collector by every traded profit
|
||||
func (s *IntervalProfitCollector) Update(profit *Profit) {
|
||||
if s.tmpTime.IsZero() {
|
||||
panic(ErrStartTimeNotValid)
|
||||
} else {
|
||||
s.TimeInMarket = append(s.TimeInMarket, profit.TradedAt.Sub(profit.PositionOpenedAt))
|
||||
duration := s.Interval.Duration()
|
||||
if profit.TradedAt.Before(s.tmpTime.Add(duration)) {
|
||||
(s.Profits)[len(s.Profits)-1] *= 1. + profit.NetProfitMargin.Float64()
|
||||
} else {
|
||||
for {
|
||||
s.Profits.Update(1.)
|
||||
s.tmpTime = s.tmpTime.Add(duration)
|
||||
s.Timestamp.Update(float64(s.tmpTime.Unix()))
|
||||
if profit.TradedAt.Before(s.tmpTime.Add(duration)) {
|
||||
(s.Profits)[len(s.Profits)-1] *= 1. + profit.NetProfitMargin.Float64()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine average and total time spend in market
|
||||
func (s *IntervalProfitCollector) GetTimeInMarket() (avgHoldSec, totalTimeInMarketSec int64) {
|
||||
if s.Profits == nil {
|
||||
return 0, 0
|
||||
}
|
||||
l := len(s.TimeInMarket)
|
||||
for i := 0; i < l; i++ {
|
||||
d := s.TimeInMarket[i]
|
||||
totalTimeInMarketSec += int64(d / time.Millisecond)
|
||||
}
|
||||
avgHoldSec = totalTimeInMarketSec / int64(l)
|
||||
return
|
||||
}
|
||||
|
||||
// Get all none-profitable intervals
|
||||
func (s *IntervalProfitCollector) GetNonProfitableIntervals() (result []ProfitReport) {
|
||||
if s.Profits == nil {
|
||||
|
@ -93,9 +113,9 @@ func (s *IntervalProfitCollector) GetProfitableIntervals() (result []ProfitRepor
|
|||
// Get number of profitable traded intervals
|
||||
func (s *IntervalProfitCollector) GetNumOfProfitableIntervals() (profit int) {
|
||||
if s.Profits == nil {
|
||||
panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?")
|
||||
panic(ErrProfitArrEmpty)
|
||||
}
|
||||
for _, v := range *s.Profits {
|
||||
for _, v := range s.Profits {
|
||||
if v > 1. {
|
||||
profit += 1
|
||||
}
|
||||
|
@ -107,9 +127,9 @@ func (s *IntervalProfitCollector) GetNumOfProfitableIntervals() (profit int) {
|
|||
// (no trade within the interval or pnl = 0 will be also included here)
|
||||
func (s *IntervalProfitCollector) GetNumOfNonProfitableIntervals() (nonprofit int) {
|
||||
if s.Profits == nil {
|
||||
panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?")
|
||||
panic(ErrProfitArrEmpty)
|
||||
}
|
||||
for _, v := range *s.Profits {
|
||||
for _, v := range s.Profits {
|
||||
if v <= 1. {
|
||||
nonprofit += 1
|
||||
}
|
||||
|
@ -121,10 +141,11 @@ func (s *IntervalProfitCollector) GetNumOfNonProfitableIntervals() (nonprofit in
|
|||
// no smart sharpe ON for the calculated result
|
||||
func (s *IntervalProfitCollector) GetSharpe() float64 {
|
||||
if s.tmpTime.IsZero() {
|
||||
panic("No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?")
|
||||
panic(ErrStartTimeNotValid)
|
||||
}
|
||||
if s.Profits == nil {
|
||||
panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?")
|
||||
panic(ErrStartTimeNotValid)
|
||||
|
||||
}
|
||||
return Sharpe(Sub(s.Profits, 1.), s.Profits.Length(), true, false)
|
||||
}
|
||||
|
@ -133,10 +154,10 @@ func (s *IntervalProfitCollector) GetSharpe() float64 {
|
|||
// No risk-free return rate and smart sortino OFF for the calculated result.
|
||||
func (s *IntervalProfitCollector) GetSortino() float64 {
|
||||
if s.tmpTime.IsZero() {
|
||||
panic("No valid start time. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?")
|
||||
panic(ErrStartTimeNotValid)
|
||||
}
|
||||
if s.Profits == nil {
|
||||
panic("profits array empty. Did you create IntervalProfitCollector instance using NewIntervalProfitCollector?")
|
||||
panic(ErrProfitArrEmpty)
|
||||
}
|
||||
return Sortino(Sub(s.Profits, 1.), 0., s.Profits.Length(), true, false)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
package version
|
||||
|
||||
const Version = "v1.60.0-3a2e4dfd2-dev"
|
||||
const Version = "v1.60.3-26b1fd2ae-dev"
|
||||
|
||||
const VersionGitRef = "3a2e4dfd2"
|
||||
const VersionGitRef = "26b1fd2ae"
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
package version
|
||||
|
||||
const Version = "v1.60.0-3a2e4dfd2"
|
||||
const Version = "v1.60.3-26b1fd2ae"
|
||||
|
||||
const VersionGitRef = "3a2e4dfd2"
|
||||
const VersionGitRef = "26b1fd2ae"
|
||||
|
|
Loading…
Reference in New Issue
Block a user