mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
Merge pull request #209 from c9s/feature/schedule
strategy: add schedule strategy
This commit is contained in:
commit
8b987dad6e
4
go.mod
4
go.mod
|
@ -13,7 +13,7 @@ require (
|
|||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.4.0
|
||||
github.com/go-redis/redis/v8 v8.8.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-test/deep v1.0.6 // indirect
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
|
@ -46,7 +46,7 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/tebeka/strftime v0.1.3 // indirect
|
||||
github.com/ugorji/go v1.2.3 // indirect
|
||||
github.com/valyala/fastjson v1.5.1
|
||||
|
|
25
go.sum
25
go.sum
|
@ -99,6 +99,8 @@ github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7a
|
|||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-redis/redis/v8 v8.4.0 h1:J5NCReIgh3QgUJu398hUncxDExN4gMOHI11NVbVicGQ=
|
||||
github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
|
||||
github.com/go-redis/redis/v8 v8.8.0 h1:fDZP58UN/1RD3DjtTXP/fFZ04TFohSYhjZDkcDe2dnw=
|
||||
github.com/go-redis/redis/v8 v8.8.0/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
|
@ -139,6 +141,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
|
@ -274,10 +277,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||
|
@ -355,6 +360,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tebeka/strftime v0.1.3 h1:5HQXOqWKYRFfNyBMNVc9z5+QzuBtIXy03psIhtdJYto=
|
||||
|
@ -375,6 +382,7 @@ github.com/webview/webview v0.0.0-20210216142346-e0bfdf0e5d90/go.mod h1:rpXAuuHg
|
|||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJzfthRT6usrui8uGmg=
|
||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
github.com/zserge/lorca v0.1.9 h1:vbDdkqdp2/rmeg8GlyCewY2X8Z+b0s7BqWyIQL/gakc=
|
||||
|
@ -384,6 +392,13 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opentelemetry.io/otel v0.14.0 h1:YFBEfjCk9MTjaytCNSUkp9Q8lF7QJezA06T71FbQxLQ=
|
||||
go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw=
|
||||
go.opentelemetry.io/otel v0.19.0 h1:Lenfy7QHRXPZVsw/12CWpxX6d/JkrX8wrx2vO8G80Ng=
|
||||
go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
|
||||
go.opentelemetry.io/otel/metric v0.19.0 h1:dtZ1Ju44gkJkYvo+3qGqVXmf88tc+a42edOywypengg=
|
||||
go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=
|
||||
go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=
|
||||
go.opentelemetry.io/otel/trace v0.19.0 h1:1ucYlenXIDA1OlHVLDZKX0ObXV5RLaq06DtUKz5e5zc=
|
||||
go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
|
@ -394,6 +409,7 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
|
|||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
|
@ -420,6 +436,7 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
|
|||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -439,6 +456,8 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
|
||||
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
|
@ -449,6 +468,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -474,6 +494,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210217090653-ed5674b6da4a h1:m4knbKtdWq+rPB3TE+ApaRzkETZngkKdhYjvTnnRq4s=
|
||||
golang.org/x/sys v0.0.0-20210217090653-ed5674b6da4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
||||
|
@ -510,9 +531,13 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
|
||||
gonum.org/v1/gonum v0.8.1 h1:wGtP3yGpc5mCLOLeTeBdjeui9oZSz5De0eOjMLC/QuQ=
|
||||
gonum.org/v1/gonum v0.8.1/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
|
||||
|
|
|
@ -285,6 +285,10 @@ func (environ *Environment) Init(ctx context.Context) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := session.InitSymbols(ctx, environ) ; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -82,7 +82,7 @@ func (set *StandardIndicatorSet) SMA(iw types.IntervalWindow) *indicator.SMA {
|
|||
return inc
|
||||
}
|
||||
|
||||
// GetEWMA returns the exponential weighed moving average indicator of the given interval and the window size.
|
||||
// EWMA returns the exponential weighed moving average indicator of the given interval and the window size.
|
||||
func (set *StandardIndicatorSet) EWMA(iw types.IntervalWindow) *indicator.EWMA {
|
||||
inc, ok := set.ewma[iw]
|
||||
if !ok {
|
||||
|
@ -193,6 +193,8 @@ func NewExchangeSession(name string, exchange types.Exchange) *ExchangeSession {
|
|||
}
|
||||
}
|
||||
|
||||
// Init initializes the basic data structure and market information by its exchange.
|
||||
// Note that the subscribed symbols are not loaded in this stage.
|
||||
func (session *ExchangeSession) Init(ctx context.Context, environ *Environment) error {
|
||||
if session.IsInitialized {
|
||||
return ErrSessionAlreadyInitialized
|
||||
|
@ -267,18 +269,22 @@ func (session *ExchangeSession) Init(ctx context.Context, environ *Environment)
|
|||
session.lastPrices[kline.Symbol] = kline.Close
|
||||
})
|
||||
|
||||
session.IsInitialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (session *ExchangeSession) InitSymbols(ctx context.Context, environ *Environment) error {
|
||||
if err := session.initUsedSymbols(ctx, environ); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session.IsInitialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// initUsedSymbols uses usedSymbols to initialize the related data structure
|
||||
func (session *ExchangeSession) initUsedSymbols(ctx context.Context, environ *Environment) error {
|
||||
for symbol := range session.usedSymbols {
|
||||
if err := session.InitSymbol(ctx, environ, symbol); err != nil {
|
||||
if err := session.initSymbol(ctx, environ, symbol); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -286,9 +292,9 @@ func (session *ExchangeSession) initUsedSymbols(ctx context.Context, environ *En
|
|||
return nil
|
||||
}
|
||||
|
||||
// InitSymbol loads trades for the symbol, bind stream callbacks, init positions, market data store.
|
||||
// please note, InitSymbol can not be called for the same symbol for twice
|
||||
func (session *ExchangeSession) InitSymbol(ctx context.Context, environ *Environment, symbol string) error {
|
||||
// initSymbol loads trades for the symbol, bind stream callbacks, init positions, market data store.
|
||||
// please note, initSymbol can not be called for the same symbol for twice
|
||||
func (session *ExchangeSession) initSymbol(ctx context.Context, environ *Environment, symbol string) error {
|
||||
if _, ok := session.initializedSymbols[symbol]; ok {
|
||||
// return fmt.Errorf("symbol %s is already initialized", symbol)
|
||||
return nil
|
||||
|
@ -399,7 +405,7 @@ func (session *ExchangeSession) InitSymbol(ctx context.Context, environ *Environ
|
|||
}
|
||||
}
|
||||
|
||||
log.Infof("last price: %f", session.lastPrices[symbol])
|
||||
log.Infof("%s last price: %f", symbol, session.lastPrices[symbol])
|
||||
|
||||
session.initializedSymbols[symbol] = struct{}{}
|
||||
return nil
|
||||
|
|
1
pkg/bbgo/testdata/persistence.yaml
vendored
1
pkg/bbgo/testdata/persistence.yaml
vendored
|
@ -19,6 +19,7 @@ persistence:
|
|||
database: "persistence"
|
||||
|
||||
strategies:
|
||||
- on: max
|
||||
swing:
|
||||
symbolPosition:
|
||||
persistence:
|
||||
|
|
|
@ -66,9 +66,9 @@ type Logger interface {
|
|||
|
||||
type SilentLogger struct{}
|
||||
|
||||
func (logger *SilentLogger) Infof(message string, args ...interface{}) {}
|
||||
func (logger *SilentLogger) Warnf(message string, args ...interface{}) {}
|
||||
func (logger *SilentLogger) Errorf(message string, args ...interface{}) {}
|
||||
func (logger *SilentLogger) Infof(string, ...interface{}) {}
|
||||
func (logger *SilentLogger) Warnf(string, ...interface{}) {}
|
||||
func (logger *SilentLogger) Errorf(string, ...interface{}) {}
|
||||
|
||||
type Trader struct {
|
||||
environment *Environment
|
||||
|
@ -165,6 +165,7 @@ func (trader *Trader) AttachCrossExchangeStrategy(strategy CrossExchangeStrategy
|
|||
return trader
|
||||
}
|
||||
|
||||
// SetRiskControls sets the risk controller
|
||||
// TODO: provide a more DSL way to configure risk controls
|
||||
func (trader *Trader) SetRiskControls(riskControls *RiskControls) {
|
||||
trader.riskControls = riskControls
|
||||
|
@ -211,7 +212,7 @@ func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy Si
|
|||
}
|
||||
|
||||
if symbol, ok := isSymbolBasedStrategy(rs); ok {
|
||||
log.Debugf("found symbol based strategy from %s", rs.Type())
|
||||
log.Infof("found symbol based strategy from %s", rs.Type())
|
||||
if _, ok := hasField(rs, "Market"); ok {
|
||||
if market, ok := session.Market(symbol); ok {
|
||||
// let's make the market object passed by pointer
|
||||
|
@ -223,21 +224,27 @@ func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy Si
|
|||
|
||||
// StandardIndicatorSet
|
||||
if _, ok := hasField(rs, "StandardIndicatorSet"); ok {
|
||||
if indicatorSet, ok := session.StandardIndicatorSet(symbol); ok {
|
||||
indicatorSet, ok := session.StandardIndicatorSet(symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("standardIndicatorSet of symbol %s not found", symbol)
|
||||
}
|
||||
|
||||
if err := injectField(rs, "StandardIndicatorSet", indicatorSet, true); err != nil {
|
||||
return errors.Wrapf(err, "failed to inject StandardIndicatorSet on %T", strategy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := hasField(rs, "MarketDataStore"); ok {
|
||||
if store, ok := session.MarketDataStore(symbol); ok {
|
||||
store, ok := session.MarketDataStore(symbol)
|
||||
if !ok {
|
||||
return fmt.Errorf("marketDataStore of symbol %s not found", symbol)
|
||||
}
|
||||
|
||||
if err := injectField(rs, "MarketDataStore", store, true); err != nil {
|
||||
return errors.Wrapf(err, "failed to inject MarketDataStore on %T", strategy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the strategy has Validate() method, run it and check the error
|
||||
if v, ok := strategy.(Validator); ok {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
_ "github.com/c9s/bbgo/pkg/strategy/grid"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/mirrormaker"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/pricealert"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/schedule"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/support"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/swing"
|
||||
_ "github.com/c9s/bbgo/pkg/strategy/trailingstop"
|
||||
|
|
|
@ -72,19 +72,12 @@ func (e KLineBatchQuery) Query(ctx context.Context, symbol string, interval type
|
|||
errC = make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
limiter := rate.NewLimiter(rate.Every(5*time.Second), 2) // from binance (original 1200, use 1000 for safety)
|
||||
|
||||
defer close(c)
|
||||
defer close(errC)
|
||||
|
||||
for startTime.Before(endTime) {
|
||||
if err := limiter.Wait(ctx); err != nil {
|
||||
logrus.WithError(err).Error("rate limit error")
|
||||
}
|
||||
|
||||
kLines, err := e.QueryKLines(ctx, symbol, interval, types.KLineQueryOptions{
|
||||
StartTime: &startTime,
|
||||
Limit: 1000,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -714,9 +714,9 @@ func (e *Exchange) SubmitOrders(ctx context.Context, orders ...types.SubmitOrder
|
|||
// QueryKLines queries the Kline/candlestick bars for a symbol. Klines are uniquely identified by their open time.
|
||||
func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) {
|
||||
|
||||
var limit = 500
|
||||
var limit = 1000
|
||||
if options.Limit > 0 {
|
||||
// default limit == 500
|
||||
// default limit == 1000
|
||||
limit = options.Limit
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/util"
|
||||
)
|
||||
|
||||
var closedOrderQueryLimiter = rate.NewLimiter(rate.Every(6*time.Second), 1)
|
||||
var tradeQueryLimiter = rate.NewLimiter(rate.Every(4*time.Second), 1)
|
||||
var accountQueryLimiter = rate.NewLimiter(rate.Every(5*time.Second), 1)
|
||||
var marketDataLimiter = rate.NewLimiter(rate.Every(5*time.Second), 1)
|
||||
var closedOrderQueryLimiter = rate.NewLimiter(rate.Every(5*time.Second), 1)
|
||||
var tradeQueryLimiter = rate.NewLimiter(rate.Every(3*time.Second), 1)
|
||||
var accountQueryLimiter = rate.NewLimiter(rate.Every(3*time.Second), 1)
|
||||
var marketDataLimiter = rate.NewLimiter(rate.Every(2*time.Second), 10)
|
||||
|
||||
var log = logrus.WithField("exchange", "max")
|
||||
|
||||
|
|
|
@ -66,6 +66,10 @@ func (v Value) Div(v2 Value) Value {
|
|||
return NewFromFloat(v.Float64() / v2.Float64())
|
||||
}
|
||||
|
||||
func (v Value) DivFloat64(v2 float64) Value {
|
||||
return NewFromFloat(v.Float64() / v2)
|
||||
}
|
||||
|
||||
func (v Value) Floor() Value {
|
||||
return NewFromFloat(math.Floor(v.Float64()))
|
||||
}
|
||||
|
|
|
@ -11,13 +11,12 @@ type Store interface {
|
|||
}
|
||||
|
||||
type RedisPersistenceConfig struct {
|
||||
Host string `json:"host" env:"REDIS_HOST"`
|
||||
Port string `json:"port" env:"REDIS_PORT"`
|
||||
Password string `json:"password" env:"REDIS_PASSWORD"`
|
||||
DB int `json:"db" env:"REDIS_DB"`
|
||||
Host string `yaml:"host" json:"host" env:"REDIS_HOST"`
|
||||
Port string `yaml:"port" json:"port" env:"REDIS_PORT"`
|
||||
Password string `yaml:"password,omitempty" json:"password,omitempty" env:"REDIS_PASSWORD"`
|
||||
DB int `yaml:"db" json:"db" env:"REDIS_DB"`
|
||||
}
|
||||
|
||||
type JsonPersistenceConfig struct {
|
||||
Directory string `json:"directory"`
|
||||
Directory string `yaml:"directory" json:"directory"`
|
||||
}
|
||||
|
||||
|
|
229
pkg/strategy/schedule/strategy.go
Normal file
229
pkg/strategy/schedule/strategy.go
Normal file
|
@ -0,0 +1,229 @@
|
|||
package schedule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
const ID = "schedule"
|
||||
|
||||
func init() {
|
||||
bbgo.RegisterStrategy(ID, &Strategy{})
|
||||
}
|
||||
|
||||
// Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data.
|
||||
type Float64Indicator interface {
|
||||
Last() float64
|
||||
}
|
||||
|
||||
type MovingAverageSettings struct {
|
||||
Type string `json:"type"`
|
||||
Interval types.Interval `json:"interval"`
|
||||
Window int `json:"window"`
|
||||
|
||||
Side *types.SideType `json:"side"`
|
||||
Quantity *fixedpoint.Value `json:"quantity"`
|
||||
Amount *fixedpoint.Value `json:"amount"`
|
||||
}
|
||||
|
||||
func (settings MovingAverageSettings) IntervalWindow() types.IntervalWindow {
|
||||
var window = 99
|
||||
if settings.Window > 0 {
|
||||
window = settings.Window
|
||||
}
|
||||
|
||||
return types.IntervalWindow{
|
||||
Interval: settings.Interval,
|
||||
Window: window,
|
||||
}
|
||||
}
|
||||
|
||||
func (settings *MovingAverageSettings) Indicator(indicatorSet *bbgo.StandardIndicatorSet) (inc Float64Indicator, err error) {
|
||||
var iw = settings.IntervalWindow()
|
||||
|
||||
switch settings.Type {
|
||||
case "SMA":
|
||||
inc = indicatorSet.SMA(iw)
|
||||
|
||||
case "EWMA", "EMA":
|
||||
inc = indicatorSet.EWMA(iw)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported moving average type: %s", settings.Type)
|
||||
}
|
||||
|
||||
return inc, nil
|
||||
}
|
||||
|
||||
type Strategy struct {
|
||||
Market types.Market
|
||||
|
||||
Notifiability *bbgo.Notifiability
|
||||
|
||||
// StandardIndicatorSet contains the standard indicators of a market (symbol)
|
||||
// This field will be injected automatically since we defined the Symbol field.
|
||||
*bbgo.StandardIndicatorSet
|
||||
|
||||
// Interval is the period that you want to submit order
|
||||
Interval types.Interval `json:"interval"`
|
||||
|
||||
// Symbol is the symbol of the market
|
||||
Symbol string `json:"symbol"`
|
||||
|
||||
// Side is the order side type, which can be buy or sell
|
||||
Side types.SideType `json:"side"`
|
||||
|
||||
// Quantity is the quantity of the submit order
|
||||
Quantity fixedpoint.Value `json:"quantity,omitempty"`
|
||||
|
||||
Amount fixedpoint.Value `json:"amount,omitempty"`
|
||||
|
||||
BelowMovingAverage *MovingAverageSettings `json:"belowMovingAverage,omitempty"`
|
||||
|
||||
AboveMovingAverage *MovingAverageSettings `json:"aboveMovingAverage,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Strategy) ID() string {
|
||||
return ID
|
||||
}
|
||||
|
||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval.String()})
|
||||
}
|
||||
|
||||
func (s *Strategy) Validate() error {
|
||||
if s.Quantity == 0 && s.Amount == 0 {
|
||||
return errors.New("either quantity or amount can not be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||
if s.StandardIndicatorSet == nil {
|
||||
return errors.New("StandardIndicatorSet can not be nil, injection failed?")
|
||||
}
|
||||
|
||||
var belowMA Float64Indicator
|
||||
var aboveMA Float64Indicator
|
||||
var err error
|
||||
if s.BelowMovingAverage != nil {
|
||||
belowMA, err = s.BelowMovingAverage.Indicator(s.StandardIndicatorSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if s.AboveMovingAverage != nil {
|
||||
aboveMA, err = s.AboveMovingAverage.Indicator(s.StandardIndicatorSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
session.Stream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != s.Symbol {
|
||||
return
|
||||
}
|
||||
|
||||
closePrice := fixedpoint.NewFromFloat(kline.Close)
|
||||
quantity := s.Quantity
|
||||
amount := s.Amount
|
||||
|
||||
side := s.Side
|
||||
|
||||
if s.BelowMovingAverage != nil || s.AboveMovingAverage != nil {
|
||||
|
||||
match := false
|
||||
// if any of the conditions satisfies then we execute order
|
||||
if belowMA != nil && closePrice.Float64() < belowMA.Last() {
|
||||
match = true
|
||||
if s.BelowMovingAverage != nil {
|
||||
if s.BelowMovingAverage.Side != nil {
|
||||
side = *s.BelowMovingAverage.Side
|
||||
}
|
||||
|
||||
// override the default quantity or amount
|
||||
if s.BelowMovingAverage.Quantity != nil {
|
||||
quantity = *s.BelowMovingAverage.Quantity
|
||||
} else if s.BelowMovingAverage.Amount != nil {
|
||||
amount = *s.BelowMovingAverage.Amount
|
||||
}
|
||||
|
||||
}
|
||||
} else if aboveMA != nil && closePrice.Float64() > aboveMA.Last() {
|
||||
match = true
|
||||
if s.AboveMovingAverage != nil {
|
||||
if s.AboveMovingAverage.Side != nil {
|
||||
side = *s.AboveMovingAverage.Side
|
||||
}
|
||||
|
||||
// override the default quantity or amount
|
||||
if s.AboveMovingAverage.Quantity != nil {
|
||||
quantity = *s.AboveMovingAverage.Quantity
|
||||
} else if s.AboveMovingAverage.Amount != nil {
|
||||
amount = *s.AboveMovingAverage.Amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !match {
|
||||
s.Notifiability.Notify("skip, the closed price is below or above moving average")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// convert amount to quantity if amount is given
|
||||
if amount > 0 {
|
||||
quantity = amount.Div(closePrice)
|
||||
}
|
||||
|
||||
// calculate quote quantity for balance checking
|
||||
quoteQuantity := quantity.Mul(closePrice)
|
||||
|
||||
// execute orders
|
||||
switch side {
|
||||
case types.SideTypeBuy:
|
||||
quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if quoteBalance.Available < quoteQuantity {
|
||||
s.Notifiability.Notify("Quote balance %s is not enough: %f < %f", s.Market.QuoteCurrency, quoteBalance.Available.Float64(), quoteQuantity.Float64())
|
||||
return
|
||||
}
|
||||
|
||||
case types.SideTypeSell:
|
||||
baseBalance, ok := session.Account.Balance(s.Market.BaseCurrency)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if baseBalance.Available < quantity {
|
||||
s.Notifiability.Notify("Base balance %s is not enough: %f < %f", s.Market.QuoteCurrency, baseBalance.Available.Float64(), quantity.Float64())
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
s.Notifiability.Notify("Submitting scheduled order %s quantity %f", s.Symbol, quantity.Floor())
|
||||
_, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Side: side,
|
||||
Type: types.OrderTypeMarket,
|
||||
Quantity: quantity.Float64(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error("submit order error")
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
|
@ -13,7 +13,7 @@ import (
|
|||
|
||||
const ID = "swing"
|
||||
|
||||
// The indicators (SMA and EWMA) that we want to use are returning float64 data.
|
||||
// Float64Indicator is the indicators (SMA and EWMA) that we want to use are returning float64 data.
|
||||
type Float64Indicator interface {
|
||||
Last() float64
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package types
|
|||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SideType define side type of order
|
||||
|
@ -17,6 +19,8 @@ const (
|
|||
SideTypeBoth = SideType("BOTH")
|
||||
)
|
||||
|
||||
var ErrInvalidSideType = errors.New("invalid side type")
|
||||
|
||||
func (side *SideType) UnmarshalJSON(data []byte) (err error) {
|
||||
var s string
|
||||
err = json.Unmarshal(data, &s)
|
||||
|
@ -34,6 +38,10 @@ func (side *SideType) UnmarshalJSON(data []byte) (err error) {
|
|||
case "both":
|
||||
*side = SideTypeBoth
|
||||
|
||||
default:
|
||||
err = ErrInvalidSideType
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
|
|
Loading…
Reference in New Issue
Block a user