From 499f34179b60989e80336b31bbce2afa5465ec32 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 26 Apr 2021 17:05:15 +0800 Subject: [PATCH 1/9] upgrade go module --- go.mod | 4 ++-- go.sum | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 8e4358027..28998a056 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 5c686bb04..905d78bc4 100644 --- a/go.sum +++ b/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,7 @@ 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/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 +381,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 +391,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 +408,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 +435,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 +455,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 +467,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 +493,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 +530,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= From 8fea2022e53872894f14250b999d76d8a47da69f Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 17:46:08 +0800 Subject: [PATCH 2/9] adjust rate limit for backtest data syncing --- pkg/exchange/batch/batch.go | 7 ------- pkg/exchange/binance/exchange.go | 4 ++-- pkg/exchange/max/exchange.go | 8 ++++---- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/pkg/exchange/batch/batch.go b/pkg/exchange/batch/batch.go index de9216130..287c8044e 100644 --- a/pkg/exchange/batch/batch.go +++ b/pkg/exchange/batch/batch.go @@ -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 { diff --git a/pkg/exchange/binance/exchange.go b/pkg/exchange/binance/exchange.go index 353f9e280..4c260bba4 100644 --- a/pkg/exchange/binance/exchange.go +++ b/pkg/exchange/binance/exchange.go @@ -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 } diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 6611f7b51..cdf928101 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -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") From 20d673f76967df333c5adf9b2bf8511cc7f9c7ed Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 18:03:41 +0800 Subject: [PATCH 3/9] add schedule strategy --- pkg/cmd/builtin.go | 1 + pkg/strategy/schedule/strategy.go | 97 +++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 pkg/strategy/schedule/strategy.go diff --git a/pkg/cmd/builtin.go b/pkg/cmd/builtin.go index 4f2e8114a..3087b9279 100644 --- a/pkg/cmd/builtin.go +++ b/pkg/cmd/builtin.go @@ -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" diff --git a/pkg/strategy/schedule/strategy.go b/pkg/strategy/schedule/strategy.go new file mode 100644 index 000000000..e7be18182 --- /dev/null +++ b/pkg/strategy/schedule/strategy.go @@ -0,0 +1,97 @@ +package schedule + +import ( + "context" + + "github.com/c9s/bbgo/pkg/fixedpoint" + 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{}) +} + +type Strategy struct { + Market types.Market + Notifiability *bbgo.Notifiability + + // 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"` + +} + +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) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + session.Stream.OnKLineClosed(func(kline types.KLine) { + if kline.Symbol != s.Symbol { + return + } + + + closePrice := kline.Close + quantity := s.Quantity.Float64() + if s.Amount > 0 { + quantity = s.Amount.Float64() / closePrice + } + quoteQuantity := quantity * closePrice + + switch s.Side { + case types.SideTypeBuy: + quoteBalance, ok := session.Account.Balance(s.Market.QuoteCurrency) + if !ok { + return + } + if quoteBalance.Available.Float64() < quoteQuantity { + s.Notifiability.Notify("quote balance %s is not enough: %f < %f", s.Market.QuoteCurrency, quoteBalance.Available.Float64(), quoteQuantity) + return + } + + case types.SideTypeSell: + baseBalance, ok := session.Account.Balance(s.Market.BaseCurrency) + if !ok { + return + } + if baseBalance.Available.Float64() < quantity { + s.Notifiability.Notify("base balance %s is not enough: %f < %f", s.Market.QuoteCurrency, baseBalance.Available.Float64(), quantity) + return + } + + } + + _, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ + Symbol: s.Symbol, + Side: s.Side, + Type: types.OrderTypeMarket, + Quantity: quantity, + }) + + if err != nil { + log.WithError(err).Error("submit order error") + } + }) + + return nil +} From e29d9af9c817bc6df92d15e54596281796ebf140 Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 18:16:34 +0800 Subject: [PATCH 4/9] fix persistence config unmarshalling --- go.sum | 1 + pkg/bbgo/testdata/persistence.yaml | 1 + pkg/service/persistence.go | 11 +++++------ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.sum b/go.sum index 905d78bc4..b35e75d8c 100644 --- a/go.sum +++ b/go.sum @@ -360,6 +360,7 @@ 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= diff --git a/pkg/bbgo/testdata/persistence.yaml b/pkg/bbgo/testdata/persistence.yaml index 457303b33..f418b55f9 100644 --- a/pkg/bbgo/testdata/persistence.yaml +++ b/pkg/bbgo/testdata/persistence.yaml @@ -19,6 +19,7 @@ persistence: database: "persistence" strategies: +- on: max swing: symbolPosition: persistence: diff --git a/pkg/service/persistence.go b/pkg/service/persistence.go index ef55ca2ef..e8cb47c35 100644 --- a/pkg/service/persistence.go +++ b/pkg/service/persistence.go @@ -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"` } - From 822a0109322339c611480173cb6ef72e866ea550 Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 20:58:32 +0800 Subject: [PATCH 5/9] add moving average configuration to the schedule strategy --- pkg/bbgo/session.go | 4 +- pkg/fixedpoint/convert.go | 4 + pkg/strategy/schedule/strategy.go | 156 +++++++++++++++++++++++++++--- pkg/strategy/swing/strategy.go | 2 +- pkg/types/side.go | 8 ++ 5 files changed, 159 insertions(+), 15 deletions(-) diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 8df5b2a9d..4fbaffad7 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -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 { @@ -399,7 +399,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 diff --git a/pkg/fixedpoint/convert.go b/pkg/fixedpoint/convert.go index 525186e9f..e349088ea 100644 --- a/pkg/fixedpoint/convert.go +++ b/pkg/fixedpoint/convert.go @@ -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())) } diff --git a/pkg/strategy/schedule/strategy.go b/pkg/strategy/schedule/strategy.go index e7be18182..7bd048592 100644 --- a/pkg/strategy/schedule/strategy.go +++ b/pkg/strategy/schedule/strategy.go @@ -2,8 +2,10 @@ 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" @@ -16,10 +18,59 @@ 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"` @@ -34,6 +85,9 @@ type Strategy struct { Amount fixedpoint.Value `json:"amount,omitempty"` + BelowMovingAverage *MovingAverageSettings `json:"belowMovingAverage,omitempty"` + + AboveMovingAverage *MovingAverageSettings `json:"aboveMovingAverage,omitempty"` } func (s *Strategy) ID() string { @@ -44,28 +98,105 @@ 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 - closePrice := kline.Close - quantity := s.Quantity.Float64() - if s.Amount > 0 { - quantity = s.Amount.Float64() / closePrice + 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 + } } - quoteQuantity := quantity * closePrice - switch s.Side { + // 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.Float64() < quoteQuantity { - s.Notifiability.Notify("quote balance %s is not enough: %f < %f", s.Market.QuoteCurrency, quoteBalance.Available.Float64(), quoteQuantity) + if quoteBalance.Available < quoteQuantity { + s.Notifiability.Notify("Quote balance %s is not enough: %f < %f", s.Market.QuoteCurrency, quoteBalance.Available.Float64(), quoteQuantity.Float64()) return } @@ -74,18 +205,19 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se if !ok { return } - if baseBalance.Available.Float64() < quantity { - s.Notifiability.Notify("base balance %s is not enough: %f < %f", s.Market.QuoteCurrency, baseBalance.Available.Float64(), quantity) + 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: s.Side, + Side: side, Type: types.OrderTypeMarket, - Quantity: quantity, + Quantity: quantity.Float64(), }) if err != nil { diff --git a/pkg/strategy/swing/strategy.go b/pkg/strategy/swing/strategy.go index 1a999ddc9..b9bcadc54 100644 --- a/pkg/strategy/swing/strategy.go +++ b/pkg/strategy/swing/strategy.go @@ -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 } diff --git a/pkg/types/side.go b/pkg/types/side.go index dcf2e4983..cc1ac7c30 100644 --- a/pkg/types/side.go +++ b/pkg/types/side.go @@ -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 From 5ec05668886f5a4d357f6fceb300d8e8957c531e Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 23:46:16 +0800 Subject: [PATCH 6/9] add more injection checks --- pkg/bbgo/trader.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index d2665663d..8635d34e8 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -211,7 +211,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,18 +223,24 @@ func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy Si // StandardIndicatorSet if _, ok := hasField(rs, "StandardIndicatorSet"); ok { - if indicatorSet, ok := session.StandardIndicatorSet(symbol); ok { - if err := injectField(rs, "StandardIndicatorSet", indicatorSet, true); err != nil { - return errors.Wrapf(err, "failed to inject StandardIndicatorSet on %T", strategy) - } + 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 { - if err := injectField(rs, "MarketDataStore", store, true); err != nil { - return errors.Wrapf(err, "failed to inject MarketDataStore on %T", strategy) - } + 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) } } } From 2ef13293e93da4bfdaad11b4ec11950ed780a1aa Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 23:47:57 +0800 Subject: [PATCH 7/9] fix IDE warnings --- pkg/bbgo/trader.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index 8635d34e8..ef1816f29 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -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 From 2230c56e566490dc55a57142434ace874fe7a2ea Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 23:48:53 +0800 Subject: [PATCH 8/9] fix comment warning --- pkg/bbgo/trader.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index ef1816f29..2f2f2a271 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -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 From 50db944053c6e8704841540ed94874085e1c4d2d Mon Sep 17 00:00:00 2001 From: c9s Date: Sun, 2 May 2021 23:58:34 +0800 Subject: [PATCH 9/9] fix initSymbol stages --- pkg/bbgo/environment.go | 4 ++++ pkg/bbgo/session.go | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pkg/bbgo/environment.go b/pkg/bbgo/environment.go index ef763acd9..a9bfd0b48 100644 --- a/pkg/bbgo/environment.go +++ b/pkg/bbgo/environment.go @@ -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 diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 4fbaffad7..e9d42fb48 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -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