diff --git a/go.mod b/go.mod index 2e8caff..62baa2e 100644 --- a/go.mod +++ b/go.mod @@ -9,21 +9,21 @@ require ( github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.32.0 github.com/samber/lo v1.39.0 - golang.org/x/text v0.16.0 + golang.org/x/text v0.21.0 gopkg.in/telebot.v3 v3.2.1 ) require ( github.com/bitly/go-simplejson v0.5.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/jpillora/backoff v1.0.0 // indirect 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 6843da1..e104f0b 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/adshao/go-binance/v2 v2.5.0 h1:mk8ylSjIzDYVBF9Wf2KXu6GWD/Ws4LLzD9q2R2mqZB0= -github.com/adshao/go-binance/v2 v2.5.0/go.mod h1:41Up2dG4NfMXpCldrDPETEtiOq+pHoGsFZ73xGgaumo= +github.com/adshao/go-binance/v2 v2.8.5 h1:2i8uVFrt1HbZPggnfdL1A1g/PS9MeD1FnoBoIXNhbow= +github.com/adshao/go-binance/v2 v2.8.5/go.mod h1:XkkuecSyJKPolaCGf/q4ovJYB3t0P+7RUYTbGr+LMGM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -135,8 +135,8 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= -github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -155,6 +155,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -212,6 +213,8 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -220,8 +223,8 @@ github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/Oth github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= @@ -258,12 +261,12 @@ github.com/jedib0t/go-pretty/v6 v6.5.5 h1:PpIU8lOjxvVYGGKule0QxxJfNysUSbC9lggQU2 github.com/jedib0t/go-pretty/v6 v6.5.5/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -316,11 +319,9 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -368,6 +369,8 @@ github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5A github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -509,8 +512,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -624,8 +627,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -637,8 +640,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/internal/data/imarket.go b/internal/data/imarket.go index d0fa699..ba7a6d4 100644 --- a/internal/data/imarket.go +++ b/internal/data/imarket.go @@ -1,9 +1,15 @@ package data +import "me.thuanle/bbot/internal/data/market" + type IMarket interface { GetFuturePrice(symbol string) (float64, float64, int64, bool) GetAllFundRate() (map[string]float64, map[string]int64) GetSpotPrice(symbol string) (float64, bool) GetMarginInterestRates() map[string]float64 + + // Alpha token methods + IsAlphaToken(symbol string) bool + GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool) } diff --git a/internal/data/market/alpha_tokens.go b/internal/data/market/alpha_tokens.go new file mode 100644 index 0000000..8bdb6ff --- /dev/null +++ b/internal/data/market/alpha_tokens.go @@ -0,0 +1,179 @@ +package market + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/rs/zerolog/log" +) + +// AlphaTokenInfo represents the response from Binance Alpha token API +type AlphaTokenInfo struct { + TokenID string `json:"tokenId"` + ChainID string `json:"chainId"` + ChainIconURL string `json:"chainIconUrl"` + ChainName string `json:"chainName"` + ContractAddress string `json:"contractAddress"` + Name string `json:"name"` + Symbol string `json:"symbol"` + IconURL string `json:"iconUrl"` + Price string `json:"price"` + PercentChange24h string `json:"percentChange24h"` + Volume24h string `json:"volume24h"` + MarketCap string `json:"marketCap"` + FDV string `json:"fdv"` + Liquidity string `json:"liquidity"` + TotalSupply string `json:"totalSupply"` + CirculatingSupply string `json:"circulatingSupply"` + Holders string `json:"holders"` + Decimals int `json:"decimals"` + ListingCex bool `json:"listingCex"` + HotTag bool `json:"hotTag"` + CexCoinName string `json:"cexCoinName"` + CanTransfer bool `json:"canTransfer"` + Denomination int `json:"denomination"` + Offline bool `json:"offline"` + TradeDecimal int `json:"tradeDecimal"` + AlphaID string `json:"alphaId"` + Offsell bool `json:"offsell"` + PriceHigh24h string `json:"priceHigh24h"` + PriceLow24h string `json:"priceLow24h"` + Count24h string `json:"count24h"` + OnlineTge bool `json:"onlineTge"` + OnlineAirdrop bool `json:"onlineAirdrop"` + Score int `json:"score"` + CexOffDisplay bool `json:"cexOffDisplay"` + StockState bool `json:"stockState"` + ListingTime int64 `json:"listingTime"` + MulPoint int `json:"mulPoint"` + BnExclusiveState bool `json:"bnExclusiveState"` +} + +// AlphaTokenResponse represents the API response structure +type AlphaTokenResponse struct { + Code string `json:"code"` + Message *string `json:"message"` + MessageDetail *string `json:"messageDetail"` + Data []AlphaTokenInfo `json:"data"` +} + +// GetPrice returns the price as float64 +func (a *AlphaTokenInfo) GetPrice() float64 { + price, err := strconv.ParseFloat(a.Price, 64) + if err != nil { + log.Error().Err(err).Str("symbol", a.Symbol).Msg("Failed to parse Alpha token price") + return 0 + } + return price +} + +// GetPercentChange24h returns the 24h percentage change as float64 +func (a *AlphaTokenInfo) GetPercentChange24h() float64 { + change, err := strconv.ParseFloat(a.PercentChange24h, 64) + if err != nil { + log.Error().Err(err).Str("symbol", a.Symbol).Msg("Failed to parse Alpha token 24h change") + return 0 + } + return change +} + +// fetchAlphaTokens fetches Alpha tokens from Binance API +func (ms *MarketData) fetchAlphaTokens() ([]AlphaTokenInfo, error) { + const alphaTokenURL = "https://www.binance.com/bapi/defi/v1/public/wallet-direct/buw/wallet/cex/alpha/all/token/list" + + client := &http.Client{ + Timeout: 10 * time.Second, + } + + resp, err := client.Get(alphaTokenURL) + if err != nil { + return nil, fmt.Errorf("failed to fetch Alpha tokens: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Alpha token API returned status: %d", resp.StatusCode) + } + + var tokenResponse AlphaTokenResponse + if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil { + return nil, fmt.Errorf("failed to decode Alpha token response: %w", err) + } + + if tokenResponse.Code != "000000" { + return nil, fmt.Errorf("Alpha token API returned error code: %s", tokenResponse.Code) + } + + return tokenResponse.Data, nil +} + +// refreshAlphaTokenCache refreshes the Alpha token cache +func (ms *MarketData) refreshAlphaTokenCache() { + ms.alphaCacheMutex.Lock() + defer ms.alphaCacheMutex.Unlock() + + log.Info().Msg("Refreshing Alpha token cache") + + tokens, err := ms.fetchAlphaTokens() + if err != nil { + log.Error().Err(err).Msg("Failed to refresh Alpha token cache") + return + } + + // Clear existing cache + ms.alphaTokens = make(map[string]AlphaTokenInfo) + + // Populate cache with new data + for _, token := range tokens { + symbol := token.Symbol // Already uppercase from API + ms.alphaTokens[symbol] = token + } + + // Update spot prices for Alpha tokens (separate lock to avoid holding both mutexes) + ms.mu.Lock() + for _, token := range tokens { + symbol := token.Symbol + if price := token.GetPrice(); price > 0 { + ms.spotPrice[symbol] = price + } + } + ms.mu.Unlock() + + ms.lastAlphaCacheUpdate = time.Now() + log.Info().Int("count", len(tokens)).Msg("Alpha token cache refreshed successfully") +} + +// GetAlphaToken returns Alpha token info by symbol +func (ms *MarketData) GetAlphaToken(symbol string) (AlphaTokenInfo, bool) { + ms.alphaCacheMutex.RLock() + defer ms.alphaCacheMutex.RUnlock() + + token, exists := ms.alphaTokens[symbol] + return token, exists +} + +// IsAlphaToken checks if a symbol is an Alpha token +func (ms *MarketData) IsAlphaToken(symbol string) bool { + _, exists := ms.GetAlphaToken(symbol) + return exists +} + +// shouldRefreshAlphaCache checks if the Alpha cache should be refreshed +func (ms *MarketData) shouldRefreshAlphaCache() bool { + // Refresh if cache is empty (first time) or if it's older than 30 minutes + if len(ms.alphaTokens) == 0 { + return true + } + + return time.Since(ms.lastAlphaCacheUpdate) > 30*time.Minute +} + +// ensureAlphaCacheLoaded ensures Alpha cache is loaded, refreshes if needed +func (ms *MarketData) ensureAlphaCacheLoaded() { + if ms.shouldRefreshAlphaCache() { + ms.refreshAlphaTokenCache() + } +} \ No newline at end of file diff --git a/internal/data/market/future_price.go b/internal/data/market/future_price.go index 67aec61..872a80e 100644 --- a/internal/data/market/future_price.go +++ b/internal/data/market/future_price.go @@ -7,6 +7,8 @@ import ( ) func (ms *MarketData) GetFuturePrice(symbol string) (float64, float64, int64, bool) { + ms.mu.RLock() + defer ms.mu.RUnlock() p, ok := ms.futureMarkPrice[symbol] if !ok { return 0, 0, 0, false @@ -23,6 +25,8 @@ func (ms *MarketData) StartFutureWsMarkPrice() error { } func (ms *MarketData) futureWsMarkPriceHandler(event futures.WsAllMarkPriceEvent) { + ms.mu.Lock() + defer ms.mu.Unlock() for _, priceEvent := range event { price, err := strconv.ParseFloat(priceEvent.MarkPrice, 64) if err != nil { @@ -46,5 +50,15 @@ func (ms *MarketData) futureWsErrHandler(err error) { } func (ms *MarketData) GetAllFundRate() (map[string]float64, map[string]int64) { - return ms.futureFundingRate, ms.futureNextFundingTime + ms.mu.RLock() + defer ms.mu.RUnlock() + rates := make(map[string]float64, len(ms.futureFundingRate)) + for k, v := range ms.futureFundingRate { + rates[k] = v + } + times := make(map[string]int64, len(ms.futureNextFundingTime)) + for k, v := range ms.futureNextFundingTime { + times[k] = v + } + return rates, times } diff --git a/internal/data/market/main.go b/internal/data/market/main.go index 19bec1a..5e1e9c3 100644 --- a/internal/data/market/main.go +++ b/internal/data/market/main.go @@ -1,13 +1,24 @@ package market -import "github.com/rs/zerolog/log" +import ( + "sync" + "time" + + "github.com/rs/zerolog/log" +) type MarketData struct { + mu sync.RWMutex futureMarkPrice map[string]float64 futureFundingRate map[string]float64 futureNextFundingTime map[string]int64 spotPrice map[string]float64 + + // Alpha token cache + alphaTokens map[string]AlphaTokenInfo + alphaCacheMutex sync.RWMutex + lastAlphaCacheUpdate time.Time } func NewMarketData() *MarketData { @@ -17,9 +28,14 @@ func NewMarketData() *MarketData { futureFundingRate: make(map[string]float64), futureNextFundingTime: make(map[string]int64), - spotPrice: make(map[string]float64), + spotPrice: make(map[string]float64), + alphaTokens: make(map[string]AlphaTokenInfo), } _ = ms.StartFutureWsMarkPrice() _ = ms.StartSpotWsMarkPrice() + + // Initialize Alpha token cache + go ms.refreshAlphaTokenCache() + return ms } diff --git a/internal/data/market/spot_price.go b/internal/data/market/spot_price.go index cf51795..a56a7db 100644 --- a/internal/data/market/spot_price.go +++ b/internal/data/market/spot_price.go @@ -1,14 +1,35 @@ package market import ( + "strconv" + "github.com/adshao/go-binance/v2" "github.com/rs/zerolog/log" - "strconv" ) func (ms *MarketData) GetSpotPrice(symbol string) (float64, bool) { + ms.mu.RLock() p, ok := ms.spotPrice[symbol] - return p, ok + ms.mu.RUnlock() + if ok { + return p, true + } + + // If not found, check if it's an Alpha token and ensure cache is loaded + ms.ensureAlphaCacheLoaded() + if ms.IsAlphaToken(symbol) { + if alphaToken, exists := ms.GetAlphaToken(symbol); exists { + price := alphaToken.GetPrice() + if price > 0 { + ms.mu.Lock() + ms.spotPrice[symbol] = price + ms.mu.Unlock() + return price, true + } + } + } + + return 0, false } func (ms *MarketData) StartSpotWsMarkPrice() error { @@ -21,6 +42,8 @@ func (ms *MarketData) StartSpotWsMarkPrice() error { } func (ms *MarketData) spotWsAllMarketsStatHandler(event binance.WsAllMarketsStatEvent) { + ms.mu.Lock() + defer ms.mu.Unlock() for _, priceEvent := range event { price, err := strconv.ParseFloat(priceEvent.LastPrice, 64) if err != nil { diff --git a/internal/helper/binancex/symbol.go b/internal/helper/binancex/symbol.go index 62d5f0b..b4a45cc 100644 --- a/internal/helper/binancex/symbol.go +++ b/internal/helper/binancex/symbol.go @@ -1,10 +1,11 @@ package binancex import ( + "strings" + "me.thuanle/bbot/internal/configs/binance" "me.thuanle/bbot/internal/data" "me.thuanle/bbot/internal/utils/stringx" - "strings" ) func Symbol2Token(sym string) string { @@ -74,7 +75,14 @@ func Token2Symbols(token string) []string { } func IsToken(s string) bool { - return len(Token2Symbols(s)) > 0 + // First check regular symbols + if len(Token2Symbols(s)) > 0 { + return true + } + + // Then check Alpha tokens + s = strings.ToUpper(s) + return data.Market.IsAlphaToken(s) } // Future2SpotSymbol convert future symbol to spot symbol diff --git a/internal/services/tele/commands/token.go b/internal/services/tele/commands/token.go index daaef91..bfced49 100644 --- a/internal/services/tele/commands/token.go +++ b/internal/services/tele/commands/token.go @@ -1,6 +1,8 @@ package commands import ( + "strings" + "golang.org/x/text/language" "golang.org/x/text/message" "gopkg.in/telebot.v3" @@ -9,7 +11,6 @@ import ( "me.thuanle/bbot/internal/helper/binancex" "me.thuanle/bbot/internal/services/tele/chat" "me.thuanle/bbot/internal/services/tele/view" - "strings" ) var lastEthPrice float64 @@ -27,6 +28,22 @@ func showStickerMode(context telebot.Context, token string) { } func OnTokenInfoByToken(context telebot.Context, token string) error { + token = strings.ToUpper(token) + + // Check if it's an Alpha token first + if data.Market.IsAlphaToken(token) { + showStickerMode(context, token) + + if alphaToken, exists := data.Market.GetAlphaToken(token); exists { + sp := alphaToken.GetPrice() + // For Alpha tokens, we don't have future price, funding rate, etc. + // Use spot price for both spot and future in the display + _ = chat.ReplyMessage(context, view.RenderOnPriceMessage(token, sp, sp, 0, 0, 0)) + } + return nil + } + + // Regular token handling symbols := binancex.Token2Symbols(token) if len(symbols) == 0 { return nil @@ -36,7 +53,7 @@ func OnTokenInfoByToken(context telebot.Context, token string) error { fp, fundRate, fundTime, ok := data.Market.GetFuturePrice(symbols[0]) marginRates := data.Market.GetMarginInterestRates() - tokenInterestRate := marginRates[strings.ToUpper(token)] + tokenInterestRate := marginRates[token] if !ok { return nil }