Browse Source

fix:add code

master
1447560092@qq.com 2 months ago
commit
e71bae6f14
  1. 27
      .gitignore
  2. 8
      Makefile
  3. 132
      README.md
  4. 208
      ServerDeployment
  5. 43
      ServerList
  6. 128
      ServiceUpdateLog
  7. 357
      api/api.go
  8. 521
      cmd/closingMarket/closingMarket.go
  9. 223
      cmd/common/base.go
  10. 1122
      cmd/common/common.go
  11. 71
      cmd/common/gzip.go
  12. 671
      cmd/common/notStock.go
  13. 115
      cmd/main.go
  14. 278
      cmd/marketwsscliert/marketcontract.go
  15. 430
      cmd/marketwsscliert/marketforex.go
  16. 216
      cmd/marketwsscliert/marketshare.go
  17. 401
      cmd/marketwsscliert/marketspots.go
  18. 270
      cmd/marketwsscliert/marketwssclient.go
  19. 589
      cmd/marketwsscliert/marketwssclient_test.go
  20. 217
      cmd/marketwsscliert/modifycontract.go
  21. 421
      cmd/marketwsscliert/modifyforex.go
  22. 517
      cmd/selfContract/aggregation.go
  23. 57
      cmd/selfContract/mongo.go
  24. 723
      cmd/selfContract/virtualContract.go
  25. 300
      cmd/selfMarketSpot/aggregation.go
  26. 59
      cmd/selfMarketSpot/model.go
  27. 698
      cmd/selfMarketSpot/virtualContract.go
  28. 13
      cmd/servicemanager/currencyWss.go
  29. 31
      cmd/servicemanager/gather.go
  30. 68
      cmd/servicemanager/gin.go
  31. 38
      cmd/servicemanager/selfContract.go
  32. 38
      cmd/servicemanager/selfMarketSpot.go
  33. 79
      cmd/servicemanager/shareWss.go
  34. 20
      cmd/servicemanager/shareus.go
  35. 55
      cmd/servicemanager/tickdb.go
  36. 161
      cmd/websocketcollect/cache/leveldb.go
  37. 1
      cmd/websocketcollect/db/forex/CURRENT
  38. 1
      cmd/websocketcollect/db/forex/CURRENT.bak
  39. 0
      cmd/websocketcollect/db/forex/LOCK
  40. 15
      cmd/websocketcollect/db/forex/LOG
  41. BIN
      cmd/websocketcollect/db/forex/MANIFEST-000003
  42. BIN
      cmd/websocketcollect/db/us/000016.ldb
  43. BIN
      cmd/websocketcollect/db/us/000033.ldb
  44. 1
      cmd/websocketcollect/db/us/CURRENT
  45. 1
      cmd/websocketcollect/db/us/CURRENT.bak
  46. 0
      cmd/websocketcollect/db/us/LOCK
  47. 161
      cmd/websocketcollect/db/us/LOG
  48. BIN
      cmd/websocketcollect/db/us/MANIFEST-000037
  49. 36
      cmd/websocketcollect/forex/route.go
  50. 725
      cmd/websocketcollect/forex/wss.go
  51. 75
      cmd/websocketcollect/us/route.go
  52. 241
      cmd/websocketcollect/us/wss.go
  53. 540
      cmd/websocketservice/websocketclient.go
  54. 412
      cmd/websocketservice/websocketusclient.go
  55. 25
      config/config.go
  56. 84
      dictionary/publickey.go
  57. 106
      internal/Publicmethods.go
  58. 58
      internal/common.go
  59. 48
      internal/data/business/forextoexcel.go
  60. 245
      internal/data/business/mgocontract.go
  61. 119
      internal/data/business/mgohistoricalus.go
  62. 993
      internal/data/business/mgoinitdata.go
  63. 1175
      internal/data/business/mgolistdataus.go
  64. 638
      internal/data/business/mgolistdatemys.go
  65. 67
      internal/data/business/mgomanager.go
  66. 86
      internal/data/business/mgoshareus.go
  67. 381
      internal/data/business/mgospots.go
  68. 331
      internal/data/business/mgostockus.go
  69. 24
      internal/data/gorm.go
  70. 501
      internal/data/index.go
  71. 767
      internal/data/mongo.go
  72. 48
      internal/data/mysql.go
  73. 65
      internal/data/mysqlbusiness/addinfo.go
  74. 112
      internal/data/mysqlbusiness/getinfo.go
  75. 102
      internal/data/mysqlbusiness/updateinfo.go
  76. 61
      internal/gzip/gzipanalysis.go
  77. 195
      internal/httprequest.go
  78. 194
      internal/model/contractMarket.go
  79. 193
      internal/model/forexMarket.go
  80. 44
      internal/model/pingmessage.go
  81. 22
      internal/model/pingv1message.go
  82. 24
      internal/model/pingv2message.go
  83. 18
      internal/model/websocketv1authenticationrequest.go
  84. 28
      internal/model/websocketv2authenticationrequest.go
  85. 48
      internal/mq/redis.go
  86. 17
      internal/mq/redis_test.go
  87. 89
      internal/paramstr.go
  88. 85
      internal/pubsub/pubsub.go
  89. 42
      internal/pubsub/pubsub_test.go
  90. 233
      internal/redis/redis.go
  91. 60
      internal/requestbuilder/privateurlbuilder.go
  92. 25
      internal/requestbuilder/publicurlbuilder.go
  93. 41
      internal/requestbuilder/signer.go
  94. 65
      internal/requestbuilder/websocketv1requestbuilder.go
  95. 65
      internal/requestbuilder/websocketv2requestbuilder.go
  96. 55
      logging/applogger/applogger.go
  97. 84
      logging/perflogger/performancelogger.go
  98. 137
      pkg/bawssclient/candlestickwebsocketclient.go
  99. 114
      pkg/bawssclient/depthwebsocketclient.go
  100. 122
      pkg/bawssclient/tickerwebsocketclient.go

27
.gitignore

@ -0,0 +1,27 @@
# ---> Go
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
.idea/
# Test binary, built with `go test -c`
*.test
cmd/config.yaml
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
vendor/
cmd/config.yaml
# Go workspace file
go.work
*.log
config/config.yaml
go.*

8
Makefile

@ -0,0 +1,8 @@
GOHOSTOS:=$(shell go env GOHOSTOS)
GOPATH:=$(shell go env GOPATH)
VERSION=$(shell git describe --tags --always)
.PHONY: win_build
# win_build services
win_build:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/wssPool ./cmd/main.go

132
README.md

@ -0,0 +1,132 @@
### wss-pool
## 项目说明
根据撮合系统需求,实现一个广播分发的webSocket数据服务;
* 对接第三方数据服务[火币,币安,OKX,股票...].
* 基于go-websocket提供高性能的、稳定的、时效性的数据服务.
* 实现用户订阅功能
* 实现数据分发功能
## Benchmarks
#现货数据ws调用规则
*ws://127.0.0.1:端口/quotes-wss
- (1)客户端ping:
{
"type":"ping",
"symbol":"ping"
}
- (2)订阅:
{
"type":"subscribe",
"symbol":"market.btcusdt.kline.1min"
}
- (3)取消订阅:
{
"type":"unSubscribe",
"symbol":"market.btcusdt.kline.1min"
}
| Benchmark name | 服务端端口|
| ------------------------------ | ---------:|
| wssPool服务 | :8861|
| 数采集服务 | :8852|
*服务启动说明
- wssPool服务启动:./服务名称 --check=server --hostS 127.0.0.1 --addrS 服务端端口号
- 数采集服务端启动:./服务名称 --check=gather --hostG 127.0.0.1 --addrG 服务端端口号
#现货-合约-股票静态数据服务
| Static Services name | 服务名称 |
| ------------------------------ | --------------------:|
| 现货-K线数据(蜡烛图) | spots/kline|
| 现货-聚合行情(Ticker) | spots/merged|
| 现货-所有交易对的最新 | spots/tickers|
| 现货-最近市场成交记录 | spots/trade|
| 现货-市场深度数据 | spots/depth|
| 现货-最近24小时行情数据 | spots/detail|
| 现货-获得近期交易记录 | spots/history/trade|
| 现货列表数据服务 | spots/merged/list|
| 合约-获取行情深度数据 | contract/depth|
| 合约-获取市场最优挂单 | contract/bbo|
| 合约-K线数据获取 | contract/history/kline|
| 合约-行情数据信息 | contract/merged |
| 合约-获取标记价格的K线数据 | contract/history/price_kline |
| 合约-批量获取聚合行情(V2) | contract/batch_merged |
| 合约-获取市场最近成交记录 | contract/trade|
| 合约-批量获取最近的交易记录 | contract/history/trade|
| 合约-平台历史持仓量查询 | contract/swap_his_open_interest|
| 合约-获取合约的溢价指数K线 | contract/history/linear_swap_premium_index_kline|
| 合约-获取实时预测资金费率的K线数据 | contract/history/linear_swap_estimated_rate_kline|
| 合约-获取基差数据 | contract/history/linear_swap_basis |
*服务启动说明
- 服务端启动:./staticS --check=gin --hostC 127.0.0.1 --addrC :8851
*端口配置
- 静态服务端口:8851
#现货-合约-股票静态服务调用规则
- http://127.0.0.1:8851/服务名称
# 股票服务
### supervisor 管理服务
```
1、http静态服务
WEB主服务端启动:服务名称 --check gin --hostS 0.0.0.0 --addrS :88
2、(美股|外汇[实时|买一卖一])行情服务
外汇分发服务:服务名称 --check collectForex --hostS 0.0.0.0 --addrS :7778 --config /home/ubuntu/wss-server/config/config.yaml
外汇采集服务:服务名称 --check gatherForex --hostS 0.0.0.0 --addrS :8965 --model forex --config /home/ubuntu/wss-server/config/config.yaml
美股分发服务:服务名称 --check collectUs --hostS 0.0.0.0 --addrS :7777 --config /home/ubuntu/wss-server/config/config.yaml
美股采集服务:服务名称 --check gatherUs --hostS 0.0.0.0 --addrS :8964 --model usShare --config /home/ubuntu/wss-server/config/config.yaml
3、股票采集和更新服务
印度期权股票服务端启动:服务名称 --check indiaOption --hostS 0.0.0.0 --addrS :95
指数股票服务端启动:服务名称 --check stockIndex --hostS 0.0.0.0 --addrS :92
日本股票服务端启动:服务名称 --check japanStock --hostS 0.0.0.0 --addrS :86
印尼股票服务端启动:服务名称 --check indonesiaStock --hostS 0.0.0.0 --addrS :89
泰国股票服务端启动:服务名称 --check thailandStock --hostS 0.0.0.0 --addrS :90
印度股票服务端启动:服务名称 --check indiaStock --hostS 0.0.0.0 --addrS :91
马来股票服务端启动:服务名称 --check malaysiaStock --hostS 0.0.0.0 --addrS :93
新加坡股票服务端启动:服务名称 --check singaporeStock --hostS 0.0.0.0 --addrS :94
港股票服务端启动:服务名称 --check hongkongStock --hostS 0.0.0.0 --addrS :96
英股票服务端启动:服务名称 --check ukStock --hostS 0.0.0.0 --addrS :97
德股票服务端启动:服务名称 --check germanyStock --hostS 0.0.0.0 --addrS :98
巴西股票服务端启动:服务名称 --check brazilStock --hostS 0.0.0.0 --addrS :103
美股票服务端启动:服务名称 --check usStock --hostS 0.0.0.0 --addrS :102
```
### cron 管理服务(行情报警、指数、泰股、马股、港股、印度股、印尼股、新加坡股、英股、德股、法股、巴西、日本)
```
*/20 * * * 1-5 root /home/ubuntu/wss-server/checkStock --check tickDB --model checkStock --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/checkStock.log 2>&1 &
*/5 * * * 1-6 root /home/ubuntu/wss-server/stockIndex --check tickDB --model stockIndex --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/stockIndex.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/ukStock --check tickDB --model southAsiaStock --contract UK --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/ukStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/indiaStock --check tickDB --model southAsiaStock --contract India --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/indiaStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/thailandStock --check tickDB --model southAsiaStock --contract Thailand --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/thailandStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/malaysiaStock --check tickDB --model southAsiaStock --contract Malaysia --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/malaysiaStock.log 2>&1
*/5 * * * 1-5 root /home/ubuntu/wss-server/hongkongStock --check tickDB --model southAsiaStock --contract HongKong --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/hongkongStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/indonesiaStock --check tickDB --model southAsiaStock --contract Indonesia --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/indonesiaStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/singaporeStock --check tickDB --model southAsiaStock --contract Singapore --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/singaporeStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/germanyStock --check tickDB --model southAsiaStock --contract Germany --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/germanyStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/franceStock --check tickDB --model southAsiaStock --contract France --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/franceStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/japanStock --check tickDB --model southAsiaStock --contract Japan --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/japanStock.log 2>&1 &
```
### 更新美股上一次行情价格
```
8 9 * * * root /home/ubuntu/wss-server/preClose --check tickDB --model previousClose --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/preClose.log 2>&1 &
```
### K线数据优化
```
12 22 * * 2-5 root /home/ubuntu/wss-server/deleteSpot --check tickDB --model deleteSpot --contract false --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/deleteSpot.log 2>&1 &
```
### 插针数据推送
```
*/1 * * * * root /home/ubuntu/wss-server/stockCloseData --check tickDB --model stockCloseData --config /home/ubuntu/wss-server/config/config.yaml>>/var/log/stockCloseData.log 2>&1 &
```

208
ServerDeployment

@ -0,0 +1,208 @@
公司无线密码:
账号1:H3C_2202-5G-1
密码:A13b142202)
账号2:ssid
密码:Meetingyou0)
-------------------------------------------------------------------------------------------------------------------------------------------------
1、美股数据接入账号:
https://polygon.io/dashboard/api-keys
账号:rnldburn@gmail.com
密码: Meetingyou0
key: CDGMfPJmyiEX5dbjagLSEipf5Y4XbXVb
2、亚马逊oss接入:
myaccesspoint
桶名称:log-aws-bucket-2023
S3: s3://arn:aws:s3:ap-southeast-1:297182325232:accesspoint/myaccesspoint
ARN: arn:aws:s3:ap-southeast-1:297182325232:accesspoint/myaccesspoint
别名:myaccesspoint-9kcrj5icrgdejw1fydhda6n6rfcjkaps1a-s3alias
3、区域:ap-southeast-1
aws_access_key_id:AKIAUKMLSNHYAP7EOBDE
aws_secret_access_key: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
endpoint:s3.ap-southeast-1.amazonaws.com
------------------------------------------------------------------------------------------------------------------------------------------------
1、域名解析:
cotelaiamelia@gmail.com
Meetingyou0)
2、GCP-谷歌云-测试环境:
项目名称:SC Project 47850
LoginAccount:zajdelvipondespq9931@gmail.com
邮箱密码:7ae8b5w0u0z
访问地址:https://cloud.google.com/?hl=zh-CN
3、GCP-谷歌云-正式环境:
项目名称:SC Project 51416
登录邮箱:cloudrun40@gmail.com
邮箱密码:Meetingyou0
辅助邮箱:rnldburn@gmail.com
访问地址:https://cloud.google.com/?hl=zh-CN
4、跳板机
ec2-13-212-72-30.ap-southeast-1.compute.amazonaws.com
用户名:Administrator
密码:J.RUhgy8hHf?QsaK50cckzv3ynl7X.W=
------------------------------------------------------------------------------------------------------------------------------------------------
1、测试服务接口是否正常
curl -X POST -H 'Content-Type: application/json' -d '{ "status": "0","pageSize": "10","pageCount": "1"}' http://10.160.0.2:8003/order_shareus/share_list
curl -X POST -H 'Content-Type: application/json' -d '{ "status": "0","pageSize": "10","pageCount": "1"}' http://trade.lazardinvestgroup.net/order_shareus/share_list
curl -X POST -H 'Content-Type: application/json' -d '{ "status": "0","pageSize": "10","pageCount": "1"}' http://10.160.0.2:8002/order_contract/contract_list
curl -X POST -H 'Content-Type: application/json' -d '{ "status": "0","pageSize": "10","pageCount": "1"}' http://trade.chdh.me/order_contract/contract_list
curl -X POST -H 'Content-Type: application/json' -d '{ "status": "0","pageSize": "10","pageCount": "1"}' https://172.23.48.59:8004/order_sharemys/share_list
curl -X POST -H 'Content-Type: application/json' -d '{ }' http://10.148.0.6:8000/order_sharepre/update_all_stock_id
curl -X POST -H 'Content-Type: application/json' -d '{ "code": "BSE:IXIGO","id": "80","stock": "7"}' http://10.160.0.17:8000/order_sharepre/share_pre_trade
2、初始化股票数据 34.100.189.47
./shareUs -conf /home/ubuntu/service/config/shareUs.yaml -check shareUs -network onLine
./digitalInit -conf /home/ubuntu/service/config/digitalInit.yaml -check digitalInit -network onLine
./optionInr -conf /home/ubuntu/service/config/optionInr.yaml -check optionInr -network onLine
./shareClearCache -conf /home/ubuntu/service/config/shareInit.yaml -check shareClearCache -network onLine
./contract -conf /home/ubuntu/service/config/contract.yaml -check contract -network onLine
./second -conf /home/ubuntu/service/config/second.yaml -check second -network onLine
./shareCache -conf /home/ubuntu/service/config/shareCache.yaml -check shareCache -network onLine
./wssPool --check tickDB --hostS 0.0.0.0 --addrS :1000 --model allUs --config /home/ubuntu/wss-server/config/config06.yaml
./wssPool --check tickDB --hostS 0.0.0.0 --addrS :1000 --model Us --config /home/ubuntu/wss-server/config/config06.yaml
./wssPool --check tickDB --hostS 0.0.0.0 --addrS :1000 --model allUs --config /home/ubuntu/wss-server/config/config06.yaml
./wssPool --check stockDataUs --hostS 0.0.0.0 --addrS :1000 --project US --config /home/ubuntu/wss-server/config/config06.yaml
./wssPool --check tickDB --hostS 0.0.0.0 --addrS :1000 --model updateStockUsCode --config /home/ubuntu/wss-server/config/config06.yaml
./wssPool --check stockCode --hostS 0.0.0.0 --addrS :7777 --project US --config /home/ubuntu/wss-server/config/config06.yaml
./wssPool --check gatherUs --hostS 0.0.0.0 --addrS :7777
3、mysql-生成model
./xorm.exe reverse mysql admin:Meetingyou0@\(dbtest.crsocbk1nt38.ap-southeast-1.rds.amazonaws.com:3306\)/bourse?charset=utf8 templates/goxorm
./xorm.exe reverse mysql admin:Meetingyou0@\(ubsfim.c59brkvf12hq.ap-southeast-3.rds.amazonaws.com:3306\)/bourse?charset=utf8 templates/goxorm
./xorm.exe reverse mysql root:'q7%B/$o>ck5r]{x<'@\(35.186.154.125:3306\)/bourse?charset=utf8 templates/goxorm
./xorm.exe reverse mysql root:123456789@\(127.0.0.1:13306\)/bourse?charset=utf8 templates/goxorm
4、本地环境-docker启动服务
docker run --name mysql -p 13306:3306 -e MYSQL_ROOT_PASSWORD=12345678 mysql:8.0.28
docker run --name f2ad9f23df82a3e5efabd1574b862a94c0657c73a6179efec07d5cf9ae5a307f -p 13306:3306 -e MYSQL_ROOT_PASSWORD=123456789 -d mysql:8.0.28
docker run --name my-redis -p 6379:6379 -d redis --requirepass "123456"
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
服务器环境部署:
1、redis部署
sudo apt update
sudo apt-get install redis-server
2、mongod部署
sudo apt update
wget -qO- https://get.docker.com/ | sh
apt install docker-compose
docker-compose up -d
3、安装supervisor
sudo apt update
apt install supervisor
4、安装nginxs
sudo apt update
sudo apt install nginx
sudo systemctl start nginx
sudo service nginx reload {start|stop|restart|reload|force-reload|status|configtest|rotate|upgrade)
5、设置最大链接数
vim /etc/profile
ulimit -n 1000000
source /etc/profile
6、初始化服务器
切换用户:sudo -i
修改登录权限:vim /etc/ssh/sshd_config
修改配置:PasswordAuthentication yes
修改配置: ChallengeResponseAuthentication yes
重启ssh服务:service ssh restart
修改密码:passwd
7、修改服务器时区
sudo timedatectl set-timezone Asia/Shanghai
8、查看redis链接数
netstat -an | grep :6379 | wc -l
netstat -tuln | grep :80
9、corn 定时器
sudo systemctl status cron
sudo systemctl stop cron
sudo systemctl enable cron
sudo service cron restart
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
1、市场总资产: 冻结 + 可用 + 持仓市值
2、市场可用资产: 可用
3、市场累计盈亏(订单表-->平仓状态):
1、买涨:订单量 * (平仓价 - 开仓价)
2、买跌:订单量 * (开仓价 - 平仓价)
4、市场冻结资产: 冻结
5、市场总手续费(统计订单表-->【持仓和平仓】状态):
1、交易手续费:bot_stock_fur_trade ------ sum(service_cost + closing_cost)
2、申购手续费:bot_user_fur_pre_stock_order ----- sum(get_fee)
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
目前杠杆设置注解:
1、全市场杠杆倍数设置——》针对所有市场股票给的默认杠杆倍数(目前设置的是1,可修改),且不需要关闭,当然也不影响用户单独设置其他杠杆倍数;
2、用户杠杆倍数设置——》如果用户通过了申请且满足了后台设置触发杠杆的条件(例如:1、是否开启杠杆,2、是否达到最小面值,3、是否满足设置杠杆的范围(最大和最小)),就会使用这个用户单独设置的杠杆倍数;
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
docker save liuqingzheng/yapi:latest > yapi_latest.tar
docker save mongo:latest > mongo_latest.tar
docker save gitea/gitea:latest > gitea_latest.tar
docker save mysql:5.7 > mysql.tar
scp -P 31544 mongo_latest.tar mysql.tar gitea_latest.tar yapi_latest.tar root@154.86.0.30:/root
docker load < mongo_latest.tar mysql.tar gitea_latest.tar yapi_latest.tar
docker run -d --name yapi-mongo -e MONGO_INITDB_ROOT_USERNAME=admin@admin.com -e MONGO_INITDB_ROOT_PASSWORD=admin mongo:latest
docker run -d --name yapi-mongo mongo:latest
docker run -d --name yapi-web -p 3001:3000 liuqingzheng/yapi:latest
find / -name config.json 2>/dev/null
docker run -d --name gitea_server_1 -p 3000:3000 -p 222:31544 gitea/gitea:latest
docker run -d --name gitea_db_1 -p 3306:3306 -p 33060:33060 mysql/mysql:5.7
文档|git
yapi:http://154.86.0.30:3001/
git:http://103.71.254.42:3000/
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
cron 执行定时任务; cat /etc/crontab
// 行情报警、指数、泰股、马股、港股、印度股、印尼股、新加坡股、英股、德股、法股、日本
*/20 * * * 1-5 root /home/ubuntu/wss-server/checkStock --check tickDB --model checkStock --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/checkStock.log 2>&1 &
*/5 * * * 1-6 root /home/ubuntu/wss-server/stockIndex --check tickDB --model stockIndex --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/stockIndex.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/thailandStock --check tickDB --model southAsiaStock --contract Thailand --hostS 0.0.0.0.0 --addrS :289 --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/thailandStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/malaysiaStock --check tickDB --model southAsiaStock --contract Malaysia --hostS 0.0.0.0.0 --addrS :299 --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/malaysiaStock.log 2>&1
*/5 * * * 1-5 root /home/ubuntu/wss-server/hongkongStock --check tickDB --model southAsiaStock --contract HongKong --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/hongkongStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/indiaStock --check tickDB --model southAsiaStock --contract India --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/indiaStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/indonesiaStock --check tickDB --model southAsiaStock --contract Indonesia --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/indonesiaStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/singaporeStock --check tickDB --model southAsiaStock --contract Singapore --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/singaporeStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/ukStock --check tickDB --model southAsiaStock --contract UK --config=/home/ubuntu/wss-server/config/config06.yaml>>/var/log/ukStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/germanyStock --check tickDB --model southAsiaStock --contract Germany --config=/home/ubuntu/wss-server/config/config06.yaml>>/var/log/germanyStock.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/franceStock --check tickDB --model southAsiaStock --contract France --config=/home/ubuntu/wss-server/config/config06.yaml>>/var/log/franceStock.log 2>&1 &
59 59 23 * * root /home/ubuntu/wss-server/forexClosePrice --check tickDB --model forexClosePrice --config=/home/ubuntu/wss-server/config/config.yaml>>/var/log/forexClosePrice.log 2>&1 &
*/5 * * * 1-5 root /home/ubuntu/wss-server/deleteForexTrade --check tickDB --model deleteForexTrade --config=/home/ubuntu/wss-server/config/config.yaml>>/var/log/forexClosePrice.log 2>&1 &
// 更新上一次行情价格
8 9 * * * root /home/ubuntu/wss-server/preClose --check=tickDB --model=previousClose --config=/home/ubuntu/wss-server/config/config06.yaml>>/var/log/preClose.log 2>&1 &
// 数据清理
12 22 * * 2-5 root /home/ubuntu/wss-server/deleteSpot --check=tickDB --model=deleteSpot --contract=false --config /home/ubuntu/wss-server/config/config06.yaml>>/var/log/deleteSpot.log 2>&1 &
// 插针数据推送
*/1 * * * * root /home/ubuntu/wss-server/stockCloseData --check=tickDB --model=stockCloseData --config=/home/ubuntu/wss-server/config/config06.yaml>>/var/log/stockCloseData.log 2>&1 &
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
[program:collectUs]
command=/home/ubuntu/wss-server/collectUs --check collectUs --hostS 0.0.0.0 --addrS :7777 --config /home/ubuntu/wss-server/config/config06.yaml
startsecs=30
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/collectUs_err.log
stdout_logfile=/var/log/supervisor/collectUs_info.log
stdout_logfile_maxbytes = 5MB
stdout_logfile_backups = 3
-----------------------------------------------------------------------------------------------------------------------------------------------------------------

43
ServerList

@ -0,0 +1,43 @@
------------------------------------------------------------------------------------------------------------------------------------------------
test(谷歌云)服务器(域名:orbisimg.com)
web-mysql 47.237.29.68(公) 172.26.45.216(私有)
trade-quotes 47.237.64.60(公) 172.26.45.215(私有)
mongo-redis 8.222.169.172(公) 172.26.45.217(私有)
msyql
host:47.237.29.68(公) 172.26.45.216(私有)
user:root
密码:Meetingyou0))
redis
host:47.237.29.68(公) 172.26.45.216(私有)
密码:MRrfvtyujnb&hg56
端口:6379
mongodb数据库
host:8.222.169.172(公) 172.26.45.217(私有)
账号:pqRRVamndJ
密码:35LlW3pXF76&WD!OOlnI
------------------------------------------------------------------------------------------------------------------------------------------------
project06-新服务器(域名:yrsig.com)
p6-mongo 10.154.0.10 (nic0) 35.189.116.242 (nic0)
p6-quotes 10.154.0.8 (nic0) 35.246.61.201 (nic0)
p6-trade 10.154.0.7 (nic0) 34.147.138.200 (nic0)
p6-web 10.154.0.9 (nic0) 34.105.182.222 (nic0)
用户名:root
密码:Meetingyou0))
mysql
IP: 10.154.0.9 (nic0) 34.105.132.51 (nic0)
port: 23306
USER: root
PWD: Meetingyou0))2024$
redis
IP: 10.154.0.9 (nic0) 34.105.132.51 (nic0)
port :26379
PWD: 7d00cb62-1d1c-4c86-b50a-ebf9f00cc9fd
------------------------------------------------------------------------------------------------------------------------------------------------

128
ServiceUpdateLog

@ -0,0 +1,128 @@
------------------------------------------------------------------------------------------------------------------------------------------------
p7正式环境,美股|泰股|马股|港股|IPO交易服务更新
服务部署完毕(2024-06-21)
此次更新功能包含:
1、部署美股、泰股、马股、港股、IPO交易服务
2、访问地址
1>交易订单域名: https://trade.tdcowengroup.com
2>交易订单Wss: wss://trade.tdcowengroup.com
3、相关API文档参见
1>http://103.71.254.42:3001/project/56/interface/api
2>http://103.71.254.42:3001/project/56/interface/api/cat_88
------------------------------------------------------------------------------------------------------------------------------------------------
测试|正式环境,德股|法股交易服务更新
服务部署完毕(2024-06-24)
此次更新功能包含:
1、部署德股、法股交易服务
2、测试访问地址
1>交易订单域名: https://trade.jdtest88.com
2>交易订单Wss: wss://trade.jdtest88.com
3、线上访问地址
1>交易订单域名: https://trade.twinim.com
2>交易订单Wss: wss://trade.twinim.com
4、相关API文档参见
1>http://103.71.254.42:3001/project/56/interface/api/cat_807
2>http://103.71.254.42:3001/project/56/interface/api/cat_799
3>http://103.71.254.42:3001/project/56/interface/api/cat_88
------------------------------------------------------------------------------------------------------------------------------------------------
测试|p6正式环境,股票交易服务更新
服务部署完毕(2024-06-27)
此次更新功能包含:
1、统一股票订单列表时间格式
------------------------------------------------------------------------------------------------------------------------------------------------
p2|p7正式环境,股票交易服务更新
服务部署完毕(2024-07-01)
此次更新功能包含:
1、更新通过配置股票插针进行后台交易
------------------------------------------------------------------------------------------------------------------------------------------------
p8正式环境,数字币交易服务更新
服务部署完毕(2024-07-02)
此次更新功能包含:
1、部署合约、现货、秒合约交易服务
2、线上访问地址
1>交易订单域名: https://trade.chdh.me
2>交易订单Wss: wss://trade.chdh.me
3、相关API文档参见
1>http://103.71.254.42:3001/project/56/interface/api/cat_519
2>http://103.71.254.42:3001/project/56/interface/api/cat_74
3>http://103.71.254.42:3001/project/56/interface/api/cat_81
------------------------------------------------------------------------------------------------------------------------------------------------
关于插针改动(p2\p6\p7):
1、前端/后端-(订单wss订阅|浮动盈亏wss订阅|市场总金额浮动盈亏wss订阅)取值为:实时价、闭盘价(优先级:实时价 > 闭盘价)
2、盘中插针(优先级:插针价 > 实时价)
1>设置时:交易开仓取值为:插针价
2>未设置时:交易开仓取值为:实时价
3、盘前|盘后插针 (在设置容许下单的前提下:例如调整开盘时间等)
1>设置时:交易开仓取值为:插针价
------------------------------------------------------------------------------------------------------------------------------------------------
测试环境,股票交易统计服务更新
服务部署完毕(2024-07-10)
此次更新功能包含:
1、新增股票各个市场订阅统计服务(包含:用户市场总资产、用户市场总可用余额、用户市场冻结、用户市场累计盈亏、用户市场总手续费、用户市场总浮动盈亏)
2、相关API文档参见
1>http://103.71.254.42:3001/project/56/interface/api/cat_88
------------------------------------------------------------------------------------------------------------------------------------------------
FB
应用编号:489884953731337
应用密钥:77fcf7fe8f8b1ba26ad622b537e321c9
gg
客户端ID: 220504529176-bl8cfsr1dktbebl1qo1km6mu02lfdjaa.apps.googleusercontent.com
客户端密钥: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
------------------------------------------------------------------------------------------------------------------------------------------------
p6正式环境,股票交易服务更新
服务部署完毕(2024-08-02)
此次更新功能包含:
1、新增股票市场IPO欠款功能
------------------------------------------------------------------------------------------------------------------------------------------------
正式环境,p9股票交易服务更新
服务部署完毕(2024-08-13)
此次更新功能包含:
1、部署美股、泰股、巴西股交易服务
2、线上访问地址
1>交易订单域名: https://trade.wedbushig.com
2>交易订单Wss: wss://trade.wedbushig.com
3、相关API文档参见
1>订单api:http://103.71.254.42:3001/project/56/interface/api/cat_808
2>订单订阅:http://103.71.254.42:3001/project/56/interface/api/4543
3>管理员|浮动盈亏订阅:http://103.71.254.42:3001/project/56/interface/api/519
------------------------------------------------------------------------------------------------------------------------------------------------
正式|测试(美股\德股\法股\英股)环境,p6股票交易服务更新
服务部署完毕(2024-08-14)
此次更新功能包含:
1、修改股票交易杠杆取值逻辑
------------------------------------------------------------------------------------------------------------------------------------------------
目前杠杆开启的条件:
1、申请杠杆(开启杠杆)
2、是否满足设置杠杆的最小购买量
3、设置杠杆倍数是否满足区间范围(最小~最大)
杠杆优先级:
1、设置默认杠杆
2、设置单个用户杠杆
3、优先级:设置单个用户杠杆 > 默认值
------------------------------------------------------------------------------------------------------------------------------------------------
p9测试环境,数字币交易服务更新
服务部署完毕(2024-09-19)
此次更新功能包含:
1、部署合约、现货、秒合约交易服务
2、交易访问地址
1>交易订单域名: https://trade.jdtest88.com
2>交易订单Wss: wss://trade.jdtest88.com
3、行情访问地址
1>行情订单域名: https://quotes.jdtest88.com
2>行情订单Wss: wss://quotes.jdtest88.com
------------------------------------------------------------------------------------------------------------------------------------------------

357
api/api.go

@ -0,0 +1,357 @@
package api
import (
"github.com/gin-gonic/gin"
"net/http"
"wss-pool/pkg/processor"
)
// 处理跨域
func Core() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token,Authorization,Token")
c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type")
c.Header("Access-Control-Allow-Credentials", "True")
// Release Index Options
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// Process Request
c.Next()
}
}
// 数据API服务路由
func RouterApiServer(project string) *gin.Engine {
routers := gin.Default()
routers.Use(Core())
router := routers.Group("/")
// excel 导出
router.GET("/spots/excel", processor.SymbolToExcel)
router.GET("/spots/excel/forex", processor.ExcelToForexCode)
router.GET("/spots/excel/japan", processor.ExcelToSymbolByJapanJson)
router.GET("/spots/excel/japanJson", processor.ExcelToSymbolByJapan)
// 现货数据API服务
router.GET("/main/list", processor.MainSpotList)
router.GET("/main/free-list", processor.MainFreeSpotList)
router.GET("/spots/kline", processor.SpotsKline)
router.GET("/spots/merged", processor.SpotsMerged)
router.GET("/spots/tickers", processor.SpotsTickers)
router.GET("/spots/trade", processor.SpotsTrade)
router.GET("/spots/intro", processor.IntroList)
router.GET("/spots/img/visit", processor.Visit)
router.GET("/spots/detail", processor.SpotsDetail)
router.GET("/spots/history/trade", processor.SpotsHistoryTrade)
router.GET("/spots/depth", processor.SpotsDepth)
router.GET("/spots/merged/list", processor.SpotsMergedList)
router.GET("/spots/index/list", processor.ExchangeSymbolIndexList)
router.GET("/spots/index/info", processor.StockIndexInfo)
router.GET("/spots/index/kline/list", processor.StockIndexKLineList)
// 合约数据API服务
router.GET("/contract/bbo", processor.ContractBbo)
router.GET("/contract/history/kline", processor.ContractHistoryKline)
router.GET("/contract/history/price_kline", processor.ContractHistoryPriceKline)
router.GET("/contract/batch_merged", processor.ContractBatchMerged)
router.GET("/contract/trade", processor.ContractTrade)
router.GET("/contract/swap_his_open_interest", processor.ContractsWapHisOpenInterest)
router.GET("/contract/history/linear_swap_premium_index_kline", processor.ContractHistoryLinearSwapPremiumIndexKline)
router.GET("/contract/history/linear_swap_estimated_rate_kline", processor.ContractHistoryLinearSwapEstimatedRateKline)
router.GET("/contract/history/linear_swap_basis", processor.ContractHistoryLinearSwapBasis)
router.GET("/contract/merged/list", processor.ContractMergedList)
router.GET("/contract/merged", processor.ContractMerged)
router.GET("/contract/depth", processor.ContractDepth)
router.GET("/contract/history/trade", processor.ContractHistoryTrade)
router.POST("/encryption/spots/news/add", processor.StockNewAdd)
// 股票基本信息查询
router.GET("share/fundamentals", processor.Fundamentals)
router.GET("share/fundamentals_new", processor.FundamentalsNew)
router.GET("share/eod", processor.Eod)
router.GET("share/get-list-optional-stock", processor.FindShareBySymbol)
router.GET("share/intradiscal", processor.IntraDisCal)
router.GET("share/exchange-symbol-list", processor.ExchangeSymbolList)
router.GET("share/exchange-free-symbol-list", processor.ExchangeFreeSymbolList)
router.GET("share/intraday", processor.Intraday)
router.GET("/spots/news/list", processor.StockNewsList)
router.GET("/spots/kline/list", processor.StockKLineList)
router.GET("/spots/southAsia/info", processor.StockSouthAsiaInfo)
router.GET("/spots/us/info", processor.StockUsInfo)
router.GET("/spots/kline/us/list", processor.StockKLineUsList)
router.GET("/spots/ticker_to_excel", processor.TickerToExcel)
router.POST("/spots/update/img", processor.UpdateImg)
router.POST("/spots/update/keep", processor.UpdateKeepDecimal)
router.POST("/spots/list/new/add", processor.StockListAddToPHP)
router.POST("/spots/php/update", processor.StockListUpdateToPHP)
router.POST("/spots/index/list/new/add", processor.StockIndexListUpdateToPHP)
// 美股股票静态数据查询
router.GET("/market/grouped", processor.Grouped)
router.GET("/market/trades", processor.Trades)
router.GET("/market/last-trade", processor.LastTrade)
router.GET("/market/quotes", processor.Quotes)
router.GET("/market/last-quote", processor.LastQuote)
router.GET("/market/snapshot-all-tickers", processor.SnapshotAllTickers)
router.GET("/market/snapshot-gainers-losers", processor.SnapshotGainersLosers)
router.GET("/market/snapshot-one-ticker", processor.SnapshotOneTicker)
router.GET("/market/reference-ticker", processor.ReferenceTicker)
router.GET("/market/contract-price-kline", processor.ContractPriceKLineList)
router.GET("/market/history-us", processor.HistoryUsList)
router.GET("/market/inquiry/price", processor.InquiryPrice)
router.GET("/market/spot-kline", processor.SpotKLineList)
router.GET("/market/contract-kline", processor.ContractKLineList)
router.GET("/market/reference-ticker-details", processor.ReferenceTickerDetails)
router.GET("/market/aggregates", processor.Aggregates)
router.GET("/market/open-close", processor.OpenClose)
router.GET("/market/previous-close", processor.PreviousClose)
router.GET("/market/reference-ticker-news", processor.ReferenceTickerNews)
router.POST("/market/msg", processor.MsgSend)
router.POST("/market/mobilelogin", processor.MobileLogin)
router.POST("/market/phonenumberbypassword", processor.PhoneNumberByPassWord)
router.POST("/market/registration", processor.Registration)
router.POST("/market/forgetpasswore", processor.ForgetPassWore)
router.POST("/market/setphonenumber", processor.SetPhoneNumber)
// 期权数据API服务
router.GET("/option/list", processor.ExchangeOptionList)
router.GET("/option/info", processor.OptionInfo)
router.GET("/option/php/list", processor.OptionPHPList)
router.GET("/option/excel", processor.OptionToExcel)
// 外汇数据API服务
router.GET("/forex/kline", processor.ForexAggregates)
router.GET("/forex/tickers/list", processor.ForexAllTickers)
router.GET("/forex/ticker", processor.ForexTicker)
router.GET("/forex/previous_close", processor.ForexPreviousClose)
router.GET("/forex/grouped_daily", processor.ForexGroupedDaily)
router.GET("/forex/ticker_search_list", processor.ForexSymbolList)
router.GET("/forex/ticker_free_list", processor.ForexFreeSymbolList)
router.GET("/forex/quotes_bbo", processor.ForexQuotesBBO)
router.GET("/forex/last_quote_bbo", processor.ForexLastQuote)
router.GET("/forex/real_time_currency", processor.ForexRealTimeCurrency)
// 新版外汇API服务
router.GET("/forex/ticker_new_search_list", processor.ForexSymbolListNew)
router.GET("/forex/ticker_new_free_list", processor.ForexFreeSymbolListNew)
router.GET("/forex/kline_history", processor.ForexAggregatesNewGet)
router.GET("/forex/trade_tick_list", processor.ForexTradeList)
router.POST("/forex/depth_tick", processor.ForexAggregatesDepthTick)
router.POST("/forex/trade_tick", processor.ForexAggregatesTradeTick)
router.POST("/forex/kline_new", processor.ForexAggregatesNewPost)
router.POST("/forex/spots/news/add", processor.StockNewAdd)
return routers
}
// 指数股票API服务
func RouterStockIndexApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/stockIndex")
{
group.POST("/new/add", processor.StockIndexInfoAdd)
group.POST("/list/add", processor.StockIndexListAdd)
group.POST("/info/add", processor.StockIndexInfoMon)
group.POST("/list/update", processor.StockIndexListUpdate)
group.GET("/list/get", processor.StockIndexListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 印度期权股票API服务
func RouterIndiaOptionApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/indiaOption")
{
group.POST("/info/add", processor.OptionInfoAdd)
group.POST("/list/add", processor.OptionListAdd)
}
return router
}
// 美股股票API服务
func RouterUSApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/us")
{
group.POST("/message/add", processor.UsMessage)
}
return router
}
// 印尼股票API服务
func RouterIndonesiaApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/indonesia")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 泰国股票API服务
func RouterThailandApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/thailand")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 印度股票API服务
func RouterIndiaApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/india")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 马来西亚股票API服务
func RouterMalaysiaApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/malaysia")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 新加坡股票API服务
func RouterSingaporeApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/singapore")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 港股股票API服务
func RouterHongKongApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/hongkong")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 英国股票API服务
func RouterUKApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/uk")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 法国股票API服务
func RouterFranceApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/france")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 德国股票API服务
func RouterGermanyApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/germany")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 巴西股票API服务
func RouterBrazilApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/brazil")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}
// 日本股票API服务
func RouterJapanApiServer() *gin.Engine {
router := gin.Default()
router.Use(Core())
group := router.Group("/japan")
{
group.POST("/spots/new/add", processor.StockInfoAdd)
group.POST("/spots/list/add", processor.StockListAdd)
group.POST("/spots/info/add", processor.StockInfoMon)
group.POST("/spots/list/update", processor.StockListUpdate)
group.GET("/spots/list/get", processor.StockListGet)
group.POST("/spots/news/add", processor.StockNewAdd)
}
return router
}

521
cmd/closingMarket/closingMarket.go

@ -0,0 +1,521 @@
package closingMarket
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
"github.com/satori/go.uuid"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"log"
"math"
"net/http"
"strconv"
"strings"
"sync"
"time"
"wss-pool/cmd/common"
"wss-pool/dictionary"
"wss-pool/internal/data/business"
"wss-pool/internal/model"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
models "wss-pool/pkg/model"
)
// Define a websocket connection object that contains information for each connection
type Client struct {
Id string // Client ID
conn *websocket.Conn // Define websocket link objects
msg chan []byte // Define messages received and distributed
symbol sync.Map // Concurrent Security - Manage User Subscription Types
mux sync.Mutex
}
type StockMessage struct {
S string `json:"s,omitempty"` // 股票代码
Country string `json:"country"` //国家
StockCode string `json:"stock_code" bson:"stock_code"` // 股票代码
Symbol string `json:"symbol"`
Stock string `json:"stock"` // 期权代码
IsStockIndex bool `json:"is_stock_index"`
IsOptionList bool `json:"is_option_list"`
IsOptionInfo bool `json:"is_option_info"`
}
var (
wsStockConMap = map[string][]*websocket.Conn{}
mutexStock = sync.RWMutex{}
msgStockChan = make(chan []byte)
mutexConn = sync.RWMutex{}
TotalNum int
mutexTotal = sync.RWMutex{}
countryMap = make(map[string][]string)
mutexCountry = sync.RWMutex{}
clearClientChan = make(chan *websocket.Conn)
pinStockMap = make(map[string]bool)
mutexPinMap = sync.RWMutex{}
)
const (
stockConnNum int = 20
writeWait = 10 * time.Second
)
// Define an UpGrader to upgrade a regular HTTP connection to a websocket connection
var upServer = &websocket.Upgrader{
// Define read/write buffer size
WriteBufferSize: 1024,
ReadBufferSize: 1024,
// Verification request
CheckOrigin: func(r *http.Request) bool {
// If it is not a get request, return an error
if r.Method != "GET" {
fmt.Println("Request method error")
return false
}
// If chat is not included in the path, an error is returned
if r.URL.Path != "/quotes-pin-wss" {
fmt.Println("Request path error")
return false
}
token := r.URL.Query().Get("token")
if !common.CheckToken(token) {
applogger.Debug("token expired")
return false
}
// Verification rules can also be customized according to other needs
return true
},
}
// ShareConnect
func ShareConnect(host, addr string) {
go writeShare()
go offLineStock()
go pinStock()
http.HandleFunc("/quotes-pin-wss", wsHandleShare)
url := fmt.Sprintf("%v%v", host, addr)
err := http.ListenAndServe(url, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
// 读取插针数据
func pinStock() {
for {
stockPinData()
time.Sleep(20 * time.Second)
}
}
func getPinMap(symbol string) bool {
mutexStock.RLock()
defer mutexStock.RUnlock()
return pinStockMap[symbol]
}
func setPinMap(symbol string) {
mutexPinMap.Lock()
defer mutexPinMap.Unlock()
pinStockMap[symbol] = true
applogger.Info("set pinMap", symbol)
}
func initPinMap() {
mutexPinMap.Lock()
defer mutexPinMap.Unlock()
pinStockMap = make(map[string]bool)
}
// 针对资产插针
func stockPinData() {
//清理上一次数据
initPinMap()
for k, v := range business.StockClosedDataList {
hashListName := fmt.Sprintf("STOCK_PRICES:%d", v)
keys := red.Scan(hashListName)
for _, key := range keys {
res, _ := red.HGetAll(key)
status, _ := strconv.Atoi(res["status"])
code := res["stock_code"]
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), res)
if status != business.StockStatusOn {
continue
}
symbol := fmt.Sprintf("%s.%s", code, k)
setPinMap(symbol)
mutexStock.RLock()
_, ok := wsStockConMap[symbol]
mutexStock.RUnlock()
//没有订阅没必要推送
if !ok {
applogger.Info("No subscription ", symbol)
continue
}
if k == "US" {
UsPinStock(code, res["price"], k)
} else {
stockCode := common.GetOldCode(code)
SouthAsiaPinSpot(code, stockCode, res["price"], k)
}
}
}
}
func SouthAsiaPinSpot(symbol, stockCode, price, country string) {
prices, _ := strconv.ParseFloat(price, 64)
param := models.StockParam{
Symbol: symbol,
StockCode: stockCode,
StockName: "",
Price: prices,
UpDownRate: decimal.NewFromInt(0),
UpDown: decimal.NewFromInt(0),
TradeV: decimal.NewFromInt(0),
TradeK: "买入",
Country: strings.ToLower(country),
Ts: time.Now().UnixMilli(),
ClosingMarket: true,
}
param.Token = ""
msgStr, err := json.Marshal(param)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
return
}
msgStockChan <- msgStr
}
func UsPinStock(code, price, country string) {
message := &models.ClientMessage{
S: code, // 股票代码
C: []decimal.Decimal{decimal.NewFromInt(0), decimal.NewFromFloat(1)}, // 条件,有关更多信息,请参阅贸易条件术语表
V: common.CalculateContractPrices(decimal.NewFromInt(int64(100)), float64(0.02), 0, 1)[0].IntPart(), // 交易量,代表在相应时间戳处交易的股票数量 -- 报价交易量
Dp: true, // 暗池真/假
Ms: "open", // 市场状态,指示股票市场的当前状态(“开盘”、“收盘”、“延长交易时间”)
T: time.Now().UnixMilli(), // 以毫秒为单位的时间戳 -- 此聚合窗口的结束时钟周期的时间戳(以 Unix 毫秒为单位)
Cl: decimal.RequireFromString(price), // 此聚合窗口的收盘价
A: decimal.NewFromInt(11), // 今天的成交量加权平均价格
Se: time.Now().UnixMilli(),
H: decimal.RequireFromString(price), // 此聚合窗口的最高逐笔报价
L: decimal.RequireFromString(price), // 此聚合窗口的最低价格变动价格
Op: decimal.RequireFromString(price), // 今天正式开盘价格
// P: decimal.RequireFromString(price),
ClosingMarket: true,
}
msgStr, err := json.Marshal(message)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
return
}
msgStockChan <- msgStr
}
func wsHandleShare(w http.ResponseWriter, r *http.Request) {
// Obtain a link through the upgraded upgrade tool
conn, err := upServer.Upgrade(w, r, nil)
if err != nil {
applogger.Info("Failed to obtain connection:%v", err)
return
}
// Register users after successful connection
client := &Client{
Id: uuid.NewV4().String(),
conn: conn,
msg: make(chan []byte),
symbol: sync.Map{},
mux: sync.Mutex{},
}
readShare(client)
}
func setTotalNum(num int) {
mutexTotal.Lock()
defer mutexTotal.Unlock()
TotalNum += num
}
func getTotalNum() {
mutexTotal.RLock()
defer mutexTotal.RUnlock()
applogger.Debug("number of colleagues online :%v", TotalNum)
}
// Read the message sent by the client and process the return response
func readShare(cl *Client) {
defer cl.conn.Close()
setTotalNum(1)
getTotalNum()
for {
_, msg, err := cl.conn.ReadMessage()
if err != nil {
clearClientChan <- cl.conn
applogger.Debug("user exit:%v", cl.conn.RemoteAddr().String())
return
}
// Process business logic
psgMsg := model.SubMessage(string(msg))
if psgMsg != nil {
switch psgMsg.Type {
case "ping": // Receiving ping
aloneSendStock(cl.conn, []byte(model.ReturnValue("pong")))
case "subscribe": // Receive subscription
country, stock := getCountry(psgMsg.Symbol)
//applogger.Info(country, stock)
if !dictionary.StockCountryMap[country] {
applogger.Error(country, "incorrect subscription information 不属于合规的股票市场")
aloneSendStock(cl.conn, []byte(model.ReturnValue("incorrect subscription information")))
clearClientChan <- cl.conn
return
}
aloneSendStock(cl.conn, []byte(model.ReturnValue("subscribe success")))
//获取 服务是否 订阅该数据
mutexStock.RLock()
conns, ok := wsStockConMap[psgMsg.Symbol]
mutexStock.RUnlock()
if ok {
//查询client是否订阅
conns = checkClient(conns, cl.conn)
} else {
//添加订阅
conns = make([]*websocket.Conn, 0)
conns = append(conns, cl.conn)
}
//applogger.Info("psgMsg.Symbol", psgMsg.Symbol)
mutexStock.Lock()
wsStockConMap[psgMsg.Symbol] = conns
mutexStock.Unlock()
if !ok {
go cl.userPSubscribeUs(country, stock)
}
case "unSubscribe": // Receive unsubscribe
applogger.Info("Received unsubscribe message body:", string(msg))
mutexStock.RLock()
conns, ok := wsStockConMap[psgMsg.Symbol]
mutexStock.RUnlock()
if ok {
//取消订阅
removeWsconnStock(conns, cl.conn, psgMsg.Symbol)
}
aloneSendStock(cl.conn, []byte(model.ReturnValue("unSubscribe success")))
applogger.Debug("Subscription type after current user deletion:%v", ok)
default:
// TODO: Handling other situations transmitted by customers
applogger.Debug("Please provide accurate instructions......")
}
}
}
}
// 废弃 客户端
func offLineStock() {
for userConn := range clearClientChan {
setTotalNum(-1)
getTotalNum()
for key, v := range wsStockConMap {
removeWsconnStock(v, userConn, key)
}
}
}
// send message
func aloneSendStock(conn *websocket.Conn, message []byte) error {
mutexConn.Lock()
defer mutexConn.Unlock()
//applogger.Debug("aloneSendStock :%v",conn,message)
conn.SetWriteDeadline(time.Now().Add(writeWait))
w, err := conn.NextWriter(websocket.TextMessage) // Write data in the form of io, with parameters of data type
if err != nil {
//发送失败
applogger.Error("Failed to conn.NextWriter :%v", err)
return err
}
if _, err := w.Write(message); err != nil { // Write data, this function is truly used to transmit data to the foreground
applogger.Error("Failed Write message :%v", err)
return err
}
if err := w.Close(); err != nil { // Close write stream
applogger.Error("Failed to close write stream:%v", err)
return nil
}
return nil
}
// 清理客户端
func removeWsconnStock(conn []*websocket.Conn, userConn *websocket.Conn, symbol string) error {
index := -1
for i, v := range conn {
if v == userConn {
index = i
break
}
}
if index >= 0 {
conn = append(conn[:index], conn[index+1:]...)
mutexStock.Lock()
wsStockConMap[symbol] = conn
mutexStock.Unlock()
}
return nil
}
func deleteWsconnStock(symbol string) {
mutexStock.Lock()
defer mutexStock.Unlock()
delete(wsStockConMap, symbol)
}
// 广播
func broadcastWebSocketStock(msg []byte, symbol string) {
mutexStock.RLock()
conns, ok := wsStockConMap[symbol]
mutexStock.RUnlock()
if !ok {
applogger.Error("Parsing data information:%v")
return
}
total := len(conns)
if total <= 0 {
return
}
start := time.Now()
connDiv := int(math.Ceil(float64(total) / float64(stockConnNum)))
//分批并发推送
for i := 0; i < connDiv; i++ {
startIndex := i * stockConnNum
endIndex := (i + 1) * stockConnNum
if endIndex > total {
endIndex = total
}
wg := sync.WaitGroup{}
for _, val := range conns[startIndex:endIndex] {
wg.Add(1)
go func(val *websocket.Conn, msg []byte) {
defer wg.Done()
aloneSendStock(val, msg)
}(val, msg)
}
wg.Wait()
}
applogger.Info("broadcast WebSocket info : %v ;total:%v;time-consuming %v ", symbol, total, time.Since(start))
}
func writeShare() {
for message := range msgStockChan {
var subMsg StockMessage
if err := json.Unmarshal(message, &subMsg); err != nil {
applogger.Error(err.Error())
}
if subMsg.S == "" {
if subMsg.IsStockIndex { //指数
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.StockCode, common.StockIndexPrefix))
} else if subMsg.IsOptionList { //期权列表
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.Stock, fmt.Sprintf("%s%s%s", common.StockOption, common.CapitalizeFirstLetter(subMsg.Country), common.StockOptionList)))
} else if subMsg.IsOptionInfo { //期权详情
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.Stock, fmt.Sprintf("%s%s%s", common.StockOption, common.CapitalizeFirstLetter(subMsg.Country), common.StockOptionInfo)))
} else { //tradingview
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.Symbol, common.CapitalizeFirstLetter(subMsg.Country)))
}
//applogger.Info("broadcast WebSocket Sub message %v info:%v", subMsg.Country, subMsg.StockCode)
continue
}
//美股
broadcastWebSocketStock(message, fmt.Sprintf("%s.US", subMsg.S))
//applogger.Info("broadcast WebSocket Sub message US info:%v", subMsg.S)
}
}
func checkClient(conns []*websocket.Conn, conn *websocket.Conn) []*websocket.Conn {
for _, v := range conns {
if v == conn {
return conns
}
}
conns = append(conns, conn)
return conns
}
func getCountry(symbol string) (string, string) {
symbolArr := strings.Split(symbol, ".")
if len(symbolArr) < 2 {
applogger.Error("symbol 有误")
return "", ""
}
county := symbolArr[len(symbolArr)-1]
return county, symbol[0 : strings.Index(symbol, county)-1]
}
// 按市场订阅
func (cl *Client) userPSubscribeUs(country, symbol string) {
mutexCountry.RLock()
symbols, ok := countryMap[country]
mutexCountry.RUnlock()
//提加
mutexCountry.Lock()
countryMap[country] = append(symbols, symbol)
mutexCountry.Unlock()
if ok {
//applogger.Error(country, "已订阅")
return
}
applogger.Debug(country, "start a stock subscription")
pubSub := red.RedisClient.PSubscribe(fmt.Sprintf("*.%s", country))
defer func() {
pubSub.Close()
}()
_, err := pubSub.Receive()
if err != nil {
applogger.Error("failed to receive from control PubSub,%v", zap.Error(err))
return
}
ch := pubSub.Channel()
for msg := range ch {
mutexStock.RLock()
conns, ok := wsStockConMap[msg.Channel]
mutexStock.RUnlock()
//未订阅股票跳出
if !ok {
continue
}
// TODO: 是否还有客户端
if len(conns) > 0 {
//是否有插针 必须放在下面判断,不然会被误判 客户端 断开链接
if !getPinMap(msg.Channel) {
msgStockChan <- []byte(msg.Payload)
}
} else {
deleteWsconnStock(msg.Channel)
mutexCountry.RLock()
symbols := countryMap[country]
mutexCountry.RUnlock()
index := -1
msgChannel := strings.Split(msg.Channel, ".")
for i, v := range symbols {
if v == msgChannel[0] {
index = i
break
}
}
if index >= 0 {
symbols = append(symbols[:index], symbols[index+1:]...)
mutexCountry.Lock()
countryMap[country] = symbols
mutexCountry.Unlock()
}
// 退订
if len(symbols) <= 0 {
mutexCountry.Lock()
delete(countryMap, country)
mutexCountry.Unlock()
applogger.Debug("Starting unsubscribe.....", country)
pubSub.PUnsubscribe(fmt.Sprintf("*.%s", country))
return
}
}
}
}

223
cmd/common/base.go

@ -0,0 +1,223 @@
package common
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"regexp"
"strconv"
"strings"
"wss-pool/config"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/internal/redis"
"wss-pool/logging/applogger"
)
const (
StockProject string = "stock"
CoinProject string = "coin"
StockOn string = "1"
StockIndexOn int = 1
StockIndexPrefix string = "StockIndex"
StockOption string = "Option"
StockOptionList string = "List"
StockOptionInfo string = "Info"
)
var CountryStartTime = map[string]int64{
"India": 42300000,
"Thailand": 39000000,
"Indonesia": 36000000,
"Malaysia": 32400000,
"Singapore": 32400000,
"UK": 54000000,
"France": 54000000,
"Germany": 54000000,
"Brazil": 75600000,
"Japan": 42300000,
}
var StockToPHPMap = map[string]string{
"US": "US",
"Thailand": "THA",
"India": "IN",
"Indonesia": "IDN",
"Malaysia": "MYS",
"Singapore": "SGD",
"HongKong": "HKD",
"UK": "UK",
"Germany": "EUR",
"France": "FUR",
"Brazil": "BR",
"Japan": "JP",
}
var path = map[string]bool{
"/spots/update/keep": true,
"/stock/spots/update/keep": true,
"/stock/spots/list/new/add": true,
"/spots/list/new/add": true,
"/spots/news/add": true,
"/spots/index/list/new/add": true,
"/stock/spots/index/list/new/add": true,
"/option/php/list": true,
"/stock/option/php/list": true,
"/spots/php/update": true,
"/stock/spots/php/update": true,
}
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
// JWTAuthMiddleware 基于JWT的认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
if path[c.FullPath()] {
c.Next()
return
}
token := strings.TrimSpace(c.Request.Header.Get("token"))
if token == "" {
c.Abort()
JsonResult(401, "can't find the token", "", c)
return
}
//applogger.Debug("鉴权Token用户:%v", fmt.Sprintf("TOKEN:USER:%s", token))
userId, _ := redis.Get_Cache_Data(fmt.Sprintf("TOKEN:USER:%s", token))
if userId == "" || userId == "0" {
c.Abort()
JsonResult(401, "token expired", "", c)
return
}
}
}
func CheckToken(token string) bool {
if token == "" {
return false
}
userId, _ := redis.Get_Cache_Data(fmt.Sprintf("TOKEN:USER:%s", token))
if userId == "" || userId == "0" {
return false
}
return true
}
func JsonResult(code int, msg string, data interface{}, c *gin.Context) {
// 开始时间
c.JSON(http.StatusOK, Response{
code,
msg,
data,
})
}
func IsLetter(str string) bool {
reg := regexp.MustCompile(`^[a-zA-Z0-9&-]+$`)
return reg.MatchString(str)
}
func DelRes(hashListName, str string) {
redis.RedisClient = redis.RedisInit(config.Config.Redis.DbEleven)
keys, _ := redis.HGetAll(hashListName)
for key, _ := range keys {
if strings.Contains(key, str) {
redis.HDel(hashListName, key)
fmt.Println(key)
}
}
//if len(k)> 0 {
// redis.HDel(hashListName, strings.Join(k, ","))
// k = make([]string,0)
//}
}
func IsExistStock(country, code string) bool {
key := fmt.Sprintf("%s:STOCK:LIST:%s", StockToPHPMap[country], code)
status, _ := redis.Hget(key, "status")
if status == StockOn {
return true
}
return false
}
func IsExistStockNew(country, code string, redisIp string) bool {
red := redis.RedisClientMap[redisIp]
key := fmt.Sprintf("%s:STOCK:LIST:%s", StockToPHPMap[country], code)
status, _ := redis.HGetNew(key, "status", red)
if status == StockOn {
return true
}
return false
}
func IsExistOption(country, code string) (bool, float64) {
key := fmt.Sprintf("%s:OPTION:LIST:%s", StockToPHPMap[country], code)
status, _ := redis.Hget(key, "status")
//fmt.Println(country,code,status)
if status == StockOn {
rate, _ := redis.Hget(key, "rate")
rateFloat, _ := strconv.ParseFloat(rate, 64)
return true, rateFloat
}
return false, 0
}
func TgBotSendMsg(msg string) {
if _, err := internal.HttpPost(config.Config.TgBot.URL, fmt.Sprintf(`{"text":"%s","chat_id":%d}`, msg, config.Config.TgBot.ChatId)); err != nil {
applogger.Error("TgBotSendMsg", err)
}
}
func UPdateAll(country string) {
data.Mgo_init(config.Config.Mongodb)
var per = []string{
"1day",
"1week",
"1mon",
}
for _, v := range per {
UPdateTime(1696089600000, 1700796517000, country, v)
}
}
func UPdateTime(from, to int64, country, period string) {
//filter := bson.M{"timestamp": bson.M{"$gte": from, "$lte": to}}
//tableName := data.GetStockSouthAsiaTableName(country, period)
//res := make([]model.StockMogoParam, 0)
//projection := bson.M{"symbol": 1, "stock_code": 1, "country": 1, "timestamp": 1}
//sort := bson.M{"timestamp": 1}
//data.MgoFindProjectionRes(tableName, filter, projection, sort, &res, 0)
//if len(res) <= 0 {
// applogger.Error(" no data", period)
// return
//}
//var key = make(map[int64]bool)
//for _, v := range res {
// utcTime := time.Unix(v.Ts/1000, 0)
// location, _ := time.LoadLocation("Asia/Singapore")
// t := utcTime.In(location)
// if t.Hour() != 0 {
// continue
// }
// l := len(key)
// key[v.Ts] = true
// if l == len(key){
// continue
// }
// fmt.Println(v.Ts)
// filter = bson.M{"timestamp": bson.M{"$eq": v.Ts}}
// update := bson.D{{"$set", bson.D{
// {"timestamp", v.Ts + CountryStartTime[country]},
// }}}
// applogger.Info("GetTimeNewPriceAll info: %v %v", update,v)
// fmt.Println(data.GetStockSouthAsiaTableName(country, period))
// if err := data.MgoUpdateMany(data.GetStockSouthAsiaTableName(country, period), filter,update); err != nil {
// applogger.Error("stock MgoInsertMany err:%v", err)
// }
//}
}

1122
cmd/common/common.go

File diff suppressed because it is too large

71
cmd/common/gzip.go

@ -0,0 +1,71 @@
package common
import (
"bytes"
"compress/gzip"
"encoding/json"
)
// 压缩 与json搭配使用
func MarshalToJsonWithGzip(jsonData interface{}) []byte {
dataAfterMarshal, _ := json.Marshal(jsonData)
dataAfterGzip, err := Encode(dataAfterMarshal)
if err != nil {
return nil
}
return dataAfterGzip
}
// 解压 与json搭配使用
func UnmarshalDataFromJsonWithGzip(msg []byte) (*struct{}, error) {
dataAfterDecode, err := Decode(msg)
if err != nil {
return nil, err
}
data := &struct {
}{}
err = json.Unmarshal(dataAfterDecode, data)
if err != nil {
return nil, err
}
return data, nil
}
// Gzip用法 压缩数据
func Encode(input []byte) ([]byte, error) {
// 创建一个新的 byte 输出流
var buf bytes.Buffer
// 创建一个新的 gzip 输出流
gzipWriter := gzip.NewWriter(&buf)
// 将 input byte 数组写入到此输出流中
_, err := gzipWriter.Write(input)
if err != nil {
_ = gzipWriter.Close()
return nil, err
}
if err := gzipWriter.Close(); err != nil {
return nil, err
}
// 返回压缩后的 bytes 数组
return buf.Bytes(), nil
}
// Gzip用法 解压数据
func Decode(input []byte) ([]byte, error) {
// 创建一个新的 gzip.Reader
bytesReader := bytes.NewReader(input)
gzipReader, err := gzip.NewReader(bytesReader)
if err != nil {
return nil, err
}
defer func() {
// defer 中关闭 gzipReader
_ = gzipReader.Close()
}()
buf := new(bytes.Buffer)
// 从 Reader 中读取出数据
if _, err := buf.ReadFrom(gzipReader); err != nil {
return nil, err
}
return buf.Bytes(), nil
}

671
cmd/common/notStock.go

@ -0,0 +1,671 @@
package common
import (
"bufio"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"os"
"strings"
"sync"
"time"
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
)
var validStockCodeMutex = sync.RWMutex{}
var validStockCode = map[string]bool{}
var NotStockCode = map[string]bool{
"BSE:20MICRONS": true,
"BSE:21STCENMGM": true,
"BSE:360ONE": true,
"BSE:3IINFOLTD": true,
"BSE:3MINDIA": true,
"BSE:3PLAND": true,
"BSE:5PAISA": true,
"BSE:63MOONS": true,
"BSE:A2ZINFRA": true,
"BSE:AAATECH": true,
"BSE:AAREYDRUGS": true,
"BSE:AARTECH": true,
"BSE:AARTIDRUGS": true,
"BSE:AARTIIND": true,
"BSE:AARTIPHARM": true,
"BSE:AARVEEDEN": true,
"BSE:AAVAS": true,
"BSE:ABAN": true,
"BSE:ABB": true,
"BSE:ABBOTINDIA": true,
"BSE:ABCAPITAL": true,
"BSE:ABFRL": true,
"BSE:ABSLAMC": true,
"BSE:ACC": true,
"BSE:ACCELYA": true,
"BSE:ACE": true,
"BSE:ACI": true,
"BSE:ACL": true,
"BSE:ADANIENSOL": true,
"BSE:ADANIENT": true,
"BSE:ADANIGREEN": true,
"BSE:ADANIPORTS": true,
"BSE:ADANIPOWER": true,
"BSE:ADFFOODS": true,
"BSE:ADL": true,
"BSE:ADORWELD": true,
"BSE:ADROITINFO": true,
"BSE:ADSL": true,
"BSE:ADVANIHOTR": true,
"BSE:ADVENZYMES": true,
"BSE:AEROFLEX": true,
"BSE:AETHER": true,
"BSE:AFFLE": true,
"BSE:AGARIND": true,
"BSE:AGI": true,
"BSE:AGRITECH": true,
"BSE:AGSTRA": true,
"BSE:AHL": true,
"BSE:AHLEAST": true,
"BSE:AHLUCONT": true,
"BSE:AIAENG": true,
"BSE:AIRAN": true,
"BSE:AJANTPHARM": true,
"BSE:AJMERA": true,
"BSE:AKI": true,
"BSE:AKSHAR": true,
"BSE:AKSHARCHEM": true,
"BSE:AKSHOPTFBR": true,
"BSE:AKZOINDIA": true,
"BSE:ALANKIT": true,
"BSE:ALBERTDAVD": true,
"BSE:ALEMBICLTD": true,
"BSE:ALICON": true,
"BSE:ALKALI": true,
"BSE:ALKEM": true,
"BSE:ALKYLAMINE": true,
"BSE:ALLCARGO": true,
"BSE:ALLSEC": true,
"BSE:ALMONDZ": true,
"BSE:ALOKINDS": true,
"BSE:ALPA": true,
"BSE:ALPHAGEO": true,
"BSE:ALPSINDUS": true,
"BSE:AMBER": true,
"BSE:AMBICAAGAR": true,
"BSE:AMBIKCO": true,
"BSE:AMBUJACEM": true,
"BSE:AMDIND": true,
"BSE:AMIORG": true,
"BSE:AMJLAND": true,
"NSE:AMNPLST": true,
"BSE:AMRUTANJAN": true,
"BSE:ANANDRATHI": true,
"BSE:ANANTRAJ": true,
"BSE:ANDHRAPAP": true,
"BSE:ANDHRSUGAR": true,
"BSE:ANDREWYU": true,
"BSE:ANGELONE": true,
"BSE:ANIKINDS": true,
"BSE:ANKITMETAL": true,
"BSE:ANMOL": true,
"BSE:ANUP": true,
"BSE:ANURAS": true,
"BSE:APARINDS": true,
"BSE:APCL": true,
"BSE:APCOTEXIND": true,
"BSE:APEX": true,
"BSE:APLAPOLLO": true,
"BSE:APLLTD": true,
"BSE:APOLLO": true,
"BSE:APOLLOHOSP": true,
"BSE:APOLLOPIPE": true,
"BSE:APOLLOTYRE": true,
"BSE:APTECHT": true,
"BSE:APTUS": true,
"BSE:ARCHIDPLY": true,
"BSE:ARCHIES": true,
"BSE:ARENTERP": true,
"BSE:ARIES": true,
"BSE:ARIHANTCAP": true,
"BSE:ARIHANTSUP": true,
"BSE:ARMANFIN": true,
"BSE:AROGRANITE": true,
"BSE:ARROWGREEN": true,
"BSE:ARSHIYA": true,
"BSE:ARTEMISMED": true,
"BSE:ARVIND": true,
"BSE:ARVINDFASN": true,
"BSE:ARVSMART": true,
"BSE:ASAHIINDIA": true,
"BSE:ASAHISONG": true,
"BSE:ASAL": true,
"BSE:ASALCBR": true,
"BSE:ASHAPURMIN": true,
"BSE:ASHIANA": true,
"BSE:ASHIMASYN": true,
"BSE:ASHOKA": true,
"BSE:ASHOKLEY": true,
"BSE:ASIANENE": true,
"BSE:ASIANHOTNR": true,
"BSE:ASIANPAINT": true,
"BSE:ASIANTILES": true,
"BSE:ASMS": true,
"BSE:ASTEC": true,
"BSE:ASTERDM": true,
"BSE:ASTRAL": true,
"BSE:ASTRAMICRO": true,
"BSE:ASTRAZEN": true,
"BSE:ASTRON": true,
"BSE:ATALREAL": true,
"BSE:ATAM": true,
"BSE:ATFL": true,
"BSE:ATGL": true,
"BSE:ATL": true,
"BSE:ATUL": true,
"BSE:ATULAUTO": true,
"BSE:AUBANK": true,
"BSE:AURIONPRO": true,
"BSE:AUROPHARMA": true,
"BSE:AURUM": true,
"BSE:AUSOMENT": true,
"BSE:AUTOAXLES": true,
"BSE:AUTOIND": true,
"BSE:AVADHSUGAR": true,
"BSE:AVALON": true,
"BSE:AVANTIFEED": true,
"BSE:AVG": true,
"BSE:AVONMORE": true,
"BSE:AVROIND": true,
"BSE:AVTNPL": true,
"BSE:AWHCL": true,
"BSE:AWL": true,
"BSE:AXISBANK": true,
"BSE:AXISCADES": true,
"BSE:AXITA": true,
"BSE:AYMSYNTEX": true,
"BSE:BAFNAPH": true,
"BSE:BAGFILMS": true,
"BSE:BAIDFIN": true,
"BSE:BAJAJCON": true,
"BSE:BAJAJELEC": true,
"BSE:BAJAJFINSV": true,
"BSE:BAJAJHCARE": true,
"BSE:BAJAJHIND": true,
"BSE:BAJAJHLDNG": true,
"BSE:BAJFINANCE": true,
"BSE:BALAJITELE": true,
"BSE:BALAMINES": true,
"BSE:BALKRISHNA": true,
"BSE:BALKRISIND": true,
"BSE:BALMLAWRIE": true,
"BSE:BALPHARMA": true,
"BSE:BALRAMCHIN": true,
"BSE:BANARBEADS": true,
"BSE:BANARISUG": true,
"BSE:BANCOINDIA": true,
"BSE:BANDHANBNK": true,
"BSE:BANG": true,
"BSE:BANKBARODA": true,
"BSE:BANKINDIA": true,
"BSE:BANSWRAS": true,
"BSE:BARBEQUE": true,
"BSE:BASF": true,
"BSE:BASML": true,
"BSE:BATAINDIA": true,
"BSE:BAYERCROP": true,
"BSE:BBL": true,
"BSE:BBOX": true,
"BSE:BBTC": true,
"BSE:BBTCL": true,
"BSE:BCG": true,
"BSE:BCLIND": true,
"BSE:BCONCEPTS": true,
"BSE:BDL": true,
"BSE:BEARDSELL": true,
"BSE:BECTORFOOD": true,
"BSE:BEDMUTHA": true,
"BSE:BEL": true,
"BSE:BEML": true,
"BSE:BEPL": true,
"BSE:BERGEPAINT": true,
"BSE:BFINVEST": true,
"BSE:BFUTILITIE": true,
"BSE:BGRENERGY": true,
"BSE:BHAGCHEM": true,
"BSE:BHAGERIA": true,
"BSE:BHAGYANGR": true,
"BSE:BHANDARI": true,
"BSE:BHARATFORG": true,
"BSE:BHARATGEAR": true,
"BSE:BHARATRAS": true,
"BSE:BHARATWIRE": true,
"BSE:BHARTIARTL": true,
"BSE:BHEL": true,
"BSE:BIGBLOC": true,
"BSE:BIKAJI": true,
"BSE:BIL": true,
"BSE:BINANIIND": true,
"BSE:BIOCON": true,
"BSE:BIOFILCHEM": true,
"BSE:BIRLACABLE": true,
"BSE:BIRLACORPN": true,
"BSE:BIRLAMONEY": true,
"BSE:BKMINDST": true,
"BSE:BLAL": true,
"BSE:BLBLIMITED": true,
"BSE:BLISSGVS": true,
"BSE:BLKASHYAP": true,
"BSE:BLS": true,
"BSE:BLUECHIP": true,
"BSE:BLUEDART": true,
"BSE:BLUESTARCO": true,
"BSE:BODALCHEM": true,
"BSE:BOMDYEING": true,
"BSE:BOROLTD": true,
"BSE:BORORENEW": true,
"BSE:BOSCHLTD": true,
"BSE:BPCL": true,
"BSE:BPL": true,
"BSE:BRIGADE": true,
"NSE:BRIGHT": true,
"BSE:BRITANNIA": true,
"BSE:BRNL": true,
"BSE:BROOKS": true,
"BSE:BSL": true,
"BSE:BSOFT": true,
"BSE:BTML": true,
"BSE:BURNPUR": true,
"BSE:BUTTERFLY": true,
"BSE:BVCL": true,
"BSE:BYKE": true,
"BSE:CALSOFT": true,
"BSE:CAMLINFINE": true,
"BSE:CAMPUS": true,
"BSE:CAMS": true,
"BSE:CANBK": true,
"BSE:CANFINHOME": true,
"BSE:CANTABIL": true,
"BSE:CAPACITE": true,
"BSE:CAPLIPOINT": true,
"BSE:CAPTRUST": true,
"BSE:CARBORUNIV": true,
"BSE:CAREERP": true,
"BSE:CARERATING": true,
"BSE:CARTRADE": true,
"BSE:CARYSIL": true,
"BSE:CASTROLIND": true,
"BSE:CCHHL": true,
"BSE:CCL": true,
"BSE:CEATLTD": true,
"BSE:CELEBRITY": true,
"BSE:CENTENKA": true,
"BSE:CENTEXT": true,
"BSE:CENTRALBK": true,
"BSE:CENTRUM": true,
"BSE:CENTUM": true,
"BSE:CENTURYPLY": true,
"BSE:CENTURYTEX": true,
"BSE:CERA": true,
"BSE:CEREBRAINT": true,
"BSE:CESC": true,
"BSE:CGCL": true,
"BSE:CGPOWER": true,
"BSE:CHALET": true,
"BSE:CHAMBLFERT": true,
"BSE:CHEMBOND": true,
"BSE:CHEMCON": true,
"BSE:CHEMFAB": true,
"BSE:CHEMPLASTS": true,
"BSE:CHENNPETRO": true,
"BSE:CHEVIOT": true,
"BSE:CHOICEIN": true,
"BSE:CHOLAFIN": true,
"BSE:CHOLAHLDNG": true,
"BSE:CIEINDIA": true,
"BSE:CIGNITITEC": true,
"BSE:CINELINE": true,
"BSE:CINEVISTA": true,
"BSE:CIPLA": true,
"BSE:CLEAN": true,
"BSE:CLEDUCATE": true,
"BSE:CLSEL": true,
"BSE:CMSINFO": true,
"BSE:COALINDIA": true,
"BSE:COASTCORP": true,
"BSE:COCHINSHIP": true,
"BSE:COFFEEDAY": true,
"BSE:COFORGE": true,
"BSE:COLPAL": true,
"BSE:COMPINFO": true,
"BSE:COMPUSOFT": true,
"BSE:CONCOR": true,
"BSE:CONCORDBIO": true,
"BSE:CONFIPET": true,
"BSE:CONTROLPR": true,
"BSE:CORALFINAC": true,
"BSE:CORDSCABLE": true,
"BSE:COROMANDEL": true,
"BSE:COSMOFIRST": true,
"BSE:COUNCODOS": true,
"BSE:CRAFTSMAN": true,
"NSE:CREATIVE": true,
"BSE:CREATIVEYE": true,
"BSE:CREDITACC": true,
"BSE:CREST": true,
"BSE:CRISIL": true,
"BSE:CROMPTON": true,
"BSE:CSBBANK": true,
"BSE:CSLFINANCE": true,
"BSE:CTE": true,
"BSE:CUB": true,
"BSE:CUBEXTUB": true,
"BSE:CUMMINSIND": true,
"BSE:CUPID": true,
"BSE:CYBERMEDIA": true,
"BSE:CYBERTECH": true,
"BSE:CYIENT": true,
"BSE:CYIENTDLM": true,
"BSE:DABUR": true,
"BSE:DALBHARAT": true,
"BSE:DALMIASUG": true,
"BSE:DAMODARIND": true,
"BSE:DATAMATICS": true,
"BSE:DATAPATTNS": true,
"BSE:DBCORP": true,
"BSE:DBL": true,
"BSE:DBOL": true,
"BSE:DBREALTY": true,
"BSE:DBSTOCKBRO": true,
"BSE:DCAL": true,
"BSE:DCBBANK": true,
"BSE:DCI": true,
"BSE:DCM": true,
"BSE:DCMFINSERV": true,
"BSE:DCMNVL": true,
"BSE:DCMSHRIRAM": true,
"BSE:DCMSRIND": true,
"BSE:DCW": true,
"BSE:DCXINDIA": true,
"BSE:DECCANCE": true,
"BSE:DEEPAKFERT": true,
"BSE:DEEPAKNTR": true,
"BSE:DEEPENR": true,
"BSE:DEEPINDS": true,
"BSE:DELHIVERY": true,
"BSE:DELPHIFX": true,
"BSE:DELTACORP": true,
"BSE:DELTAMAGNT": true,
"BSE:DEN": true,
"BSE:DENORA": true,
"BSE:DEVIT": true,
"BSE:DEVYANI": true,
"BSE:DGCONTENT": true,
"BSE:DHAMPURSUG": true,
"BSE:DHANBANK": true,
"BSE:DHANI": true,
"BSE:DHANUKA": true,
"BSE:DHARMAJ": true,
"BSE:DHRUV": true,
"BSE:DHUNINV": true,
"BSE:DIACABS": true,
"BSE:DIAMINESQ": true,
"BSE:DIAMONDYD": true,
"BSE:DICIND": true,
"BSE:DIGISPICE": true,
"BSE:DISHTV": true,
"BSE:DIVGIITTS": true,
"BSE:DIVISLAB": true,
"BSE:DIXON": true,
"BSE:DJML": true,
"BSE:DLF": true,
"BSE:DLINKINDIA": true,
"BSE:DOLPHIN": true,
"BSE:DPWIRES": true,
"NSE:DRL": true,
"BSE:DTIL": true,
"BSE:DYCL": true,
"BSE:EMSLIMITED": true,
"BSE:EPIGRAL": true,
"BSE:FINPIPE": true,
"BSE:FOCUS": true,
"BSE:GANGESSECU": true,
"BSE:GENSOL": true,
"BSE:GHCLTEXTIL": true,
"BSE:GMMPFAUDLR": true,
"BSE:GREENLAM": true,
"BSE:GSTL": true,
"BSE:GUJGASLTD": true,
"BSE:GUJRAFFIA": true,
"BSE:HBSL": true,
"BSE:HISARMETAL": true,
"BSE:HMAAGRO": true,
"BSE:IDEAFORGE": true,
"BSE:IKIO": true,
"BSE:INNOVATIVE": true,
"BSE:IRBINVIT": true,
"BSE:JAYSREETEA": true,
"BSE:JIOFIN": true,
"BSE:JLHL": true,
"BSE:JSWINFRA": true,
"BSE:KALAMANDIR": true,
"BSE:KDL": true,
"BSE:KEYFINSERV": true,
"NSE:KORE": true,
"BSE:KPIL": true,
"BSE:KRIDHANINF": true,
"BSE:KRITI": true,
"BSE:LINCOLN": true,
"BSE:LLOYDSME": true,
"BSE:LOTUSEYE": true,
"BSE:MAGADSUGAR": true,
"BSE:MANAKALUCO": true,
"BSE:MANAKCOAT": true,
"BSE:MANAKSTEEL": true,
"BSE:MAXIND": true,
"BSE:MAZDA": true,
"BSE:MICEL": true,
"BSE:MKPL": true,
"BSE:MOLDTECH": true,
"BSE:MSTCLTD": true,
"BSE:MUTHOOTCAP": true,
"BSE:MVGJL": true,
"BSE:NAGAFERT": true,
"BSE:NETWEB": true,
"BSE:NIITMTS": true,
"BSE:NILAINFRA": true,
"BSE:NRAIL": true,
"BSE:NUVAMA": true,
"BSE:OCCL": true,
"BSE:ORCHPHARMA": true,
"BSE:ORICONENT": true,
"BSE:ORTINLAB": true,
"BSE:PAKKA": true,
"BSE:PALASHSECU": true,
"BSE:PALREDTEC": true,
"BSE:PAVNAIND": true,
"BSE:PODDARHOUS": true,
"BSE:PODDARMENT": true,
"BSE:PRECISION": true,
"BSE:PREMEXPLN": true,
"BSE:PROZONER": true,
"BSE:PTCIL": true,
"BSE:PYRAMID": true,
"BSE:RAJRATAN": true,
"BSE:RAJRILTD": true,
"BSE:RATNAVEER": true,
"BSE:REDTAPE": true,
"BSE:RHFL": true,
"BSE:RISHABH": true,
"BSE:ROML": true,
"BSE:RRKABEL": true,
"BSE:RSYSTEMS": true,
"BSE:SAFARI": true,
"BSE:SALONA": true,
"BSE:SALZERELEC": true,
"BSE:SAMHI": true,
"BSE:SAMPANN": true,
"BSE:SANDUMA": true,
"BSE:SBCL": true,
"BSE:SBFC": true,
"BSE:SEJALLTD": true,
"NSE:SEL": true,
"BSE:SELMC": true,
"BSE:SENCO": true,
"NSE:SENSEXETF": true,
"BSE:SHARDAMOTR": true,
"NSE:SHEETAL": true,
"BSE:SHIVATEX": true,
"BSE:SHYAMCENT": true,
"BSE:SIGIND": true,
"BSE:SIGMA": true,
"BSE:SIGNATURE": true,
"BSE:SINDHUTRAD": true,
"BSE:SOMICONVEY": true,
"BSE:SOUTHWEST": true,
"BSE:SPENCERS": true,
"BSE:SRGHFL": true,
"BSE:SUBEXLTD": true,
"BSE:TASTYBITE": true,
"BSE:TECILCHEM": true,
"BSE:TITAGARH": true,
"BSE:TPLPLASTEH": true,
"BSE:TREL": true,
"BSE:TTKHLTCARE": true,
"BSE:TVSSCS": true,
"BSE:UCAL": true,
"BSE:UDS": true,
"BSE:UMANGDAIRY": true,
"BSE:UNIENTER": true,
"BSE:URAVI": true,
"BSE:URJA": true,
"BSE:UTKARSHBNK": true,
"BSE:VALIANTLAB": true,
"BSE:VENKEYS": true,
"BSE:VIJIFIN": true,
"BSE:VIPULLTD": true,
"BSE:VLEGOV": true,
"BSE:VPRPL": true,
"NSE:WORTH": true,
"BSE:WSI": true,
"BSE:YASHO": true,
"BSE:YATHARTH": true,
"BSE:YATRA": true,
"NSE:ZEAL": true,
"BSE:BLUECOAST": true,
"BSE:DIGJAMLMTD": true,
"BSE:GATECH": true,
"NSE:SECMARK": true,
"BSE:KEL": true,
"BSE:PLAZACABLE": true,
"BSE:SICALLOG": true,
"BSE:LLOYDSENGG": true,
"NSE:MAL": true,
"NSE:TCLCONS": true,
"BSE:TPHQ": true,
"BSE:SURAJEST": true,
"BSE:NCC": true,
}
func GetIndiaStock() {
for {
filter := bson.M{"Country": "India"}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
continue
}
//start := time.Now() // 获取当前时间
validStockCodeMutex.Lock()
if len(dateList.([]primitive.M)) > 0 {
validStockCode = map[string]bool{}
}
for _, value := range dateList.([]primitive.M) {
if value["Exchange"] == nil || value["Code"] == nil {
continue
}
code := value["Code"].(string)
exchange := value["Exchange"].(string)
validStockCode[fmt.Sprintf("%s:%s", exchange, code)] = true
}
validStockCodeMutex.Unlock()
//fmt.Println("Run time: ", time.Since(start))
applogger.Info("india stock number :%v", len(validStockCode))
time.Sleep(1 * time.Hour)
}
}
func GetIndiaStockBool(key, country string) bool {
if country != "India" {
return true
}
validStockCodeMutex.RLock()
defer validStockCodeMutex.RUnlock()
return validStockCode[key]
}
func ReadTest(rename string) {
data.Mgo_init(config.Config.Mongodb)
file, err := os.Open(rename)
if err != nil {
// 错误处理
fmt.Println("Error opening file:", err)
return
}
defer file.Close() // 确保在函数结束时关闭文件
scanner := bufio.NewScanner(file)
stockList := make([]string, 0)
// i := 0
for scanner.Scan() {
//i++
// scanner.Text() 返回当前行的内容
// fmt.Println(strings.TrimSpace(scanner.Text()),i)
filter := bson.M{"Country": "India", "Code": strings.TrimSpace(scanner.Text())}
dateList, _ := data.MgoFind(data.StockList, filter)
res := dateList.([]primitive.M)
if len(res) <= 0 {
fmt.Println(res)
continue
} else if len(res) >= 2 {
fmt.Println(res)
break
}
str, ok := res[0]["Exchange"].(string)
if !ok {
fmt.Println(res)
continue
}
exchange := "BSE"
if str == "BSE" {
exchange = "NSE"
}
stockList = append(stockList, fmt.Sprintf("%s:%s", exchange, strings.TrimSpace(scanner.Text())))
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading file:", err)
}
write(stockList)
}
func write(param []string) {
fmt.Println(len(param))
filename := "example.txt"
// 打开文件以追加数据,如果文件不存在则创建它
file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
// 要追加的内容
for _, value := range param {
content := fmt.Sprintf(`"%s":true, %s`, value, "\n")
// 写入数据
_, err = file.WriteString(content)
if err != nil {
fmt.Println("Error writing to file:", err)
return
}
}
}

115
cmd/main.go

@ -0,0 +1,115 @@
package main
import (
"flag"
"wss-pool/cmd/servicemanager"
"wss-pool/cmd/websocketcollect/forex"
"wss-pool/cmd/websocketcollect/us"
"wss-pool/config"
"wss-pool/internal/data/business"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var (
BuildTime string
configName = flag.String("config", "./config/config.yaml", "choose service")
// service selection
checkInt = flag.String("check", "gin", "choose service")
// Service Method Selection
checkStr = flag.String("model", "spots", "choose service")
// Service IP and Port Configuration [10.148.0.7,10.148.0.5]
ipServer = flag.String("hostS", "0.0.0.0", "Server distribution IP")
addrServer = flag.String("addrS", ":8861", "Server distribution Post")
contractCode = flag.String("contract", "", "Server distribution Post")
project = flag.String("project", "", "Server distribution project")
stockTs = flag.Int64("stockTs", 0, "")
)
func init() {
applogger.Info("build time:", BuildTime)
flag.Parse()
config.LoadConfig(*configName)
}
func main() {
applogger.Info("gather service start")
applogger.Info("intService---checkBool:%v,ginIp:%v,ginPost:%v,checkStr:%v", *checkInt, *ipServer, *addrServer, *checkStr)
switch *checkInt {
case model.Gin: // Http查询服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.Gather: // 源订阅-火币市场行情采集
servicemanager.Gather(*checkStr, *ipServer, *addrServer)
case model.CurrencyWss: // 项目-行情Wss订阅
servicemanager.Currency(*ipServer, *addrServer)
case model.CollectUs: // 美股-行情采集分发
us.SubscribeShareUs(*ipServer, *addrServer)
case model.GatherUs: // 项目-美股市场行情采集
servicemanager.GatherUS(*checkStr, *ipServer, *addrServer)
case model.CollectForex: // 外汇-行情采集分发
forex.SubscribeForex(*ipServer, *addrServer)
case model.GatherForex: // 项目-外汇市场行情采集
servicemanager.GatherForex(*checkStr, *ipServer, *addrServer)
case model.ShareWss: // 股票市场-行情Wss订阅
servicemanager.ShareWss(*ipServer, *addrServer)
case model.PinWs: // 股票市场-插针行情Wss订阅
servicemanager.PinWs(*ipServer, *addrServer)
case model.TickDB: // 数字币|股票(mongodb)-数据优化
servicemanager.TickDB(*checkStr, *ipServer, *addrServer, *contractCode)
case model.SelfContract: // 合约服务
servicemanager.SelfContract(*checkStr, *ipServer, *addrServer, *contractCode)
case model.SelfMarketSpot: // 现货服务
servicemanager.SelfMarketSpot(*checkStr, *ipServer, *addrServer, *contractCode)
case model.StockIndex: // 指数服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.IndiaOption: // 印度期权服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.USStock: // 美股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.IndonesiaStock: // 印尼股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.ThailandStock: // 泰股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.IndiaStock: // 印度股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.MalaysiaStock: // 马来西亚股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.HongKongStock: // 港股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.SingaporeStock: // 新加坡股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.UKStock: // 英股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.GermanyStock: // 德股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.FranceStock: // 法股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.BrazilStock: // 巴西股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.JapanStock: // 日本股服务
servicemanager.GinServer(*ipServer, *addrServer, *checkInt, *project)
case model.StockData: // mongodb-倒数据
business.SymbolToStock(*project)
case model.StockDataUs: // mongodb-倒数据
business.SymbolToStockList(*project)
case model.StockDataInfo: // mongodb-倒详情数据
business.SymbolToStockInfo(*project)
case model.StockDataNews: // 股票市场数据更新
business.SymbolNews(*project)
case model.StockCode: // 更新股票市场代码列表(老版本)
business.SymbolCode(*project)
case model.DelOptionHash: // 删除期权hash
business.DelOptionHash()
case model.SendIndiaInfo: // 发送给PHP印度股票信息
business.SendIndiaInfo()
case model.MalaysiaStockUpdate: // 新增马来西亚数字代码
business.MalaysiaStockUpdate()
case model.DeleteIndia: // 清理印度市场后台没有权限的股票(优化印度股票k线-mongodb压力)
business.DeleteSpotDay(*project, *stockTs)
case model.ForexToExcel: // 导出外汇股票代码
business.TickerToExcel()
default:
applogger.Debug("Please select the startup ID......")
}
}

278
cmd/marketwsscliert/marketcontract.go

@ -0,0 +1,278 @@
package marketwsscliert
import (
"encoding/json"
"fmt"
"wss-pool/cmd/websocketservice"
"wss-pool/config"
"wss-pool/internal/data/business"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/hbwssclient/marketwssclient"
"wss-pool/pkg/model/market"
)
/*U本币合约数据
https://huobiapi.github.io/docs/usdt_swap/v1/cn/
*/
// subscribeCtKline 合约kline数据
func subscribeCtKline(symbolList map[string][]string) {
client := new(marketwssclient.ContractKLineWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, period := range symbolList {
for _, value := range period {
client.Subscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtKlineResponse)
if ok {
if &resp != nil {
if resp.Tick != nil || resp.Data != nil {
//插针
resp = RunModify(resp)
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: resp.Channel})
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
//实时数据入库
go business.UpdateSubscribeCtKline(resp)
applogger.Info("subscribeCtKline data,ServersId:%v,Sender:%v,Content:%v-%v", resp.Channel, Cli.Id, resp.Tick, resp.Data)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// subscribeCtDepth 合约深度数据源
func subscribeCtDepth(symbolList map[string][]string) {
//改为一对一 发送
for symbol, period := range symbolList {
for _, value := range period {
topic := fmt.Sprintf("market.%s.depth.%s", symbol, value)
client := new(marketwssclient.ContractDepthWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
client.Subscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
},
func(response interface{}) {
resp, ok := response.(interface{})
if ok {
if &resp != nil {
//jsonMessage, _ := json.Marshal(websocketservice.Message{
// ServersId: topic,
// Sender: Cli.Id,
// Content: resp,
// Symbol: topic})
//合约深度压缩数据
red.RedisClient.Publish(topic, resp)
applogger.Info("subscribeCtDepth data,ServersId:%v,Sender:%v,Content:%v", topic, Cli.Id)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
defer client.Close()
}
}
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
client := new(marketwssclient.ContractDepthWebSocketClient).Init(config.Config.HbContract.HbContractHost)
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// subscribeCtAddDepth 合约深度增量数据
func subscribeCtAddDepth(symbolList map[string][]string) {
client := new(marketwssclient.ContractDepthSizeWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, period := range symbolList {
for _, value := range period {
client.Subscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtAddDepthResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: resp.Channel})
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
applogger.Info("subscribeCtAddDepth data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, Cli.Id, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// subscribeCtBbo TODO: 合约买一卖一逐笔行情数据
func subscribeCtBbo(symbolList map[string][]string) {
client := new(marketwssclient.ContractBBOWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtBboResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: resp.Channel})
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
applogger.Info("subscribeCtBbo data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, Cli.Id, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// subscribeCtDetail TODO: 合约详情数据
func subscribeCtDetail(symbolList map[string][]string) {
client := new(marketwssclient.ContractDetailWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtDetailResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: resp.Channel})
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
applogger.Info("subscribeCtDetail data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, Cli.Id, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// subscribeCtTradeDetail 合约贸易详情数据
func subscribeCtTradeDetail(symbolList map[string][]string) {
client := new(marketwssclient.ContractTradeDetailWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtTradeDetailResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: resp.Channel})
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
applogger.Info("subscribeCtTradeDetail data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, Cli.Id, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
client.Close()
applogger.Info("Client closed")
}

430
cmd/marketwsscliert/marketforex.go

@ -0,0 +1,430 @@
package marketwsscliert
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"strings"
"time"
"wss-pool/internal"
"wss-pool/internal/data"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
models "wss-pool/pkg/model"
)
var messageStr chan []byte
var messageDayStr chan []byte
func init() {
messageStr = make(chan []byte)
messageDayStr = make(chan []byte)
}
// 外汇交易对实时报价
func subscribeMarketForexBakNew(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
time.Sleep(50 * time.Second)
break
}
if strings.Contains(string(msg), "ping") ||
strings.Contains(string(msg), "subscribe success") ||
strings.Contains(string(msg), "\"ev\":\"C\"") ||
strings.Contains(string(msg), "\"ev\":\"T\"") ||
strings.Contains(string(msg), "pong") {
continue
}
var subModel model.FinnhubMessageNew
if err = json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info111 err: %v", err)
continue
}
var modelCl []model.ForexJsonData
if err = json.Unmarshal([]byte(subModel.Content), &modelCl); err != nil {
applogger.Error("subModel Unmarshal info222 err: %v", err)
continue
}
for _, value := range modelCl {
//if value.Pair != "XAUUSD" {
// continue
//}
// TODO: 插针处理
forexJson, checkBool := RunModifyForexNew(value)
msgStr, err := json.Marshal(forexJson)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
continue
}
applogger.Debug("message info:%v", string(msgStr))
if checkBool {
// TODO: 启动插针数据推送(延缓数据推送)
messageStr <- msgStr
} else {
for k, db := range red.RedisClientMap {
err = db.Publish(fmt.Sprintf("%s.Forex", value.Pair), string(msgStr)).Err()
if err != nil {
applogger.Error("db", k, "存储失败:", err)
}
}
}
}
}
}
func pinInsertionRun() {
go func() {
for msg := range messageStr {
var forexModel models.ForexJsonData
if err := json.Unmarshal(msg, &forexModel); err != nil {
applogger.Error("Run Unmarshal info err:", err)
continue
}
// 推送到redis
for k, db := range red.RedisClientMap {
err := db.Publish(fmt.Sprintf("%s.Forex", forexModel.Pair), string(msg)).Err()
if err != nil {
applogger.Error("db", k, "存储失败:", err)
}
}
// 写入Mongodb数据格式
t := time.Unix(forexModel.Timestamp, 0)
formatted := t.Format("2006-01-02 15:04:05")
dataStr := bson.D{
{"code", forexModel.Pair},
{"timestamp", formatted},
{"open_price", decimal.NewFromFloat(forexModel.Open).String()},
{"close_price", decimal.NewFromFloat(forexModel.Close).String()},
{"high_price", decimal.NewFromFloat(forexModel.High).String()},
{"low_price", decimal.NewFromFloat(forexModel.Low).String()},
{"volume", decimal.NewFromInt(int64(forexModel.Volume)).String()},
{"turnover", "0.00"},
}
if err := data.MgoInsertOne(data.ForexKLine, dataStr); err != nil {
applogger.Error("Insert info err:%v", err)
return
}
}
}()
}
// 外汇交易对实时天报价
func subscribeMarketDayForexBakNew(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
time.Sleep(50 * time.Second)
break
}
if strings.Contains(string(msg), "ping") ||
strings.Contains(string(msg), "subscribe success") ||
strings.Contains(string(msg), "\"ev\":\"C\"") ||
strings.Contains(string(msg), "\"ev\":\"T\"") ||
strings.Contains(string(msg), "pong") {
continue
}
var subModel model.FinnhubMessageNew
if err = json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info111 err: %v", err)
continue
}
var modelCl []model.ForexJsonData
if err = json.Unmarshal([]byte(subModel.Content), &modelCl); err != nil {
applogger.Error("subModel Unmarshal info222 err: %v", err)
continue
}
for _, value := range modelCl {
// TODO: 插针处理
forexJson, checkBool := RunModifyForex(value)
msgStr, err := json.Marshal(forexJson)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
continue
}
applogger.Debug("message info:%v", string(msgStr))
if checkBool {
// TODO: 启动插针数据推送(延缓数据推送)
messageDayStr <- msgStr
} else {
for k, db := range red.RedisClientMap {
err = db.Publish(fmt.Sprintf("%s.DayForex", value.Pair), string(msgStr)).Err()
if err != nil {
applogger.Error("db", k, "存储失败:", err)
}
}
}
}
}
}
func pinInsertionDayRun() {
go func() {
for msg := range messageDayStr {
var forexModel models.ForexJsonData
if err := json.Unmarshal(msg, &forexModel); err != nil {
applogger.Error("Run Unmarshal info err:", err)
continue
}
// 数据处理
for k, db := range red.RedisClientMap {
err := db.Publish(fmt.Sprintf("%s.Forex", forexModel.Pair), string(msg)).Err()
if err != nil {
applogger.Error("db", k, "存储失败:", err)
}
}
}
}()
}
// 外汇交易对买一卖一报价
func subscribeMarketForexQuoteBakNew(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
time.Sleep(50 * time.Second)
break
}
msgStrOne := string(msg)
//applogger.Debug("ReadMessage data info:%v", msgStrOne)
if strings.Contains(msgStrOne, "ping") ||
strings.Contains(msgStrOne, "subscribe success") ||
strings.Contains(msgStrOne, "\"ev\":\"CAS\"") ||
strings.Contains(msgStrOne, "\"ev\":\"T\"") ||
strings.Contains(msgStrOne, "pong") {
continue
}
var subModel model.FinnhubMessageNew
if err = json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info111 err: %v", err)
continue
}
var modelCl []model.ForexLastQuote
if err = json.Unmarshal([]byte(subModel.Content), &modelCl); err != nil {
applogger.Error("subModel Unmarshal info222 err: %v", err)
continue
}
for _, value := range modelCl {
msgStr, err := json.Marshal(value)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
continue
}
applogger.Debug("message info:%v", string(msgStr))
// Write to Redis for broadcasting
if len(value.P) == 0 {
continue
}
for k, db := range red.RedisClientMap {
err = db.Publish(fmt.Sprintf("%s.LastForex", value.P), string(msgStr)).Err()
if err != nil {
applogger.Error("db", k, "存储失败:", err)
}
}
}
}
}
// 外汇交易对成交报价
func subscribeMarketForexTradeBakNew(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
time.Sleep(50 * time.Second)
break
}
msgStrOne := string(msg)
//applogger.Debug("ReadMessage data info:%v", msgStrOne)
if strings.Contains(msgStrOne, "ping") ||
strings.Contains(msgStrOne, "subscribe success") ||
strings.Contains(msgStrOne, "\"ev\":\"CAS\"") ||
strings.Contains(msgStrOne, "\"ev\":\"C\"") ||
strings.Contains(msgStrOne, "pong") {
continue
}
var subModel model.FinnhubMessageNew
if err = json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info111 err: %v", err)
continue
}
var modelCl []model.ForexTrade
if err = json.Unmarshal([]byte(subModel.Content), &modelCl); err != nil {
applogger.Error("subModel Unmarshal info222 err: %v", err)
continue
}
for _, value := range modelCl {
msgStr, err := json.Marshal(value)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
continue
}
applogger.Debug("message info:%v", string(msgStr))
for k, db := range red.RedisClientMap {
err = db.Publish(fmt.Sprintf("%s.TradeForex", value.Code), string(msgStr)).Err()
if err != nil {
applogger.Error("db", k, "存储失败:", err)
}
}
}
}
}
// 外汇交易对成交报价存储
func subscribeMarketForexTradeBak2New(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
time.Sleep(50 * time.Second)
break
}
msgStrOne := string(msg)
//applogger.Debug("ReadMessage data info:%v", msgStrOne)
if strings.Contains(msgStrOne, "ping") ||
strings.Contains(msgStrOne, "subscribe success") ||
strings.Contains(msgStrOne, "\"ev\":\"CAS\"") ||
strings.Contains(msgStrOne, "\"ev\":\"C\"") ||
strings.Contains(msgStrOne, "pong") {
continue
}
var subModel model.FinnhubMessageNew
if err = json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info111 err: %v", err)
continue
}
var modelCl []model.ForexTrade
if err = json.Unmarshal([]byte(subModel.Content), &modelCl); err != nil {
applogger.Error("subModel Unmarshal info222 err: %v", err)
continue
}
var dataList []mongo.WriteModel
for _, v := range modelCl {
filter := bson.M{"tick_time": bson.M{"$eq": decimal.RequireFromString(v.TickTime).IntPart()}, "code": bson.M{"$eq": v.Code}}
update := bson.D{{"$set",
bson.D{
{"ev", v.Ev},
{"code", v.Code},
{"seq", v.Seq},
{"tick_time", decimal.RequireFromString(v.TickTime).IntPart()},
{"price", v.Price},
{"volume", v.Volume},
{"turnover", v.Turnover},
{"trade_direction", v.TradeDirection},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
if len(dataList) > 0 {
if err = data.MgoBulkWrite(data.ForexTradeList, dataList); err != nil {
applogger.Error("forexTrade MgoBulkWrite err:%v", err)
}
}
}
}
// 清理外汇成交报价数据信息
func DeleteForexTrade() {
dateList, err := data.MgoFind(data.ForexListBak, bson.M{})
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
//applogger.Info("MgoFind info len: %v", dateList)
for _, value := range dateList.([]primitive.M) {
code := value["symbol"].(string)
filter := bson.M{"code": code}
//applogger.Debug("清理数据:%v", filter)
if err = data.MgoDeleteMany100List(data.ForexTradeList, filter, code); err != nil {
applogger.Error("DeleteForexTrade MgoDeleteMany err :", err.Error())
}
}
}
// 每周一到周五0点0分0秒更新外汇交易对闭盘价
func ForexUpdateCode() {
dateList, err := data.MgoFind(data.ForexListBak, bson.M{})
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
var codeList []string
for _, value := range dateList.([]primitive.M) {
code := value["code"].(string)
codeList = append(codeList, code)
}
UrlBatchKline := "https://quote.tradeswitcher.com/quote-b-api/batch-kline?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
var dataPost model.ConstructParametersPost
for _, value := range codeList {
dataPost.Trace = uuid.New().String()
dataPost.Data.DataList = append(dataPost.Data.DataList, model.DataParameters{
Code: value,
KlineType: 1,
KlineTimestampEnd: 0,
QueryKlineNum: 1,
AdjustType: 0,
})
}
queryStr, err := json.Marshal(&dataPost)
if err != nil {
applogger.Error("解析json错误:%v", err)
return
}
bodyStr, err := internal.HttpPost(UrlBatchKline, string(queryStr))
if err != nil {
applogger.Error("读取响应失败:%v", err)
return
}
//applogger.Debug("响应内容:%v", bodyStr)
var klineNew model.KlinePostReturnStruct
if err = json.Unmarshal([]byte(bodyStr), &klineNew); err != nil {
applogger.Error("解析失败:%v", err)
return
}
//applogger.Debug("响应内容:%v", klineNew)
var dataList []mongo.WriteModel
for _, v := range klineNew.Data.KlineList {
filter := bson.M{
"code": v.Code,
}
updateData := bson.M{}
for _, value := range v.KlineData {
updateData["openPrice"] = value.OpenPrice
updateData["highPrice"] = value.HighPrice
updateData["lowPrice"] = value.LowPrice
updateData["closePrice"] = value.ClosePrice
updateData["timestamp"] = value.Timestamp
break
}
update := bson.M{"$set": updateData}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
if len(dataList) > 0 {
if err = data.MgoBulkWrite(data.ForexListBak, dataList); err != nil {
applogger.Error("MgoInsertMany err:%v", err)
return
}
}
}

216
cmd/marketwsscliert/marketshare.go

@ -0,0 +1,216 @@
package marketwsscliert
import (
"encoding/json"
"errors"
"fmt"
"github.com/gorilla/websocket"
"go.mongodb.org/mongo-driver/bson"
"strings"
"time"
"wss-pool/cmd/common"
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/internal/data/business"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
"wss-pool/pkg/model/stock"
)
var UsNewCode = make(map[string]bool)
// 订阅美股股票行情数据
func subscribeFinnhub() *websocket.Conn {
url := fmt.Sprintf("wss://%v?token=%v", config.Config.FinnhubUs.FinnhubWss, config.Config.FinnhubUs.FinnhubKey)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
applogger.Error("Failed to link to wss server:%v", err)
//defer conn.Close()
return nil
}
return conn
}
func subscribeMarketUsBak(conn *websocket.Conn) {
_ = sendUsCode(conn, false) // 获取订阅全部股票
go singleSendUsCode(conn) // 单线程 订阅后续新增的股票
// TODO: Verify if key permissions are passed
//subAuh := fmt.Sprintf("{\"action\":\"auth\",\"params\":\"%v\"}", config.Config.ShareGather.PolygonKey)
//Send(conn, subAuh)
//// Initiate event subscription
//subData := fmt.Sprintf("{\"action\":\"subscribe\",\"params\":\"A.*\"}")
//Send(conn, subData)
//usMessage := make([]model.ClientMessage, 0)
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
return
}
applogger.Debug("ReadMessage data info:%v", string(msg))
var subModel model.FinnhubMessage
if err := json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info err: %v", err)
continue
}
if !common.IsFinnhubOpeningUS() {
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), "us shareMarket it's not opening time -----------------------------end")
continue
}
for _, value := range subModel.Data {
message := model.ClientMessage{
S: value.S, // 股票代码
C: value.C, // 条件,有关更多信息,请参阅贸易条件术语表
V: value.V, // 交易量,代表在相应时间戳处交易的股票数量 -- 报价交易量
Dp: true, // 暗池真/假
T: value.T, // 以毫秒为单位的时间戳 -- 此聚合窗口的结束时钟周期的时间戳(以 Unix 毫秒为单位)
Cl: value.P,
Op: value.P,
H: value.P,
L: value.P,
}
msgStr, err := json.Marshal(message)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
return
}
applogger.Debug("message info:%v", string(msgStr))
// Write to Redis for broadcasting
if len(message.S) == 0 {
return
}
business.JudgePublishMap("US", message.S, fmt.Sprintf("%s.US", message.S), string(msgStr))
business.JudgeHsetMap("US", business.StockClosingPrice["USNew"], message.S, message.Cl.String())
}
}
}
func sendUsCode(conn *websocket.Conn, isSingle bool) error {
filter := bson.M{"Country": "US", "YesterdayClose": bson.M{"$ne": ""}}
projection := bson.M{"Code": 1}
stockRes := make([]stock.StockPolygon, 0)
data.MgoPagingFindStructProjection(data.StockList, filter, projection, 20000, 1, -1, &stockRes)
old := len(UsNewCode)
for _, v := range stockRes {
if !common.IsExistStock("US", v.Code) {
continue
}
sendPing := fmt.Sprintf("{\"type\":\"subscribe\",\"symbol\":\"%v\"}", v.Code)
if !isSingle { //订阅全部美股
fmt.Println(sendPing)
Send(conn, sendPing)
UsNewCode[v.Code] = true
continue
}
if !UsNewCode[v.Code] { //追加订阅新股票
applogger.Debug("new us stock:%v", sendPing)
Send(conn, sendPing)
UsNewCode[v.Code] = true
}
}
applogger.Debug("old us stock num", old, len(UsNewCode))
return nil
}
func singleSendUsCode(conn *websocket.Conn) {
for {
time.Sleep(1 * time.Hour)
//发送测试数据检查该连接是否有效
if err := Send(conn, fmt.Sprintf("{\"type\":\"subscribe\",\"symbol\":\"%v\"}", "testcode.ping")); err != nil {
applogger.Error("ws:%v", err)
return
}
applogger.Debug("send new us stock start")
if err := sendUsCode(conn, true); err != nil {
applogger.Debug("send new us stock end")
return
}
}
}
// 接收分发股票实时行情
func subscribeDispense(roue string, post int) *websocket.Conn {
url := fmt.Sprintf("ws://%v:%v/%v", config.Config.FinnhubUs.DispenseWss, post, roue)
applogger.Debug("dispense wss url:", url)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
applogger.Error("Failed to link to wss server:%v", err)
return nil
}
applogger.Debug("dispense wss connect success......")
return conn
}
// 美股订阅
func subscribeMarketUsBakNew(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
time.Sleep(50 * time.Second)
break
}
msgStrOne := string(msg)
applogger.Debug("ReadMessage data info:%v", msgStrOne)
if strings.Contains(msgStrOne, "ping") || strings.Contains(msgStrOne, "subscribe success") {
continue
}
var subModel model.FinnhubMessageNew
if err = json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info111 err: %v", err)
continue
}
var modelCl model.FinnhubMessage
if err = json.Unmarshal([]byte(subModel.Content), &modelCl); err != nil {
applogger.Error("subModel Unmarshal info222 err: %v", err)
continue
}
// 美股开盘时间判定
//if !common.IsFinnhubOpeningUS() {
// applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), "us shareMarket it's not opening time -----------------------------end")
// continue
//}
for _, value := range modelCl.Data {
message := model.ClientMessage{
S: value.S, // 股票代码
C: value.C, // 条件,有关更多信息,请参阅贸易条件术语表
V: value.V, // 交易量,代表在相应时间戳处交易的股票数量 -- 报价交易量
Dp: true, // 暗池真/假
T: value.T, // 以毫秒为单位的时间戳 -- 此聚合窗口的结束时钟周期的时间戳(以 Unix 毫秒为单位)
Cl: value.P,
Op: value.P,
H: value.P,
L: value.P,
}
msgStr, err := json.Marshal(message)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
continue
}
applogger.Debug("message info:%v", string(msgStr))
// Write to Redis for broadcasting
if len(message.S) == 0 {
continue
}
business.JudgePublishMap("US", message.S, fmt.Sprintf("%s.US", message.S), string(msgStr))
business.JudgeHsetMap("US", business.StockClosingPrice["USNew"], message.S, message.Cl.String())
}
}
}
// 发送消息
func Send(conn *websocket.Conn, data string) error {
if conn == nil {
applogger.Error("WebSocket sent error: no connection available")
return errors.New("WebSocket sent error: no connection available")
}
err := conn.WriteMessage(websocket.TextMessage, []byte(data))
if err != nil {
applogger.Error("WebSocket sent error: data=%s, error=%s", data, err)
return err
}
return nil
}

401
cmd/marketwsscliert/marketspots.go

@ -0,0 +1,401 @@
package marketwsscliert
import (
"encoding/json"
"fmt"
"wss-pool/cmd/websocketservice"
"wss-pool/config"
"wss-pool/internal/data/business"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/hbwssclient/marketwssclient"
"wss-pool/pkg/model/market"
)
/* 火币现货数据来源
https://huobiapi.github.io/docs/spot/v1/cn/#k-2
*/
// subscribeDepth 市场深度行情数据
func subscribeDepth(symbolList map[string][]string) {
client := new(marketwssclient.DepthWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, value := range symbolList {
for _, vue := range value {
client.Request(key, vue, config.Config.HbGather.HbSubUids)
client.Subscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeDepthResponse)
if ok {
if &depthResponse != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: depthResponse.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: depthResponse.Channel})
red.RedisClient.Publish(depthResponse.Channel, string(jsonMessage))
applogger.Info("subscribeDepth data,ServersId:%v,Sender:%v,Content:%v-%v", depthResponse.Channel, Cli.Id, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, value := range symbolList {
for _, vue := range value {
client.UnSubscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// subscribeLevelMbp 市场深度MBP行情数据(增量推送)(150挡)
func subscribeLevelMbp(symbolList map[string][]string) {
client := new(marketwssclient.MarketByPriceWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, _ := range symbolList {
client.Request(key, config.Config.HbGather.HbSubUids)
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeMarketByPriceResponse)
if ok {
if &depthResponse != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: depthResponse.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: depthResponse.Channel})
red.RedisClient.Publish(depthResponse.Channel, string(jsonMessage))
applogger.Info("subscribeLevelMbp data,ServersId:%v,Sender:%v,Content:%v-%v", depthResponse.Channel, Cli.Id, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// subscribeFullMbp 市场深度MBP行情数据(全量推送)
func subscribeFullMbp(symbolList map[string][]int) {
client := new(marketwssclient.MarketByPriceWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, value := range symbolList {
for _, vue := range value {
client.SubscribeFull(key, vue, config.Config.HbGather.HbSubUids)
}
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeMarketByPriceResponse)
if ok {
if &depthResponse != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: depthResponse.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: depthResponse.Channel})
red.RedisClient.Publish(depthResponse.Channel, string(jsonMessage))
applogger.Info("subscribeFullMbp data,ServersId:%v,Sender:%v,Content:%v-%v", depthResponse.Channel, Cli.Id, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, value := range symbolList {
for _, vue := range value {
client.UnSubscribeFull(key, vue, config.Config.HbGather.HbSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// subscribeSubMbp 市场深度MBP行情数据(增量推送)
func subscribeSubMbp(symbolList map[string][]int) {
client := new(marketwssclient.MarketByPriceTickWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, value := range symbolList {
for _, vue := range value {
client.Request(key, vue, config.Config.HbGather.HbSubUids)
client.Subscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeMarketByPriceResponse)
if ok {
if &depthResponse != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: depthResponse.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: depthResponse.Channel})
red.RedisClient.Publish(depthResponse.Channel, string(jsonMessage))
applogger.Info("subscribeSubMbp data,ServersId:%v,Sender:%v,Content:%v-%v", depthResponse.Channel, Cli.Id, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, value := range symbolList {
for _, vue := range value {
client.UnSubscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// subscribeBbo 买一卖一逐笔行情
func subscribeBbo(symbolList map[string][]string) {
client := new(marketwssclient.BestBidOfferWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, _ := range symbolList {
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
func(resp interface{}) {
bboResponse, ok := resp.(market.SubscribeBestBidOfferResponse)
if ok {
if bboResponse.Tick != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: bboResponse.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: bboResponse.Channel})
red.RedisClient.Publish(bboResponse.Channel, string(jsonMessage))
}
applogger.Info("subscribeBbo data,ServersId:%v,Sender:%v,Content:%v-%v", bboResponse.Channel, Cli.Id, bboResponse.Tick, nil)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Connection closed")
}
// subscribeKLine K线数据
func subscribeKLine(symbolList map[string][]string) {
client := new(marketwssclient.CandlestickWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for symbol, period := range symbolList {
for _, value := range period {
client.Subscribe(symbol, value, config.Config.HbGather.HbSubUids)
}
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCandlestickResponse)
if ok {
if &resp != nil {
if resp.Tick != nil || resp.Data != nil {
//插针
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: resp.Channel,
})
//applogger.Info("k line JSON --- %s",string(jsonMessage))
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
//实时数据入库
applogger.Info("k line resp %v", resp.Data)
go business.UpdateWsMgo(resp)
}
applogger.Info("subscribeKLine data,ServersId:%v,Sender:%v,Content:%v-%v", resp.Channel, Cli.Id, resp.Tick, resp.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbGather.HbSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// subscribeTrade 成交明细
func subscribeTrade(symbolList map[string][]string) {
client := new(marketwssclient.TradeWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, _ := range symbolList {
client.Request(key, config.Config.HbGather.HbSubUids)
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeTradeResponse)
if ok {
if &depthResponse != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: depthResponse.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: depthResponse.Channel})
red.RedisClient.Publish(depthResponse.Channel, string(jsonMessage))
applogger.Info("subscribeTrade data,ServersId:%v,Sender:%v,Content:%v-%v", depthResponse.Channel, Cli.Id, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// subscribeLast24h 市场概要
func subscribeLast24h(symbolList map[string][]string) {
// Initialize a new instance
client := new(marketwssclient.Last24hCandlestickWebSocketClient).Init(config.Config.HbGather.HbHost)
// Set the callback handlers
client.SetHandler(
// Connected handler
func() {
for key, _ := range symbolList {
client.Request(key, config.Config.HbGather.HbSubUids)
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
// Response handler
func(resp interface{}) {
candlestickResponse, ok := resp.(market.SubscribeLast24hCandlestickResponse)
if ok {
if &candlestickResponse != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: candlestickResponse.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: candlestickResponse.Channel})
red.RedisClient.Publish(candlestickResponse.Channel, string(jsonMessage))
applogger.Info("subscribeLast24h data,ServersId:%v,Sender:%v,Content:%v-%v", candlestickResponse.Channel, Cli.Id, candlestickResponse.Tick, candlestickResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
// Connect to the wss and wait for the handler to handle the response
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// subscribeTicker 市场聚合行情(ticker)
func subscribeTicker(symbolList map[string][]string) {
client := new(marketwssclient.TickerWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbGather.HbSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.TickerWebsocketResponse)
if ok {
if &resp != nil {
if resp.Tick != nil || resp.Data != nil {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Sender: Cli.Id,
Content: resp,
Symbol: resp.Channel})
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
applogger.Info("subscribeTicker data,ServersId:%v,Sender:%v,Content:%v-%v", resp.Channel, Cli.Id, resp.Tick, resp.Data)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}

270
cmd/marketwsscliert/marketwssclient.go

@ -0,0 +1,270 @@
package marketwsscliert
import (
"encoding/json"
"github.com/gorilla/websocket"
"github.com/robfig/cron"
"time"
"wss-pool/cmd/common"
"wss-pool/cmd/websocketcollect/us"
"wss-pool/cmd/websocketservice"
"wss-pool/config"
"wss-pool/dictionary"
"wss-pool/internal/data/business"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var Cli websocketservice.User
// 数字币行情采集
func RunHBDataRedis(checkData string) {
HbMarketSpots(checkData)
HbContract(checkData)
}
// 火币现货行情数据
func HbMarketSpots(checkData string) {
switch checkData {
case "subscribeDepth": // 市场深度行情数据 8863
subscribeDepth(model.SymbolListString(dictionary.Depth))
case "subscribeLevelMbp": // 市场深度MBP行情数据(增量推送)(150挡) 8864
subscribeLevelMbp(model.SymbolListString([]string{}))
case "subscribeFullMbp": // 市场深度MBP行情数据(全量推送) 8865
subscribeFullMbp(model.SymbolListInt(dictionary.LevelsRefresh))
case "subscribeSubMbp": // 市场深度MBP行情数据(增量推送) 8866
subscribeSubMbp(model.SymbolListInt(dictionary.LevelsMbp))
case "subscribeBbo": // 买一卖一逐笔行情 8867
subscribeBbo(model.SymbolListString([]string{}))
case "subscribeKLine": // K线数据 8868
subscribeKLine(model.SymbolListString(dictionary.TimeCycle))
case "subscribeTrade": // 成交明细 8869
subscribeTrade(model.SymbolListString([]string{}))
case "subscribeLast24h": // 市场概要 8847
subscribeLast24h(model.SymbolListString([]string{}))
case "subscribeTicker": // 聚合行情(Ticker) 8848
subscribeTicker(model.SymbolListString([]string{}))
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 币本位永续合约行情数据
func HbContract(checkData string) {
switch checkData {
case "subscribeCtKline": // k线数据 8841
//加载配置数据
go GetModifyContract()
subscribeCtKline(model.SymbolCtListString(dictionary.ContractTime))
case "subscribeCtDepth": // 深度信息 8842
subscribeCtDepth(model.SymbolCtListString(dictionary.ContractDepth))
case "subscribeCtAddDepth": // 新增深度信息 8843
subscribeCtAddDepth(model.SymbolCtListString(dictionary.ContractAddDepth))
case "subscribeCtBbo": // 买一卖一行情数据 8844
subscribeCtBbo(model.SymbolCtListString([]string{}))
case "subscribeCtDetail": // 合约详情数据 8845
subscribeCtDetail(model.SymbolCtListString([]string{}))
case "subscribeCtTradeDetail": // 合约贸易详情数据 8846
subscribeCtTradeDetail(model.SymbolCtListString([]string{}))
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 美股行情采集
func ShareMarket(checkData string) {
// TODO: 不需要推送插针数据
go business.NewPinStock(common.GetRedisNoPin(config.Config.Redis.NoPinAss))
switch checkData {
case "usShare": // US
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
applogger.Info("Execute automatic subscription........")
if conn := subscribeFinnhub(); conn != nil {
subscribeMarketUsBak(conn)
}
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 美股行情采集分发
func ShareMarketBak(checkData string) {
// TODO: 不需要推送插针数据
go business.NewPinStock(common.GetRedisNoPin(config.Config.Redis.NoPinAss))
switch checkData {
case "usShare":
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if conn := subscribeDispense("ws", 7777); conn != nil {
webSocketsWrite(conn, "us", "subscribe")
subscribeMarketUsBakNew(conn)
}
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 外汇行情采集分发
func ForexMarketBak(checkData string) {
switch checkData {
case "forex":
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if conn := subscribeDispense("forexWs", 7778); conn != nil {
webSocketsWrite(conn, "forex", "subscribe")
pinInsertionRun()
subscribeMarketForexBakNew(conn)
}
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 外汇行情天采集分发
func ForexMarketDayBak(checkData string) {
switch checkData {
case "forex":
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if conn := subscribeDispense("forexWs", 7778); conn != nil {
webSocketsWrite(conn, "forexDay", "subscribe")
pinInsertionDayRun()
subscribeMarketDayForexBakNew(conn)
}
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 外汇买一卖一报价采集分发
func ForexMarketQuoteBak(checkData string) {
switch checkData {
case "forex":
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if conn := subscribeDispense("forexWs", 7778); conn != nil {
webSocketsWrite(conn, "quotes", "subscribe")
subscribeMarketForexQuoteBakNew(conn)
}
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 外汇成交报价采集分发
func ForexMarketTradeBak(checkData string) {
switch checkData {
case "forex":
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if conn := subscribeDispense("forexWs", 7778); conn != nil {
webSocketsWrite(conn, "trade", "subscribe")
subscribeMarketForexTradeBakNew(conn)
}
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 外汇成交报价存储
func ForexMarketTradeBak2(checkData string) {
switch checkData {
case "forex":
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if conn := subscribeDispense("forexWs", 7778); conn != nil {
webSocketsWrite(conn, "tradeStorage", "subscribe")
subscribeMarketForexTradeBak2New(conn)
}
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 外汇成交报价数据清理
func ForexMarketClearTradeBak2(checkData string) {
switch checkData {
case "forex":
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
for {
select {
case <-ticker.C:
DeleteForexTrade()
}
}
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 股票分发推送
func webSocketsWrite(conn *websocket.Conn, topic, content string) {
message := us.Message{
Topic: topic,
Content: content,
}
msg, err := json.Marshal(message)
if err != nil {
applogger.Error("marshal:%v", err)
return
}
err = conn.WriteMessage(websocket.TextMessage, msg)
if err != nil {
applogger.Error("write:%v", err)
return
}
}
// 外汇交易对更新闭盘价
func ForexUpdateClosePrice() {
// 创建一个cron调度器
c := cron.New()
// 添加任务,每天0点执行
err := c.AddFunc("0 0 0 * * 1-5", func() {
ForexUpdateCode()
})
if err != nil {
applogger.Error("", err)
}
// 启动cron调度器
c.Start()
// 阻塞主线程,防止程序退出
select {}
}

589
cmd/marketwsscliert/marketwssclient_test.go

@ -0,0 +1,589 @@
package marketwsscliert
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/shopspring/decimal"
"io/ioutil"
"log"
"math/rand"
"net/http"
"testing"
"time"
"wss-pool/cmd/common"
"wss-pool/internal"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
type ConstructParameters struct {
Trace string `json:"trace"`
Data struct {
Code string `json:"code"`
KlineType int `json:"kline_type"`
KlineTimestampEnd int `json:"kline_timestamp_end"`
QueryKlineNum int `json:"query_kline_num"`
AdjustType int `json:"adjust_type"`
} `json:"data"`
}
type KlinePostReturnStruct struct {
Ret int `json:"ret"`
Msg string `json:"msg"`
Trace string `json:"trace"`
Data struct {
KlineList []struct {
Code string `json:"code"`
KlineType int `json:"kline_type"`
KlineData []struct {
Timestamp string `json:"timestamp"`
OpenPrice string `json:"open_price"`
ClosePrice string `json:"close_price"`
HighPrice string `json:"high_price"`
LowPrice string `json:"low_price"`
Volume string `json:"volume"`
Turnover string `json:"turnover"`
} `json:"kline_data"`
} `json:"kline_list"`
} `json:"data"`
}
type KlineGetReturnStruct struct {
Ret int `json:"ret"`
Msg string `json:"msg"`
Trace string `json:"trace"`
Data struct {
Code string `json:"code"`
KlineType int `json:"kline_type"`
KlineList []struct {
Timestamp string `json:"timestamp"`
OpenPrice string `json:"open_price"`
ClosePrice string `json:"close_price"`
HighPrice string `json:"high_price"`
LowPrice string `json:"low_price"`
Volume string `json:"volume"`
Turnover string `json:"turnover"`
} `json:"kline_list"`
} `json:"data"`
}
type Symbol struct {
Code string `json:"code"`
DepthLevel int `json:"depth_level"`
}
type DataList struct {
SymbolList []Symbol `json:"symbol_list"`
}
type Request struct {
CmdID int `json:"cmd_id"`
SeqID int `json:"seq_id"`
Trace string `json:"trace"`
Data DataList `json:"data"`
}
type OrderBookOrTradeTick struct {
Trace string `json:"trace"`
Data struct {
SymbolList []SymbolList `json:"symbol_list"`
} `json:"data"`
}
type SymbolList struct {
Code string `json:"code"`
}
type DepthTradeReturnStruct struct {
Msg string `json:"msg"`
Trace string `json:"trace"`
Data struct {
TickList []struct {
Code string `json:"code"`
Seq string `json:"seq"`
TickTime string `json:"tick_time"`
Bids []struct {
Price string `json:"price"`
Volume string `json:"volume"`
} `json:"bids"`
Asks []struct {
Price string `json:"price"`
Volume string `json:"volume"`
} `json:"asks"`
} `json:"tick_list"`
} `json:"data"`
}
type TradeReturnStruct struct {
Msg string `json:"msg"`
Trace string `json:"trace"`
Data struct {
TickList []struct {
Code string `json:"code"`
Seq string `json:"seq"`
TickTime string `json:"tick_time"`
Price string `json:"price"`
Volume string `json:"volume"`
Turnover string `json:"turnover"`
} `json:"tick_list"`
} `json:"data"`
}
type Results struct {
CmdID int `json:"cmd_id"`
Data struct {
Code string `json:"code"`
Seq string `json:"seq"`
TickTime string `json:"tick_time"`
Bids []struct {
Price string `json:"price"`
Volume string `json:"volume"`
} `json:"bids"`
Asks []struct {
Price string `json:"price"`
Volume string `json:"volume"`
} `json:"asks"`
} `json:"data"`
}
type ResultsTrade struct {
CmdID int `json:"cmd_id"`
Data struct {
Code string `json:"code"`
Seq string `json:"seq"`
TickTime string `json:"tick_time"`
Price string `json:"price"`
Volume string `json:"volume"`
Turnover string `json:"turnover"`
TradeDirection int `json:"trade_direction"`
} `json:"data"`
}
func GenerateParameters(trace, code string, kline_type, query_kline_num int) string {
queryStr := fmt.Sprintf("{'trace':'%v','data':{'data_list':[{'code':'%v','kline_type':%v,'kline_timestamp_end':0,'query_kline_num':%v,'adjust_type':0}]}}", trace, code, kline_type, query_kline_num)
return queryStr
}
// GenerateRandomFloat64 在min和max之间生成一个随机浮点数
func GenerateRandomFloat64(min, max float64) float64 {
return min + rand.Float64()*(max-min)
}
// 将两个时间区间划分成指定数量的等分区间
func TestTime(t *testing.T) {
// 假设这是从MongoDB获取的时间戳(以秒为单位)
timestamp := int64(1735064100000)
// 将时间戳转换为time.Time类型
t1 := time.Unix(timestamp, 0)
// 格式化时间
formattedTime := t1.Format(time.RFC3339)
fmt.Println("时间戳:", timestamp)
fmt.Println("转换后的时间:", formattedTime)
return
layoutStart := "2024-12-17 17:00:00"
layoutEnd := "2024-12-17 17:09:00"
startT := common.TimeStringToTime(layoutStart)
endT := common.TimeStringToTime(layoutEnd)
applogger.Debug("start:%v,end:%v", startT, endT)
startF := 2658.33000
endF := 2652.33000
numIntervals := 5
msgTime := DivideTimeInterval(startT, endT, numIntervals)
msgFloat := DivideFloatInterval(startF, endF, numIntervals)
var msgTF = make(map[int]string)
// 打印每个区间的时间
for i, mt := range msgTime {
mf, ok := msgFloat[i]
if ok {
msgTF[i] = fmt.Sprintf("%v,%v", mt, mf)
applogger.Debug("msgTF:%v,%v", i, msgTF[i])
}
}
}
func TestGetKline(t *testing.T) {
percentage := decimal.RequireFromString("1").Add(decimal.RequireFromString("1").Div(decimal.RequireFromString("10000")))
startValue := 2652.33000 // 起点值 2652.33000 938.20000 0.58464
endValue := 2658.33000 // 结束值 2658.33000 958.20000 0.68464
maxValue := decimal.NewFromFloat(endValue).Mul(percentage).InexactFloat64()
var currentValue = startValue
var currentValueS float64
for {
if currentValue <= endValue {
currentValue *= percentage.InexactFloat64()
} else {
min := currentValue
currentValueS = RandomBetween(min, maxValue)
}
applogger.Debug("currentValue:%v,endSub:%v,Max:%v", currentValue, currentValueS, maxValue)
time.Sleep(1 * time.Second)
}
//for currentValue := startValue; currentValue <= endValue; currentValue *= percentage.InexactFloat64() {
// applogger.Debug("currentValue:", currentValue)
//}
}
func TestGetKlineS(t *testing.T) {
percentage := decimal.RequireFromString("1").Add(decimal.RequireFromString("1").Div(decimal.RequireFromString("10000")))
startValue := 2658.33000 // 起点值
endValue := 2652.33000 // 结束值
maxValue := decimal.NewFromFloat(endValue).Mul(percentage).InexactFloat64()
var currentValue = startValue
var currentValueS float64
for {
if currentValue <= startValue {
currentValue /= percentage.InexactFloat64()
// 变化值小于结束值
if currentValue <= endValue {
currentValueS = RandomBetween(endValue, maxValue)
}
}
applogger.Debug("currentValue:%v,endSub:%v,Max:%v", currentValue, currentValueS, maxValue)
time.Sleep(1 * time.Second)
}
}
func TestMainList(t *testing.T) {
url := "wss://quote.tradeswitcher.com/quote-b-ws-api?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
c, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
fmt.Println("dial:", err)
}
defer c.Close()
// Send heartbeat every 10 seconds
go func() {
for range time.NewTicker(10 * time.Second).C {
req := Request{
CmdID: 22000,
SeqID: 123,
Trace: "3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462",
Data: DataList{},
}
messageBytes, err := json.Marshal(req)
if err != nil {
fmt.Println("json.Marshal error:", err)
return
}
applogger.Debug("req data:", string(messageBytes))
err = c.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
fmt.Println("write:", err)
}
}
}()
req := Request{
CmdID: 22002,
SeqID: 123,
Trace: uuid.New().String(),
Data: DataList{SymbolList: []Symbol{
//{"GOLD", 5},
//{"AAPL.US", 5},
//{"700.HK", 5},
//{"GOLD", 1},
//{"Silver", 1},
{"GOLD", 1},
//{"Silver", 1},
}},
//Data: dataList,
}
messageBytes, err := json.Marshal(req)
if err != nil {
applogger.Debug("json.Marshal error:", err)
return
}
applogger.Debug("req data:", string(messageBytes))
err = c.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
fmt.Println("write:", err)
}
rece_count := 0
for {
_, message, err := c.ReadMessage()
if err != nil {
applogger.Debug("read:", err)
break
} else {
var messageToJson Results
if err = json.Unmarshal(message, &messageToJson); err != nil {
applogger.Error("SendAllClientTradeSwitcher err:%v", err)
}
switch messageToJson.CmdID {
case 22001: // 心跳
// {"ret":200,"msg":"ok","cmd_id":22001,"seq_id":123,"trace":"3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462"}
applogger.Info("Heartbeat results:%v", string(message))
case 22999: // 数据解析
// {"cmd_id":22999,"data":{"code":"EURUSD","seq":"114101723","tick_time":"1732168153591","bids":[{"price":"1.05478","volume":"100000.00"}],"asks":[{"price":"1.05479","volume":"100000.00"}]}}
applogger.Info("Received message:%v", string(message))
default:
applogger.Info("Received message err:%v", string(message))
}
}
rece_count++
if rece_count%10000 == 0 {
fmt.Println("count:", rece_count, " Received message:", string(message))
}
}
}
func TestMainLists(t *testing.T) {
url := "wss://quote.tradeswitcher.com/quote-b-ws-api?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
c, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
fmt.Println("dial:", err)
}
defer c.Close()
// Send heartbeat every 10 seconds
go func() {
for range time.NewTicker(10 * time.Second).C {
req := Request{
CmdID: 22000,
SeqID: 123456,
Trace: "3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462787878",
Data: DataList{},
}
messageBytes, err := json.Marshal(req)
if err != nil {
fmt.Println("json.Marshal error:", err)
return
}
applogger.Debug("req data:", string(messageBytes))
err = c.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
fmt.Println("write:", err)
}
}
}()
req := Request{
CmdID: 22004,
SeqID: 123456,
Trace: uuid.New().String(),
Data: DataList{SymbolList: []Symbol{
//{"GOLD", 5},
//{"AAPL.US", 5},
//{"700.HK", 5},
//{"GOLD", 1},
//{"Silver", 1},
{"GOLD", 1},
//{"Silver", 1},
}},
//Data: dataList,
}
messageBytes, err := json.Marshal(req)
if err != nil {
fmt.Println("json.Marshal error:", err)
return
}
err = c.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
fmt.Println("write:", err)
}
rece_count := 0
for {
_, msg, err := c.ReadMessage()
if err != nil {
fmt.Println("read:", err)
break
} else {
var messageToJson ResultsTrade
if err = json.Unmarshal(msg, &messageToJson); err != nil {
applogger.Error("json.Unmarshal error:%v", err)
continue
}
switch messageToJson.CmdID {
case 22001: // 处理心跳
// {"ret":200,"msg":"ok","cmd_id":22001,"seq_id":123456,"trace":"3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462787878"}
applogger.Debug("Heartbeat results:%v", string(msg))
case 22998: // 处理订阅数据
// {"cmd_id":22998,"data":{"code":"XAUUSD","seq":"65087341","tick_time":"1732267727182","price":"2694.84","volume":"95.00","turnover":"0.00","trade_direction":0}}
code := model.Check_Code[messageToJson.Data.Code]
if len(code) == 0 {
code = messageToJson.Data.Code
}
tradeMsg := &[]model.ForexTrade{
{
Ev: "T",
Code: code,
Seq: messageToJson.Data.Seq,
TickTime: messageToJson.Data.TickTime,
Price: messageToJson.Data.Price,
Volume: messageToJson.Data.Volume,
Turnover: messageToJson.Data.Turnover,
TradeDirection: messageToJson.Data.TradeDirection,
},
}
msgStr, err := json.Marshal(tradeMsg)
if err != nil {
applogger.Error("json.Marshal error:%v", err)
time.Sleep(5 * time.Second)
continue
}
applogger.Info("Message processing result:%v", string(msgStr))
default:
applogger.Debug("ReadMessage data info:%v", string(msg))
}
}
rece_count++
if rece_count%10000 == 0 {
fmt.Println("count:", rece_count, " Received message:", string(msg))
}
}
}
func TestGet(t *testing.T) {
urlStr := "https://quote.tradeswitcher.com/quote-b-api/kline"
log.Println("请求内容:", urlStr)
req, err := http.NewRequest("GET", urlStr, nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}
q := req.URL.Query()
q.Add("token", "bf8f33c446c4494286eccaa57a2e6fac-c-app")
var query ConstructParameters
query.Trace = uuid.New().String()
query.Data.Code = "AUDUSD"
query.Data.KlineType = 8
query.Data.KlineTimestampEnd = 0
query.Data.QueryKlineNum = 3
query.Data.AdjustType = 0
byteStr, err := json.Marshal(&query)
if err != nil {
return
}
q.Add("query", string(byteStr))
req.URL.RawQuery = q.Encode()
// 发送请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
bodyStr, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("读取响应失败:", err)
return
}
var klineNew KlineGetReturnStruct
if err = json.Unmarshal(bodyStr, &klineNew); err != nil {
log.Println("解析失败:", err)
return
}
log.Println("响应内容:", klineNew)
}
func TestPost(t *testing.T) {
for {
UrlBatchKline := "https://quote.tradeswitcher.com/quote-b-api/batch-kline?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
bodyStr, err := internal.HttpPost(UrlBatchKline, GenerateParameters(uuid.New().String(), "GOLD", 1, 1))
if err != nil {
log.Println("读取响应失败:", err)
return
}
var klineNew KlinePostReturnStruct
if err = json.Unmarshal([]byte(bodyStr), &klineNew); err != nil {
log.Println("解析失败:", err)
return
}
//applogger.Info("数据信息:%v", klineNew)
ClosePrice := klineNew.Data.KlineList[0].KlineData[0].ClosePrice
OpenPrice := klineNew.Data.KlineList[0].KlineData[0].OpenPrice
HighPrice := klineNew.Data.KlineList[0].KlineData[0].HighPrice
LowPrice := klineNew.Data.KlineList[0].KlineData[0].LowPrice
Volume := klineNew.Data.KlineList[0].KlineData[0].Volume
Timestamp := klineNew.Data.KlineList[0].KlineData[0].Timestamp
Turnover := klineNew.Data.KlineList[0].KlineData[0].Turnover
applogger.Debug("闭盘价:%v,开盘价:%v,最高价:%v,最低价:%v,交易量:%v,交易时间:%v,交易金额:%v", ClosePrice, OpenPrice, HighPrice, LowPrice, Volume, Timestamp, Turnover)
time.Sleep(time.Second * 1)
}
}
func TestGetOrderDepth(t *testing.T) {
urlStr := "https://quote.tradeswitcher.com/quote-b-api/depth-tick"
log.Println("请求内容:", urlStr)
req, err := http.NewRequest("GET", urlStr, nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}
q := req.URL.Query()
q.Add("token", "bf8f33c446c4494286eccaa57a2e6fac-c-app")
var query OrderBookOrTradeTick
query.Trace = uuid.New().String()
query.Data.SymbolList = []SymbolList{
{Code: "GOLD"},
}
byteStr, err := json.Marshal(&query)
if err != nil {
return
}
q.Add("query", string(byteStr))
req.URL.RawQuery = q.Encode()
// 发送请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
bodyStr, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("读取响应失败:", err)
return
}
var klineNew DepthTradeReturnStruct
if err = json.Unmarshal(bodyStr, &klineNew); err != nil {
log.Println("解析失败:", err)
return
}
log.Println("响应内容:", klineNew)
}
func TestGetOrderTrade(t *testing.T) {
urlStr := "https://quote.tradeswitcher.com/quote-b-api/trade-tick"
log.Println("请求内容:", urlStr)
req, err := http.NewRequest("GET", urlStr, nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}
q := req.URL.Query()
q.Add("token", "bf8f33c446c4494286eccaa57a2e6fac-c-app")
var query OrderBookOrTradeTick
query.Trace = uuid.New().String()
query.Data.SymbolList = []SymbolList{
{Code: "GOLD"},
}
byteStr, err := json.Marshal(&query)
if err != nil {
return
}
q.Add("query", string(byteStr))
req.URL.RawQuery = q.Encode()
// 发送请求
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
bodyStr, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("读取响应失败:", err)
return
}
var klineNew TradeReturnStruct
if err = json.Unmarshal(bodyStr, &klineNew); err != nil {
log.Println("解析失败:", err)
return
}
log.Println("响应内容:", klineNew)
}

217
cmd/marketwsscliert/modifycontract.go

@ -0,0 +1,217 @@
package marketwsscliert
import (
"fmt"
"github.com/shopspring/decimal"
"math/rand"
"strings"
"sync"
"time"
"wss-pool/cmd/common"
"wss-pool/cmd/selfContract"
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/internal/model"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/market"
)
const (
proportion int32 = 10000 //调价原价比例
numPrices int = 60 * 5 //生成随机价格数量
)
var (
ModifyContractMap = make(map[string]ModifyContract)
mu = sync.Mutex{}
UpdatePrice = make(map[string]decimal.Decimal)
)
type ModifyContract struct {
ContractCode string `json:"ContractCode"` //合约
BeginTime string `json:"BeginTime"` //开始时间
EndTime string `json:"EndTime"` //结束时间
Price decimal.Decimal `json:"Prices"` //价格
Prices string `json:"Price"` //价格
Digits int `json:"Digits"` //保留几位小数
Step int `json:"Step"` // 浮点数
BeginUnix int64
EndUnix int64
}
func SetValue(key string, value ModifyContract) {
mu.Lock()
defer mu.Unlock()
ModifyContractMap[key] = value
}
func GetValue(key string) (ModifyContract, bool) {
mu.Lock()
defer mu.Unlock()
value, ok := ModifyContractMap[key]
return value, ok
}
func getData() []model.ContractMarket {
contract := model.NewContractMarket()
result := contract.ListModifyContract()
return result
}
// 加载配置数据
func GetModifyContract() {
data.InitGorm(config.Config.Bourse)
for {
t := time.NewTimer(10 * time.Second)
<-t.C
result := getData()
for _, v := range result {
end, _ := common.TimeStrToTimes(v.EndTime)
if end.Unix() < time.Now().Unix() {
applogger.Error("该调价已过期")
continue
}
price, err := decimal.NewFromString(v.MaxPrice)
if price.IsZero() {
applogger.Error("价格有误", price)
continue
}
begin, err := common.TimeStrToTimestamp(v.BeginTime)
if err != nil {
applogger.Error("begin err", err)
continue
}
ends, err := common.TimeStrToTimestamp(v.EndTime)
if err != nil {
applogger.Info("end err", err)
continue
}
//判断当前合约 是否有任务还在执行
if mapVal, ok := GetValue(v.TradeName); ok {
if mapVal.EndUnix >= time.Now().Unix() {
applogger.Info(v.TradeName, " is run")
continue
}
}
SetValue(v.TradeName, ModifyContract{
ContractCode: v.TradeName,
BeginTime: v.BeginTime,
EndTime: v.EndTime,
Price: price,
Digits: v.KeepDecimal,
Step: v.Step,
BeginUnix: begin,
EndUnix: ends,
})
contract := model.NewContractMarket()
contract.ID = v.ID
contract.UpdateIsGetOne()
}
}
}
// 合约插针
func RunModify(param market.SubscribeCtKlineResponse) market.SubscribeCtKlineResponse {
countSplit := strings.Split(param.Channel, ".")
if len(countSplit) < 2 {
return param
}
val, ok := GetValue(countSplit[1])
if !ok {
return param
}
//不在设置的时间区间范围 过滤 ,因k线间隔 不同 id误差大,使用ts
if (val.BeginUnix)*1000 > param.Timestamp || (val.EndUnix*1000) < param.Timestamp {
return param
}
key := fmt.Sprintf("%s-%s", param.Tick.Close.String(), countSplit[1])
var price decimal.Decimal
applogger.Info("old", param.Tick.Close)
tick := param.Tick
//保持各种K线 落盘价一致 问题
price, ok = UpdatePrice[key]
if !ok {
fmt.Println("new data")
price = calculateContractPrice(val.Price, int32(val.Step), int32(val.Digits))
UpdatePrice[key] = price
}
go clearPrice(key)
tick.Close = price
if price.GreaterThan(tick.High) {
tick.High = price
}
if price.LessThan(tick.Low) {
tick.Low = price
}
applogger.Info("new", tick.Close, "channel", param.Channel)
var timestamp int64
var open decimal.Decimal
expot := countSplit[len(countSplit)-1]
switch expot {
case "1min":
timestamp = common.GenerateSingaporeMinuteTimestamp()
open = GetContractOpen(timestamp, expot, countSplit[1])
case "5min":
timestamp = common.GenerateSingaporeFiveMinTimestamp()
open = GetContractOpen(timestamp, expot, countSplit[1])
case "15min":
timestamp = common.GenerateSingaporeFifteenMinTimestamp()
open = GetContractOpen(timestamp, expot, countSplit[1])
case "30min":
timestamp = common.GenerateSingaporeThirtyMinTimestamp()
open = GetContractOpen(timestamp, expot, countSplit[1])
case "60min":
timestamp = common.GenerateSingaporeHourTimestamp()
open = GetContractOpen(timestamp, expot, countSplit[1])
case "4hour":
timestamp = common.GenerateSingaporeFourHourTimestamp() - (4 * 60 * 60)
open = GetContractOpen(timestamp, expot, countSplit[1])
case "1day":
timestamp = common.GenerateSingaporeDayTimestamp("")
open = GetContractOpen(timestamp, expot, countSplit[1])
case "1week":
timestamp = common.GetWeekTimestamp()
open = GetContractOpen(timestamp, expot, countSplit[1])
case "1mon":
timestamp = common.GenerateSingaporeMonTimestamp()
open = GetContractOpen(timestamp, expot, countSplit[1])
}
tick.Open = open
param.Tick = tick
return param
}
func GetContractOpen(timestamp int64, period, contract string) decimal.Decimal {
var open decimal.Decimal
tick := selfContract.GetNewPriceAll(contract, period)
if len(tick) > 0 {
switch tick[0].Code {
case timestamp:
open, _ = decimal.NewFromString(tick[0].Open)
default:
open, _ = decimal.NewFromString(tick[0].Close)
}
}
return open
}
func clearPrice(close string) {
if len(UpdatePrice) >= 10 {
for key := range UpdatePrice {
if key != close {
delete(UpdatePrice, key)
break
}
}
}
}
// 计算虚拟合约价格
func calculateContractPrice(basePrice decimal.Decimal, step int32, digits int32) decimal.Decimal {
rand.New(rand.NewSource(time.Now().UnixNano()))
max := basePrice.Mul(decimal.NewFromFloat(float64(step) / float64(proportion))).Round(digits)
min := max.Neg()
return basePrice.Add(generateRandomStep(max, min)).Round(digits)
}
func generateRandomStep(min, max decimal.Decimal) decimal.Decimal {
return min.Add(decimal.NewFromFloat(rand.Float64()).Mul(max.Sub(min)))
}

421
cmd/marketwsscliert/modifyforex.go

@ -0,0 +1,421 @@
package marketwsscliert
import (
"fmt"
"github.com/shopspring/decimal"
"math/rand"
"strconv"
"strings"
"sync"
"time"
"wss-pool/cmd/common"
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/internal/model"
"wss-pool/logging/applogger"
models "wss-pool/pkg/model"
)
var (
conversion_forex = decimal.RequireFromString("1")
proportion_forex = decimal.RequireFromString("1000000")
)
var (
muForex = sync.Mutex{}
ModifyForexMap = make(map[string]ModifyForex)
)
type ModifyForex struct {
ForexCode string `json:"ForexCode"` // 外汇
BeginTime string `json:"BeginTime"` // 开始时间-date
EndTime string `json:"EndTime"` // 结束时间-date
Price decimal.Decimal `json:"Prices"` // 设置价格
ChangePrice decimal.Decimal `json:"ChangePrice"` // 插针变化价格(开盘价,初始浮点变化区间起始价) - (初始浮点变化区间结束价-动态直至不在变化) - (浮点变化区间结束后的起始价格)
EndPrice decimal.Decimal `json:"EndPrice"` // 浮点变化区间结束后的结束价格
Proportion decimal.Decimal `json:"Proportion"` // 浮动率
CheckBool bool `json:"CheckBool"` // 判断是负增长还是正增长
Digits int `json:"Digits"` // 保留几位小数
Step int `json:"Step"` // 浮点数
BeginUnix int64 `json:"BeginUnix"` // 开始时间-unix
EndUnix int64 `json:"EndUnix"` // 结算时间-unix
RestoreChangePrice decimal.Decimal `json:"RestorePrice"` // 插针恢复变化价格(开盘价,初始浮点变化区间起始价) - (初始浮点变化区间结束价-动态直至不在变化) - (浮点变化区间结束后的起始价格)
RestoreBeginTime string `json:"RestoreBeginTime"` // 插针恢复开始时间-date
RestoreEndTime string `json:"RestoreEndTime"` // 插针恢复结束时间-date
RestoreBeginUnix int64 `json:"RestoreBeginUnix"` // 插针恢复开始时间-unix
RestoreEndUnix int64 `json:"RestoreEndUnix"` // 插针恢复结束时间-unix
RestoreBool bool `json:"RestoreBool"` // 插针恢复
RestoreSetChangePrice bool `json:"RestoreSetChangePrice"` // 插针恢复更新
DataHandling map[int]string `json:"DataHandling"` // 数据处理-需要初始化
NumIntervals int `json:"NumIntervals"` // 数据处理-间隔次数
}
func SetValue_Forex(key string, value ModifyForex) {
muForex.Lock()
defer muForex.Unlock()
ModifyForexMap[key] = value
}
func GetValue_Forex(key string) (ModifyForex, bool) {
muForex.Lock()
defer muForex.Unlock()
value, ok := ModifyForexMap[key]
return value, ok
}
func getData_Forex() []model.ForexMarket {
forex := model.NewForexMarket()
result := forex.ListModifyForex()
return result
}
// 外汇加载交易对插针列表
func GetModifyForex() {
data.InitGorm(config.Config.Bourse)
for {
t := time.NewTimer(10 * time.Second)
<-t.C
result := getData_Forex()
applogger.Debug("加载需要插针的交易对设置:%v", result)
for _, v := range result {
end, _ := common.TimeStrToTimes(v.EndTime)
if end.Unix() < time.Now().Unix() {
applogger.Error("该调价已过期......")
delete(ModifyForexMap, v.TradeName)
forex := model.NewForexMarket()
forex.ID = v.ID
forex.UpdateIsGetOne()
continue
}
price, err := decimal.NewFromString(v.MaxPrice)
if price.IsZero() {
applogger.Error("price err:%v", price)
continue
}
// 插针设置
begin, err := common.TimeStrToTimestamp(v.BeginTime)
if err != nil {
applogger.Error("begin err:%v", err)
continue
}
ends, err := common.TimeStrToTimestamp(v.EndTime)
if err != nil {
applogger.Info("end err:%v", err)
continue
}
// 插针恢复
restoreBegin, err := common.TimeStrAddFiveTime(v.BeginTime)
if err != nil {
applogger.Error("restoreBegin err:%v", err)
continue
}
restoreEnds, err := common.TimeStrAddFiveTime(v.EndTime)
if err != nil {
applogger.Info("restoreEnds err:%v", err)
continue
}
restoreBeginUnix, err := common.TimeStrToTimestamp(restoreBegin)
if err != nil {
applogger.Error("restoreBeginUnix err:%v", err)
continue
}
restoreEndsUnix, err := common.TimeStrToTimestamp(restoreEnds)
if err != nil {
applogger.Error("restoreEndsUnix err:%v", err)
continue
}
//判断当前外汇,是否有任务还在执行
if mapVal, ok := GetValue_Forex(v.TradeName); ok {
if mapVal.EndUnix >= time.Now().Unix() {
applogger.Info(v.TradeName, " is run")
continue
}
}
// 第二次浮点随机数值的端点值(结束浮点)
proportion_value := conversion_forex.Add(decimal.NewFromInt32(int32(v.Step)).Div(proportion_forex))
// 写入缓存中
SetValue_Forex(v.TradeName, ModifyForex{
ForexCode: v.TradeName,
BeginTime: v.BeginTime, // 设置开始时间
EndTime: v.EndTime, // 设置结束时间
Price: price, // 设置价格
ChangePrice: decimal.Zero, // 插针变化价格(开盘价,初始浮点变化区间起始价) - (初始浮点变化区间结束价-动态直至不在变化) - (浮点变化区间结束后的起始价格)
EndPrice: decimal.Zero, // 浮点变化区间结束后的结束价格
Proportion: proportion_value, // 区间浮动率
CheckBool: false,
Digits: v.KeepDecimal,
Step: v.Step,
BeginUnix: begin, // 插针开始时间
EndUnix: ends, // 插针结束时间
RestoreBeginTime: restoreBegin, // 插针恢复开始时间-date
RestoreEndTime: restoreEnds, // 插针恢复结束时间-date
RestoreBeginUnix: restoreBeginUnix, // 插针恢复开始时间-unix
RestoreEndUnix: restoreEndsUnix, // 插针恢复结束时间-unix
RestoreBool: false, // 插针恢复标识
RestoreSetChangePrice: false, // 插针恢复更新
RestoreChangePrice: decimal.Zero, // 插针恢复变化价格(开盘价,初始浮点变化区间起始价) - (初始浮点变化区间结束价-动态直至不在变化) - (浮点变化区间结束后的起始价格)
DataHandling: make(map[int]string),
NumIntervals: 5,
})
forex := model.NewForexMarket()
forex.ID = v.ID
forex.UpdateIsGetOne()
}
}
}
// 外汇插针
func RunModifyForex(param models.ForexJsonData) (models.ForexJsonData, bool) {
// 缓存中插针的交易对信息
val, ok := GetValue_Forex(param.Pair)
if !ok {
return param, false
}
// 不在设置的时间区间范围过滤
if val.BeginUnix > param.Timestamp || val.EndUnix < param.Timestamp {
return param, false
}
applogger.Debug("交易对%v,原始数据:%v", param.Pair, param)
// 数据赋值
changePrice_New := val.ChangePrice
price_new := val.Price
endPrice_new := val.EndPrice
checkBool := val.CheckBool
// 第一次浮点区间的起始价
if val.ChangePrice.IsZero() {
changePrice_New = decimal.NewFromFloat(param.Close) // k线闭盘价(即浮点起始价)
endPrice_new = price_new.Mul(val.Proportion) // 第二次浮点结束价
if changePrice_New.Cmp(price_new) >= 1 {
checkBool = true
}
}
// 平滑价格生成(价格趋向结束价)
var price decimal.Decimal
if checkBool {
// 负增长
if changePrice_New.Cmp(price_new) >= 0 {
changePrice_New = changePrice_New.Div(val.Proportion)
price = changePrice_New
// 更新缓存变量字段
val.ChangePrice = changePrice_New
val.Price = price_new
val.EndPrice = endPrice_new
val.CheckBool = checkBool
SetValue_Forex(val.ForexCode, val)
} else {
randomPrice := RandomBetween(price_new.Div(val.Proportion).InexactFloat64(), endPrice_new.InexactFloat64())
price = decimal.NewFromFloat(randomPrice)
}
} else {
// 正增长
if changePrice_New.Cmp(price_new) <= 0 {
changePrice_New = changePrice_New.Mul(val.Proportion)
price = changePrice_New
// 更新缓存变量字段
val.ChangePrice = changePrice_New
val.Price = price_new
val.EndPrice = endPrice_new
val.CheckBool = checkBool
SetValue_Forex(val.ForexCode, val)
} else {
randomPrice := RandomBetween(changePrice_New.Div(val.Proportion).InexactFloat64(), endPrice_new.InexactFloat64())
price = decimal.NewFromFloat(randomPrice)
}
}
param.Open = changePrice_New.InexactFloat64()
param.Close = price.InexactFloat64()
if price.GreaterThan(decimal.NewFromFloat(param.High)) {
param.High = price.InexactFloat64()
}
if price.LessThan(decimal.NewFromFloat(param.Low)) {
param.Low = price.InexactFloat64()
}
forexJson := models.ForexJsonData{
Event: "CAS",
Pair: param.Pair,
Open: param.Open,
Close: param.Close,
High: param.High,
Low: param.Low,
Volume: param.Volume,
Timestamp: param.Timestamp,
}
applogger.Debug("交易对%v,插针数据:%v", param.Pair, forexJson)
return forexJson, true
}
func RunModifyForexNew(param models.ForexJsonData) (models.ForexJsonData, bool) {
// 缓存中插针的交易对信息
val, ok := GetValue_Forex(param.Pair)
if !ok {
return param, false
}
// 不在设置的时间区间范围过滤
if val.BeginUnix > param.Timestamp || val.EndUnix < param.Timestamp {
if val.RestoreBeginUnix > param.Timestamp || val.RestoreEndUnix < param.Timestamp || val.RestoreBool {
return param, false
} else {
applogger.Debug("恢复交易对%v,原始数据:%v", param.Pair, param)
if val.RestoreChangePrice.IsZero() {
var startT, endT time.Time
var startF, endF float64
startT = common.TimeStringToTime(val.RestoreBeginTime) // 设置插针开始时间
endT = common.TimeStringToTime(val.RestoreEndTime) // 设置插针结束时间
msgTime := DivideTimeInterval(startT, endT, val.NumIntervals) // 时间区间划分
// 判定插针结束价格和当前即时价格
if param.Close == val.ChangePrice.InexactFloat64() {
val.RestoreBool = true
SetValue_Forex(val.ForexCode, val)
return param, false
} else {
startF = val.ChangePrice.InexactFloat64() // 插针结束价格
endF = param.Close // 恢复即时价格
}
applogger.Debug("开始价格:%v,结束价格:%v", startF, endF)
msgFloat := DivideFloatInterval(startF, endF, val.NumIntervals) // 浮点区间划分
var msgTF = make(map[int]string)
for i, mt := range msgTime {
mf, okM := msgFloat[i]
if okM {
msgTF[i] = fmt.Sprintf("%v,%v", mt, mf)
applogger.Debug("msgTF:%v,%v", i, msgTF[i])
}
}
val.DataHandling = msgTF
val.RestoreSetChangePrice = true
}
}
} else {
applogger.Debug("插针交易对%v,原始数据:%v", param.Pair, param)
// 第一次浮点区间的起始价
if val.ChangePrice.IsZero() {
var startT, endT time.Time
var startF, endF float64
val.ChangePrice = decimal.NewFromFloat(param.Close) // k线闭盘价(即浮点起始价)
startT = common.TimeStringToTime(val.BeginTime) // 设置插针开始时间
endT = common.TimeStringToTime(val.EndTime) // 设置插针结束时间
msgTime := DivideTimeInterval(startT, endT, val.NumIntervals) // 时间区间划分
startF = param.Close // 设置插针开始价格
endF = val.Price.InexactFloat64() // 设置插针结束价格
applogger.Debug("开始价格:%v,结束价格:%v", startF, endF)
msgFloat := DivideFloatInterval(startF, endF, val.NumIntervals) // 浮点区间划分
var msgTF = make(map[int]string)
for i, mt := range msgTime {
mf, okM := msgFloat[i]
if okM {
msgTF[i] = fmt.Sprintf("%v,%v", mt, mf)
applogger.Debug("msgTF:%v,%v", i, msgTF[i])
}
}
val.DataHandling = msgTF
}
}
/* 浮点区间价格随机生成浮点值
1判定当前时间戳在区间内
2在区间内则生成对应的浮点值
*/
var price decimal.Decimal
for _, msg := range val.DataHandling {
// msgTF:0,1734426000-1734426108,2652.33-2653.5299999999997
splitMsg := strings.Split(msg, ",")
if len(splitMsg) >= 2 {
splitT := strings.Split(splitMsg[0], "-") // 开始时间~结束时间 1734426000-1734426108
if len(splitT) >= 2 {
start, _ := strconv.Atoi(splitT[0]) // 开始时间 1734426000
end, _ := strconv.Atoi(splitT[1]) // 结束时间 1734426108
if param.Timestamp >= int64(start) && param.Timestamp <= int64(end) {
splitF := strings.Split(splitMsg[1], "-") // 开始浮点~结束浮点 2652.33-2653.5299999999997
if len(splitF) >= 2 {
sf := decimal.RequireFromString(splitF[0]) // 开始浮点 2652.33
ef := decimal.RequireFromString(splitF[1]) // 结束浮点 2653.5299999999997
randomPrice := RandomBetween(sf.InexactFloat64(), ef.InexactFloat64())
price = decimal.NewFromFloat(randomPrice)
val.ChangePrice = price
if val.RestoreSetChangePrice {
val.RestoreChangePrice = price
}
SetValue_Forex(val.ForexCode, val)
}
break // 跳出循环
}
}
}
}
param.Close = price.InexactFloat64()
param.Open = val.ChangePrice.InexactFloat64()
if price.GreaterThan(decimal.NewFromFloat(param.High)) {
param.High = price.InexactFloat64()
}
if price.LessThan(decimal.NewFromFloat(param.Low)) {
param.Low = price.InexactFloat64()
}
forexJson := models.ForexJsonData{
Event: "CAS",
Pair: param.Pair,
Open: param.Open,
Close: param.Close,
High: param.High,
Low: param.Low,
Volume: param.Volume,
Timestamp: param.Timestamp,
}
applogger.Debug("交易对%v,插针数据:%v", param.Pair, forexJson)
return forexJson, true
}
// 在a和b之间生成平滑随机值
func RandomBetween(a, b float64) float64 {
// 确保a小于b
if a > b {
a, b = b, a
}
// 计算范围
rangeVal := (b - a)
// 在范围内生成随机数
return rand.Float64()*rangeVal + a
}
// 时间区间
func DivideTimeInterval(start, end time.Time, numIntervals int) map[int]string {
// 计算两个时间点之间的间隔
duration := end.Sub(start)
// 计算每个区间的持续时间
intervalDuration := duration / time.Duration(numIntervals)
// 生成区间起始时间列表
var msg = make(map[int]string)
for i := 0; i <= numIntervals; i++ {
subStart := start.Add(time.Duration(i) * intervalDuration).Unix()
subEnd := start.Add(time.Duration(i+1) * intervalDuration).Unix()
msg[i] = fmt.Sprintf("%v-%v", subStart, subEnd)
applogger.Debug("时间数据展示:%v,%v", i, msg[i])
}
return msg
}
// 浮点区间
func DivideFloatInterval(start, end float64, numIntervals int) map[int]string {
// 计算每个子区间的宽度
width := (end - start) / float64(numIntervals)
// 生成区间起始时间列表
var msg = make(map[int]string)
// 生成并打印子区间
for i := 0; i <= numIntervals; i++ {
subStart := start + width*float64(i)
subEnd := start + width*float64(i+1)
msg[i] = fmt.Sprintf("%v-%v", decimal.NewFromFloat(subStart), decimal.NewFromFloat(subEnd))
applogger.Debug("浮点数据展示:%v,%v", i, msg[i])
}
return msg
}

517
cmd/selfContract/aggregation.go

@ -0,0 +1,517 @@
package selfContract
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"math/rand"
"time"
"wss-pool/cmd/common"
"wss-pool/cmd/websocketservice"
"wss-pool/internal/gzip"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/market"
"wss-pool/pkg/model/stock"
)
const (
volRand int = 1 //成交张数 随机最大值
maxRand float64 = 1000 //成交笔数 随机最大值
minRand float64 = 0.1
minLeastRand float64 = 1
quantity int = 60 //成交币
tradeNum int = 50
DepthNum int = 80 //深度数据只要
DepthMaxNum int = 300 //深度数据只要
defaultMaxStep float64 = 0.003
defaultMinStep float64 = 0.000001
)
var (
TotalAmount decimal.Decimal // 当前总成交量
TotalTradeTurnover decimal.Decimal //总成交额
TotalVol decimal.Decimal //总成交两张数
TotalCount decimal.Decimal //总成交笔数
oldAsks int64
oldBids int64
)
func ClearTotal() {
TotalAmount = decimal.NewFromFloat(0)
TotalTradeTurnover = decimal.NewFromFloat(0)
TotalVol = decimal.NewFromFloat(0)
TotalCount = decimal.NewFromFloat(0)
}
func CreateDepth(old, closePrice decimal.Decimal) {
chStep6 := fmt.Sprintf("market-%s-depth-step6", SelfContractCode)
//redisStep6, _ := red.Get_Cache_Data(chStep6)
//随机生成单量
rand.New(rand.NewSource(time.Now().UnixNano()))
tick := market.CtDepthTick{
Mrid: rand.Int63n(99999999999) + int64(99999),
Id: common.TimeToNow(),
Ts: common.TimeToNow(),
}
//卖单,买单
tick.Asks, tick.Bids = DepthStep(closePrice, old)
//applogger.Info("asks ", tick.Asks, tick.Bids)
CreateTradeDetail(tick)
//升序
tick.Asks = insertionSort(tick.Asks)
//降序
tick.Bids = quickSort(tick.Bids)
res := market.SubscribeCtDepthResponse{
Channel: chStep6,
Timestamp: common.TimeToNow(),
Tick: &tick,
}
resByte, _ := json.Marshal(res)
resStep6, _ := gzip.DecompressData(resByte)
OneDayContractDepth(res)
red.RedisClient.Publish(res.Channel, resStep6)
}
// 卖单 买单
func DepthStep(closePrice, old decimal.Decimal) ([][]decimal.Decimal, [][]decimal.Decimal) {
data := make([][]decimal.Decimal, 0)
datas := make([][]decimal.Decimal, 0)
if old.Equal(closePrice) {
return data, datas
}
amaxRand := float64(0)
bmaxRand := float64(0)
//跌
if old.GreaterThan(closePrice) {
diff := old.Sub(closePrice)
amaxRand, _ = diff.Div(old).Mul(decimal.NewFromFloat(maxRand)).Round(2).Float64()
bmaxRand = amaxRand / float64(3)
} else {
diff := closePrice.Sub(old)
bmaxRand, _ = diff.Div(old).Mul(decimal.NewFromFloat(maxRand)).Round(2).Float64()
amaxRand = bmaxRand / float64(3)
}
asksMap := make(map[string]int)
bidsMap := make(map[string]int)
minRands := minRand
if closePrice.LessThan(decimal.NewFromInt(int64(1))) {
minRands = minLeastRand
}
for i := 0; i < DepthNum; i++ {
item := make([]decimal.Decimal, 0)
price := calculateMaxMinContractPrice(closePrice, false)
if _, ok := asksMap[price.String()]; ok {
continue
}
item = append(item, price)
item = append(item, decimal.NewFromFloat(common.RandFloats(minRands, amaxRand)))
data = append(data, item)
asksMap[price.String()] = 1
if len(data) >= 20 {
break
}
}
for i := 0; i < DepthNum; i++ {
item := make([]decimal.Decimal, 0)
price := calculateMaxMinContractPrice(closePrice, true)
if _, ok := bidsMap[price.String()]; ok {
continue
}
item = append(item, price)
item = append(item, decimal.NewFromFloat(common.RandFloats(minRands, bmaxRand)))
datas = append(datas, item)
bidsMap[price.String()] = 1
if len(datas) >= 20 {
break
}
}
return data, datas
}
// 生成一天聚合深度
func OneDayContractDepth(item market.SubscribeCtDepthResponse) {
result := market.SubscribeCtDepthTempResponse{}
result.Tick = &market.CtDepthTick{}
title := fmt.Sprintf("market-%s-depth-step6", SelfContractCode)
rand.New(rand.NewSource(time.Now().UnixNano()))
result.Tick.Mrid = rand.Int63n(99999999999) + int64(99999)
result.Tick.Id = common.TimeToNow() * 1000
result.Tick.Ts = common.TimeToNow() * 1000
result.Tick.Asks = insertionSort(item.Tick.Asks)
result.Tick.Bids = quickSort(item.Tick.Bids)
result.Channel = fmt.Sprintf("market.%s.depth.step6", SelfContractCode)
resultJsons, _ := json.Marshal(result)
red.Set_Cache_Value(title, string(resultJsons))
return
//titleInfo := fmt.Sprintf("market-%s-depth-step6-info", SelfContractCode)
//redisRes, _ := red.Get_Cache_Data(title)
//if common.GetWeeHours() || redisRes == "" {
// itemJson, _ := json.Marshal(item)
// if err := red.Set_Cache_Value(title, string(itemJson)); err != nil {
// applogger.Error(title, err)
// }
// //if err := red.Set_Cache_Value(titleInfo, string(itemJson)); err != nil {
// // applogger.Error(title, err)
// //}
// return
//}
//result := market.SubscribeCtDepthTempResponse{}
//if err := json.Unmarshal([]byte(redisRes), &result); err != nil {
// applogger.Error("json err", err)
// return
//}
//bids := make(map[decimal.Decimal]decimal.Decimal, 0)
//asks := make(map[decimal.Decimal]decimal.Decimal, 0)
//for _, v := range result.Tick.Bids {
// bids[v[0]] = v[1]
//}
//for _, v := range result.Tick.Asks {
// asks[v[0]] = v[1]
//}
//bidsTemp := make([][]decimal.Decimal, 0)
//bidsAbandonMap := make(map[decimal.Decimal]int)
//// fmt.Println("bids map" ,bids)
//// fmt.Println("bids",result.Tick.Bids)
//for _, v := range item.Tick.Bids {
// value := make([]decimal.Decimal, 0)
// if len(v) <= 0 {
// continue
// }
// if num, ok := bids[v[0]]; !ok {
// value = append(value, v[0])
// value = append(value, v[1].Add(num))
// bidsTemp = append(bidsTemp, value)
// bidsAbandonMap[v[0]] = 1
// } else {
// value = append(value, v[0])
// value = append(value, v[1])
// bidsTemp = append(bidsTemp, value)
// }
//}
////fmt.Println("bidsAb",bidsAbandonMap)
//for key, val := range bids {
// if _, ok := bidsAbandonMap[key]; !ok {
// value := make([]decimal.Decimal, 0)
// value = append(value, key)
// value = append(value, val)
// bidsTemp = append(bidsTemp, value)
// }
//}
//asksTemp := make([][]decimal.Decimal, 0)
//asksAbandonMap := make(map[decimal.Decimal]int)
//for _, v := range item.Tick.Asks {
// if len(v) <= 0 {
// continue
// }
// value := make([]decimal.Decimal, 0)
// if num, ok := asks[v[0]]; ok {
// value = append(value, v[0])
// value = append(value, v[1].Add(num))
// asksAbandonMap[v[0]] = 1 //收集起来
// asksTemp = append(asksTemp, value)
// } else {
// value = append(value, v[0])
// value = append(value, v[1])
// asksTemp = append(asksTemp, value)
// }
//}
//for key, val := range asks {
// if _, ok := asksAbandonMap[key]; !ok {
// value := make([]decimal.Decimal, 0)
// value = append(value, key)
// value = append(value, val)
// asksTemp = append(asksTemp, value)
// }
//}
//result.Channel = fmt.Sprintf("market.%s.depth.step6-info", SelfContractCode)
//result.Tick.Asks = asksTemp
//result.Tick.Bids = bidsTemp
//result.Tick.Mrid = rand.Int63n(99999999999) + int64(99999)
//result.Tick.Id = common.TimeToNow() * 1000
//result.Tick.Ts = common.TimeToNow() * 1000
////resultJson, _ := json.Marshal(result)
//////所有的买卖保存
////if err := red.Set_Cache_Value(titleInfo, string(resultJson)); err != nil {
//// applogger.Error(title, err)
////}
////推给前端数据
//if len(asksTemp) >= DepthNum {
// asksTemp = asksTemp[:DepthNum]
// //fmt.Println(len(asksTemp))
// //os.Exit(11)
//}
//if len(bidsTemp) >= DepthNum {
// bidsTemp = bidsTemp[:DepthNum]
//}
////result := market.SubscribeCtDepthTempResponse{}
//result.Tick.Asks = insertionSort(asksTemp)
//result.Tick.Bids = quickSort(bidsTemp)
//result.Channel = fmt.Sprintf("market.%s.depth.step6", SelfContractCode)
//resultJsons, _ := json.Marshal(result)
//red.Set_Cache_Value(title, string(resultJsons))
//applogger.Info("OneDayContractDepth ", string(resultJsons))
}
// 生成一天聚合行情
func OneDayDetailMerged(param market.SubscribeCtKlineResponse) {
title := fmt.Sprintf("market-%s-detail-merged", SelfContractCode)
chStep6 := fmt.Sprintf("market-%s-depth-step6", SelfContractCode)
redisStep6, _ := red.Get_Cache_Data(chStep6)
resultStep6 := market.SubscribeCtDepthResponse{}
if err := json.Unmarshal([]byte(redisStep6), &resultStep6); err != nil {
applogger.Error("OneDayContractDepth json err", err)
return
}
result := market.SubscribeCtDetailResponse{}
tick := &market.CtDetailTick{}
result.Channel = title
rand.Seed(time.Now().UnixNano())
var totalPriceAsks decimal.Decimal
var totalNumAsks decimal.Decimal
for _, v := range resultStep6.Tick.Asks {
if len(v) <= 0 {
continue
}
totalPriceAsks = totalPriceAsks.Add(v[0])
totalNumAsks = totalNumAsks.Add(v[1])
}
if len(resultStep6.Tick.Asks) > 0 {
value := make([]decimal.Decimal, 0)
value = append(value, totalPriceAsks.Div(decimal.NewFromInt(int64(len(resultStep6.Tick.Asks)))).Round(digits))
value = append(value, totalNumAsks)
tick.Asks = value
}
bidsTemp := make([][]decimal.Decimal, 0)
var totalPriceBids decimal.Decimal
var totalNumBids decimal.Decimal
for _, v := range resultStep6.Tick.Bids {
if len(v) <= 0 {
continue
}
totalPriceBids = totalPriceBids.Add(v[0])
totalNumBids = totalNumBids.Add(v[1])
}
if len(resultStep6.Tick.Bids) > 0 {
value := make([]decimal.Decimal, 0)
value = append(value, totalPriceBids.Div(decimal.NewFromInt(int64(len(resultStep6.Tick.Bids)))).Round(digits))
value = append(value, totalNumBids)
bidsTemp = append(bidsTemp, value)
tick.Bids = value
}
tick.Mrid = rand.Int63n(99999999999) + int64(99999)
tick.Id = common.TimeToNow()
tick.TradeTurnover = param.Tick.Rrade_Turnover
tick.Count = param.Tick.Count.IntPart()
tick.High = param.Tick.High
tick.Open = param.Tick.Open
tick.Vol = param.Tick.Vol
tick.Close = param.Tick.Close
tick.Low = param.Tick.Low
tick.Amount = param.Tick.Amount
result.Tick = tick
result.Timestamp = common.TimeToNow()
result.Channel = fmt.Sprintf("market.%s.detail.merged", SelfContractCode)
resultJson, _ := json.Marshal(result)
if err := red.Set_Cache_Value(title, string(resultJson)); err != nil {
applogger.Error(title, err)
}
//applogger.Info("OneDayContractDepth ", string(resultJson))
}
// 生成详情
func CreateTradeDetail(tick market.CtDepthTick) {
res := market.SubscribeCtTradeDetailResponse{}
res.Tick = &market.CtTradeDetailTick{}
res.Tick.Data = make([]market.TradeDetail, 0)
TotalCount = decimal.NewFromInt(0)
//最近交易接口
tradeDetailAPI := make([]stock.MarketTrade, 0)
for key, v := range tick.Asks {
if key >= 1 {
break
}
trade := v[1].Mul(FaceValue).Mul(v[0]) //每一笔成交张数 * 合约面值 * 成交价格
item := market.TradeDetail{
Amount: v[1],
Ts: common.TimeToNow() * 1000,
Id: common.TimeToNow() * 1000,
Price: v[0],
Direction: "sell",
Quantity: trade.Div(v[0]).Round(digits), //成交币
TradeTurnover: trade,
}
//总成交额
//TotalTradeTurnover = TotalTradeTurnover.Add(trade)
//总成交量
//TotalAmount = TotalAmount.Add(item.Quantity)
//成交张数
//TotalVol = TotalVol.Add(v[1])
res.Tick.Data = append(res.Tick.Data, item)
apiItem := stock.MarketTrade{
ID: common.TimeToNow() * 1000,
OrderNumber: item.Amount.String(),
DealPrice: item.Price.String(),
OrderTime: common.TimeToNow() * 1000,
TradeType: 2,
TradeTurnover: trade,
}
tradeDetailAPI = append(tradeDetailAPI, apiItem)
}
res.Channel = fmt.Sprintf("market.%s.trade.detail", SelfContractCode)
res.Timestamp = common.TimeToNow() * 1000
if len(res.Tick.Data) > 0 {
// fmt.Println(res.Tick.Data)
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: res.Channel,
Content: res,
Symbol: res.Channel})
//applogger.Info("CreateTradeDetail sell:", string(jsonMessage))
red.RedisClient.Publish(res.Channel, string(jsonMessage))
}
//总成交笔数
//TotalCount = TotalCount.Add(decimal.NewFromInt(int64(len(tick.Asks))))
res = market.SubscribeCtTradeDetailResponse{}
res.Tick = &market.CtTradeDetailTick{}
res.Tick.Data = make([]market.TradeDetail, 0)
res.Channel = fmt.Sprintf("market.%s.trade.detail", SelfContractCode)
for key, v := range tick.Bids {
if key >= 1 {
break
}
trade := v[1].Mul(FaceValue).Mul(v[0]) //每一笔成交张数 * 合约面值 * 成交价格
item := market.TradeDetail{
Amount: v[1], //张数
Ts: common.TimeToNow() * 1000,
Id: common.TimeToNow() * 1000,
Price: v[0],
Direction: "buy",
Quantity: trade.Div(v[0]).Round(digits), //成交币
TradeTurnover: trade,
}
//总成交额
TotalTradeTurnover = TotalTradeTurnover.Add(trade)
//总成交量
TotalAmount = TotalAmount.Add(item.Quantity)
//成交张数
TotalVol = TotalVol.Add(v[1])
res.Tick.Data = append(res.Tick.Data, item)
apiItem := stock.MarketTrade{
ID: common.TimeToNow() * 1000,
OrderNumber: item.Amount.String(),
DealPrice: item.Price.String(),
OrderTime: common.TimeToNow() * 1000,
TradeType: 1,
TradeTurnover: trade,
}
tradeDetailAPI = append(tradeDetailAPI, apiItem)
}
res.Timestamp = common.TimeToNow() * 1000
//总成交笔数
TotalCount = TotalCount.Add(decimal.NewFromInt(int64(len(tick.Bids))))
if len(res.Tick.Data) > 0 {
//fmt.Println(res.Tick.Data)
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: res.Channel,
Content: res,
Symbol: res.Channel})
//applogger.Info("CreateTradeDetail buy:", string(jsonMessage))
go func(channel string, jsonMessage []byte) {
time.Sleep(1 * time.Second)
red.RedisClient.Publish(channel, string(jsonMessage))
}(res.Channel, jsonMessage)
}
//缓存
title := fmt.Sprintf("market-%s-trade-detail", SelfContractCode)
var resultJson []byte
//if len(tradeDetailAPI) < tradeNum {
// item, _ := red.Get_Cache_Data(title)
// //itemJson, _ := json.Marshal(item)
// res := make([]stock.MarketTrade, 0)
// json.Unmarshal([]byte(item), &res)
// l := len(res)
// res = append(res, tradeDetailAPI...)
// if l > 0 {
// res = res[l:]
// }
// resultJson, _ = json.Marshal(res)
//} else {
resultJson, _ = json.Marshal(tradeDetailAPI)
// }
if err := red.Set_Cache_Value(title, string(resultJson)); err != nil {
applogger.Error(title, err)
}
// os.Exit(11111)
//
// applogger.Info(string(resultJson))
}
func calculateMaxMinContractPrice(basePrice decimal.Decimal, isNegative bool) decimal.Decimal {
rand.New(rand.NewSource(time.Now().UnixNano()))
max := basePrice.Mul(decimal.NewFromFloat(rand.Float64()*defaultMinStep + rand.Float64()*(defaultMaxStep-defaultMinStep))).Round(digits)
// fmt.Println(max)
if isNegative {
return basePrice.Sub(max).Round(digits)
}
return basePrice.Add(max).Round(digits)
}
// 买单不能高于当前价,卖但不能低于当前价
func randDepth(max, min int, closePrice, old decimal.Decimal) ([][]decimal.Decimal, [][]decimal.Decimal) {
data := make([][]decimal.Decimal, 0)
datas := make([][]decimal.Decimal, 0)
//item := make([]decimal.Decimal, 0)
//item = append(item, calculateMaxMinContractPrice(closePrice, false))
//item = append(item, decimal.NewFromInt(int64(max)))
//data = append(data, item)
//item = make([]decimal.Decimal, 0)
//item = append(item, calculateMaxMinContractPrice(closePrice, true))
//item = append(item, decimal.NewFromInt(int64(min)))
//datas = append(datas, item)
//}
//if old.GreaterThan(closePrice) {
// //跌
// return data, datas
//}
//dataTemp := make([][]decimal.Decimal, 0)
//datasTemp := make([][]decimal.Decimal, 0)
//item = make([]decimal.Decimal, 0)
//item = append(item, data[0][0])
//item = append(item, datas[0][1])
//dataTemp = append(dataTemp, item)
//item = make([]decimal.Decimal, 0)
//item = append(item, datas[0][0])
//item = append(item, data[0][1])
//datasTemp = append(datasTemp, item)
return data, datas
}
// 升序
func insertionSort(nums [][]decimal.Decimal) [][]decimal.Decimal {
n := len(nums)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if nums[j][0].GreaterThan(nums[j+1][0]) {
nums[j], nums[j+1] = nums[j+1], nums[j]
}
}
}
return nums
}
// 降序
func quickSort(nums [][]decimal.Decimal) [][]decimal.Decimal {
n := len(nums)
for i := 0; i < n-1; i++ {
minIdx := i
for j := i + 1; j < n; j++ {
if nums[j][0].GreaterThan(nums[minIdx][0]) {
minIdx = j
}
}
nums[i], nums[minIdx] = nums[minIdx], nums[i]
}
return nums
}

57
cmd/selfContract/mongo.go

@ -0,0 +1,57 @@
package selfContract
import (
"fmt"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"wss-pool/internal/data"
)
func GetNewPrice(contractCode string) []data.MongoTick {
filter := bson.M{"channel": fmt.Sprintf("market.%s.kline.1min", contractCode)}
tableName := data.GetContractKLineTableName("1min")
pagedData, _ := data.MgoLimitFind(tableName, filter, int64(1))
return pagedData
}
func GetNewPriceAll(contractCode, period string) []data.MongoTick {
filter := bson.M{"channel": fmt.Sprintf("market.%s.kline.%s", contractCode, period)}
tableName := data.GetContractKLineTableName(period)
pagedData, _ := data.MgoLimitFind(tableName, filter, int64(1))
return pagedData
}
func GetTimeNewPrice(contractCode string, from, to int64, period string) (decimal.Decimal, decimal.Decimal, decimal.Decimal, decimal.Decimal, decimal.Decimal, decimal.Decimal) {
filter := bson.M{"channel": fmt.Sprintf("market.%s.kline.%s", contractCode, period), "code": bson.M{"$gte": from, "$lte": to}}
tableName := data.GetContractKLineTableName(period)
pagedData, _ := data.MgoLimitFind(tableName, filter, int64(0))
var low, high, vol, amount, count, tradeTurnover decimal.Decimal
for key, v := range pagedData {
lows, _ := decimal.NewFromString(v.Low)
highs, _ := decimal.NewFromString(v.High)
vols, _ := decimal.NewFromString(v.Vol)
amounts, _ := decimal.NewFromString(v.Amount)
counts, _ := decimal.NewFromString(v.Count.(string))
tradeTurnovers, _ := decimal.NewFromString(v.TradeTurnover)
if key == 0 {
low = lows
high = highs
vol = vols
amount = amounts
count = counts
tradeTurnover = tradeTurnovers
continue
}
vol = vol.Add(vols)
amount = vol.Add(amounts)
count = vol.Add(counts)
tradeTurnover = tradeTurnovers.Add(tradeTurnover)
if low.GreaterThan(lows) {
low = lows
}
if high.LessThan(highs) {
high = highs
}
}
return low, high, vol, amount, count, tradeTurnover
}

723
cmd/selfContract/virtualContract.go

@ -0,0 +1,723 @@
package selfContract
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"math/rand"
"sync"
"time"
"wss-pool/cmd/common"
"wss-pool/cmd/websocketservice"
"wss-pool/dictionary"
"wss-pool/internal/data/business"
"wss-pool/internal/model"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/market"
)
const (
proportion int32 = 10000 //调价原价比例
step int32 = 20 //调价指数
digits int32 = 4 //保留小数位数
numPrices int = 20 //生成随机价格数量
defaultStep float64 = 0.001 // 默认波动率
PushFrequency int = 3 //S
TimeRemaining int64 = 20 //s
)
var (
contractChan = make(chan string)
SelfContractCode string //合约代码
FaceValue decimal.Decimal // 面值合约
InitialPrice decimal.Decimal // 初始价格
KLineMap = make(map[string]KlineLowHigh)
ClosePrices decimal.Decimal //当前价格 //做调价终止值使用
OldFiveMin int64 //记录K线时间搓
OldFifteenMin int64
OldThirtyMin int64
OldOneHour int64
OldFourHour int64
OldDay int64
OldWeek int64
OldMon int64
IsRun bool
ContractMap = make(map[string]bool) //保存领取的任务
lock sync.Mutex
endChan = make(chan string)
)
type KlineLowHigh struct {
Low decimal.Decimal
High decimal.Decimal
ID int64
Vol decimal.Decimal
Amount decimal.Decimal
Count decimal.Decimal
TradeTurnover decimal.Decimal
Open decimal.Decimal
}
type ConstructorContract struct {
SelfContractCode string `json:"selfContractCode"` //虚拟合约
BeginTime string `json:"beginTime"` //开始时间
EndTime string `json:"endTime"` //结束时间
MaxPrice decimal.Decimal `json:"maxPrices"`
MinPrice decimal.Decimal `json:"minPrice"`
MaxPriceStr string `json:"maxPrice"`
}
func NewSelfContract() {
//首次启动
go func() {
this := new(ConstructorContract)
this.MinPrice = InitialPrice
this.SelfContractCode = SelfContractCode
this.defaultContract()
}()
for {
t := time.NewTimer(1 * time.Minute)
<-t.C
go func() {
if IsRun {
applogger.Info("已有调价在运行, 该次调价不能运行")
return
}
result := getData()
fmt.Println(result)
for _, v := range result {
if v.TradeName != SelfContractCode {
applogger.Info("parametric inequality")
continue
}
end, _ := common.TimeStrToTimes(v.EndTime)
if end.Unix() <= time.Now().Unix() {
applogger.Info("该调价已过期")
continue
}
if _, ok := ContractMap[fmt.Sprintf("%s-%d", v.TradeName, end.Unix())]; ok {
applogger.Info("该任务正在运行", fmt.Sprintf("%s-%d", v.TradeName, end.Unix()))
continue
}
ContractMap[fmt.Sprintf("%s-%d", v.TradeName, end.Unix())] = true //保存任务
begin, _ := common.TimeStrToTimes(v.BeginTime)
if begin.Unix() < common.TimeToNow() {
applogger.Info("该调价已过期")
continue
} else if end.Unix() <= begin.Unix() {
applogger.Info("begin end 有误")
continue
}
maxPrice, _ := decimal.NewFromString(v.MaxPrice)
//if maxPrice.LessThan(InitialPrice) {
// applogger.Info("调价有误",v.MaxPrice)
// continue
//}
//等待到开始时间
applogger.Info("等待到begin time ", v.BeginTime, v.EndTime)
IsRun = true // 调价任务已开启
time.Sleep(begin.Sub(common.TimeToNows()))
contractChan <- "constructorStart"
this := new(ConstructorContract)
this.SelfContractCode = SelfContractCode
this.EndTime = v.EndTime
this.MinPrice = InitialPrice
this.MaxPrice = maxPrice
this.BeginTime = v.BeginTime
this.constructor(begin, end)
//更改状态
contract := model.NewContractMarket()
contract.ID = v.ID
contract.UpdateIsGetOne()
}
if len(result) > 0 && IsRun {
IsRun = false
this := new(ConstructorContract)
this.MinPrice = InitialPrice
this.SelfContractCode = SelfContractCode
this.defaultContract()
}
}()
}
}
func getData() []model.ContractMarket {
contract := model.NewContractMarket()
contract.IsType = model.Contract
contract.TradeName = SelfContractCode
result := contract.List()
return result
}
// 规定启动
func (this *ConstructorContract) constructor(begin, end time.Time) {
applogger.Info("开始调价。。。", "结束时间:", this.EndTime, "当前价格:", this.MinPrice, "最终价格", this.MaxPrice)
totalDuration := end.Sub(common.TimeToNows())
closePrice := this.MinPrice
highPrice := this.MinPrice
lowPrice := this.MinPrice
oldPrice := this.MinPrice
var openPrice decimal.Decimal
//var temporalFrequency = time.Duration(PushFrequency) * time.Second
timeInterval := float64(totalDuration) / float64(time.Minute)
delta := this.MaxPrice.Sub(closePrice).Div(decimal.NewFromFloat(timeInterval))
applogger.Debug("-------------------------delta", delta)
fluctuation := closePrice.Mul(decimal.NewFromInt32(step).Div(decimal.NewFromInt32(proportion))).Round(digits)
applogger.Info("timeInterval", timeInterval, "delta", delta, "fluctuation", fluctuation)
// 开始进行调价
for !this.MaxPrice.Equal(InitialPrice) || end.Unix() > common.TimeToNows().Unix() {
// 生成随机价格波动
fmt.Println("当前价格", InitialPrice)
prices := this.generateRandomPrices(InitialPrice, fluctuation, delta)
openPrice = InitialPrice
this.GetAllLowHigh(highPrice, lowPrice)
nonVanishing(lowPrice)
numFrequency := int(60) / PushFrequency
ClearTotal()
for i := 1; i <= numFrequency; i++ {
//go func(prices []decimal.Decimal, closePrice, openPrice, highPrice, lowPrice, oldPrice decimal.Decimal, this *ConstructorContract) {
start := time.Now() // 获取当前时间
rand.New(rand.NewSource(time.Now().UnixNano()))
key := rand.Intn(len(prices))
closePrice = prices[key]
// applogger.Info("实际落盘价:", closePrice)
//if (closePrice.Sub(this.MaxPrice)).Mul(delta).GreaterThan(decimal.NewFromInt32(0)){
// closePrice = this.MaxPrice
//}
//当只剩 20 秒
if (end.Unix() - common.TimeToNows().Unix()) < TimeRemaining {
closePrice = this.MaxPrice
}
FaceValue = common.GetFaceValue(closePrice)
//生成深度、Trade Detail 数据
CreateDepth(oldPrice, closePrice)
//k 线 详情
this.pullStorage(closePrice, openPrice, highPrice, lowPrice, oldPrice, prices)
oldPrice = closePrice
applogger.Debug("目标价格", this.MaxPrice, "当前价格", closePrice, "调价结束时间", this.EndTime, "i", i, time.Now().Format("2006-01-02 15:04:05"))
//更新市价
lock.Lock()
InitialPrice = closePrice
lock.Unlock()
if end.Unix() <= common.TimeToNows().Unix() {
break
}
fmt.Println("Run time: ", time.Since(start))
s := float64(PushFrequency) - time.Since(start).Seconds()
if s > float64(0) {
applogger.Debug("停留 秒", s)
time.Sleep(time.Duration(s) * time.Second)
}
}
}
}
func (this *ConstructorContract) defaultContract() {
closePrice := this.MinPrice
highPrice := this.MinPrice
lowPrice := this.MinPrice
oldPrice := this.MinPrice
var openPrice decimal.Decimal
//var temporalFrequency = time.Duration(PushFrequency) * time.Second
for {
select {
case _, ok := <-contractChan: // 从管道接收值
if ok {
applogger.Info("calculateContractPrice start,defaultContract")
return
}
default:
//fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
rand.New(rand.NewSource(time.Now().UnixNano()))
openPrice = InitialPrice
Loop:
prices := calculateContractPrice(InitialPrice)
fmt.Println("开盘价", InitialPrice)
//更新落盘价、开盘价、最高价和最低价
key := rand.Intn(numPrices)
highPrices := this.getMaxPrices(openPrice, prices)
lowPrices := this.getMinPrices(openPrice, prices)
//被2整除 涨
if key%2 == 0 {
highPrice = this.getMaxPrice(openPrice, highPrices)
lowPrice = openPrice
prices = highPrices
} else {
highPrice = openPrice
lowPrice = this.getMinPrice(openPrice, lowPrices)
prices = lowPrices
}
if len(prices) <= 0 {
goto Loop
}
this.GetAllLowHigh(highPrice, lowPrice)
nonVanishing(lowPrice)
numFrequency := int(60) / PushFrequency
ClearTotal()
for i := 1; i <= numFrequency; i++ {
// start := time.Now() // 获取当前时间
// fmt.Println("Run time: ", time.Since(start))
go func(prices []decimal.Decimal, closePrice, openPrice, highPrice, lowPrice, oldPrice decimal.Decimal, this *ConstructorContract) {
//更新市价
//fmt.Println(closePrice)
rand.New(rand.NewSource(time.Now().UnixNano()))
key := rand.Intn(len(prices))
closePrice = prices[key]
lock.Lock()
InitialPrice = closePrice
lock.Unlock()
FaceValue = common.GetFaceValue(closePrice)
//生成深度、Trade Detail 数据
CreateDepth(oldPrice, closePrice)
//fmt.Println("Run time: ", time.Since(start))
this.pullStorage(closePrice, openPrice, highPrice, lowPrice, oldPrice, prices)
//fmt.Println("123123123 ", closePrice,openPrice, highPrice, lowPrice, oldPrice)
oldPrice = closePrice
}(prices, closePrice, openPrice, highPrice, lowPrice, oldPrice, this)
time.Sleep(time.Duration(int64(PushFrequency)) * time.Second)
}
}
}
}
func generateRandomStep(min, max decimal.Decimal) decimal.Decimal {
rand.New(rand.NewSource(time.Now().UnixNano()))
return min.Add(decimal.NewFromFloat(rand.Float64()).Mul(max.Sub(min)))
}
// 计算虚拟合约价格
func calculateContractPrice(basePrice decimal.Decimal) []decimal.Decimal {
prices := make([]decimal.Decimal, 0)
max := basePrice.Mul(decimal.NewFromFloat(defaultStep)).Round(digits)
min := max.Neg()
for i := 0; i < numPrices; i++ {
price := basePrice.Add(generateRandomStep(max, min)).Round(digits)
prices = append(prices, price)
}
return prices
}
func getOpen(timestamp int64, period string) decimal.Decimal {
tick := GetNewPriceAll(SelfContractCode, period)
open := InitialPrice
if len(tick) > 0 {
switch tick[0].Code {
case timestamp:
open, _ = decimal.NewFromString(tick[0].Open)
default:
open, _ = decimal.NewFromString(tick[0].Close)
}
}
return open
}
func nonVanishing(low decimal.Decimal) {
for k, v := range KLineMap {
if v.Low.IsZero() {
v.Low = low
}
KLineMap[k] = v
}
}
func (this *ConstructorContract) GetAllLowHigh(high, low decimal.Decimal) {
//初始化
if len(KLineMap) == 0 {
KLineMap["1min"] = KlineLowHigh{
ID: common.GenerateSingaporeMinuteTimestamp(),
Low: low,
High: high,
Vol: decimal.NewFromInt(0),
Amount: decimal.NewFromInt(0),
Count: decimal.NewFromInt(0),
TradeTurnover: decimal.NewFromInt(0),
Open: InitialPrice,
}
to := common.GenerateSingaporeFiveMinTimestamp()
from := to - int64(5*60)
low, high, vol, amount, count, tradeTurnover := GetTimeNewPrice(this.SelfContractCode, from, to, "1min")
KLineMap["5min"] = KlineLowHigh{
ID: to,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(to, "5min"),
}
OldFiveMin = to
to = common.GenerateSingaporeFifteenMinTimestamp()
from = to - int64(15*60)
low, high, vol, amount, count, tradeTurnover = GetTimeNewPrice(this.SelfContractCode, from, to, "5min")
KLineMap["15min"] = KlineLowHigh{
ID: to,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(to, "15min"),
}
OldFifteenMin = to
to = common.GenerateSingaporeThirtyMinTimestamp()
from = to - int64(30*60)
low, high, vol, amount, count, tradeTurnover = GetTimeNewPrice(this.SelfContractCode, from, to, "15min")
KLineMap["30min"] = KlineLowHigh{
ID: to,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(to, "30min"),
}
OldThirtyMin = to
from = common.GenerateSingaporeHourTimestamp()
to = from + int64(60*59)
low, high, vol, amount, count, tradeTurnover = GetTimeNewPrice(this.SelfContractCode, from, to, "30min")
KLineMap["60min"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(from, "60min"),
}
OldOneHour = from
to = common.GenerateSingaporeFourHourTimestamp()
from = to - (4 * 60 * 60)
low, high, vol, amount, count, tradeTurnover = GetTimeNewPrice(this.SelfContractCode, from, to, "60min")
KLineMap["4hour"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(from, "4hour"),
}
OldFourHour = from
from = common.GenerateSingaporeDayTimestamp("")
to = from + int64(60*60*24-1)
low, high, vol, amount, count, tradeTurnover = GetTimeNewPrice(this.SelfContractCode, from, to, "4hour")
KLineMap["1day"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(from, "1day"),
}
OldDay = from
from = common.GenerateSingaporeMonTimestamp()
to = common.TimeToNow()
low, high, vol, amount, count, tradeTurnover = GetTimeNewPrice(this.SelfContractCode, from, to, "1week")
KLineMap["1mon"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(from, "1mon"),
}
OldMon = from
from = common.GetWeekTimestamp()
to = common.TimeToNow()
low, high, vol, amount, count, tradeTurnover = GetTimeNewPrice(this.SelfContractCode, from, to, "1day")
KLineMap["1week"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
TradeTurnover: tradeTurnover,
Open: getOpen(from, "1week"),
}
OldWeek = from
}
for k, v := range KLineMap {
switch k {
case "1min":
v.ID = common.GenerateSingaporeMinuteTimestamp()
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.High = high
v.Low = low
v.Open = InitialPrice
case "5min":
v.ID = common.GenerateSingaporeFiveMinTimestamp()
if v.ID > OldFiveMin {
//新时间节点更新
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldFiveMin = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "15min":
v.ID = common.GenerateSingaporeFifteenMinTimestamp()
if v.ID > OldFifteenMin {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldFifteenMin = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "30min":
v.ID = common.GenerateSingaporeThirtyMinTimestamp()
if v.ID > OldThirtyMin {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldThirtyMin = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "60min":
v.ID = common.GenerateSingaporeHourTimestamp()
if v.ID > OldOneHour {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldOneHour = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "4hour":
v.ID = common.GenerateSingaporeFourHourTimestamp() - (4 * 60 * 60)
if v.ID > OldFourHour {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldFourHour = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "1day":
v.ID = common.GenerateSingaporeDayTimestamp("")
if v.ID > OldDay {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldDay = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "1week":
v.ID = common.GetWeekTimestamp()
if v.ID > OldWeek {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldWeek = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "1mon":
v.ID = common.GenerateSingaporeMonTimestamp()
if v.ID > OldMon {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.TradeTurnover = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldMon = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
}
KLineMap[k] = v
}
}
func (this *ConstructorContract) pullStorage(close, open, high, low, oldPrice decimal.Decimal, prices []decimal.Decimal) {
for _, v := range dictionary.ContractPriceTime {
resp := market.SubscribeCtKlineResponse{
Channel: fmt.Sprintf("market.%s.kline.%s", this.SelfContractCode, v),
Timestamp: KLineMap[v].ID,
Tick: &market.CtKlineTick{
Open: KLineMap[v].Open,
High: KLineMap[v].High,
Low: KLineMap[v].Low,
Close: close,
Id: KLineMap[v].ID,
Vol: TotalVol.Add(KLineMap[v].Vol),
Count: TotalCount.Add(KLineMap[v].Count),
Amount: TotalAmount.Add(KLineMap[v].Amount),
Rrade_Turnover: TotalTradeTurnover.Add(KLineMap[v].TradeTurnover),
Mrid: int64(rand.Intn(99999999999999) + 99999),
},
}
if v == "1day" {
go OneDayDetailMerged(resp)
}
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Content: resp,
Symbol: resp.Channel})
//applogger.Info("SubscribeCtKline %s:", string(jsonMessage), v)
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
go business.UpdateSubscribeCtKline(resp)
}
//合约详情
detail := market.SubscribeCtDetailResponse{
Channel: fmt.Sprintf("market.%s.detail", this.SelfContractCode),
Timestamp: time.Now().Unix(),
Tick: &market.CtDetailTick{
Open: open,
High: high,
Low: low,
Close: close,
Id: time.Now().Unix(),
Vol: TotalVol,
Count: TotalCount.IntPart(),
Amount: TotalAmount,
TradeTurnover: TotalTradeTurnover,
},
}
jsonMessages, _ := json.Marshal(websocketservice.Message{
ServersId: detail.Channel,
Content: detail,
Symbol: detail.Channel})
//applogger.Info("detail :", string(jsonMessages))
red.RedisClient.Publish(detail.Channel, string(jsonMessages))
}
// 生成随机价格波动
func (this *ConstructorContract) generateRandomPrices(currentPrice, fluctuation, delta decimal.Decimal) []decimal.Decimal {
prices := make([]decimal.Decimal, 0)
rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < numPrices; i++ {
price := currentPrice.Add(delta.Add(fluctuation.Mul(decimal.NewFromFloat(2*rand.Float64() - 1)))).Round(digits)
prices = append(prices, price)
}
return prices
}
// 获取最高价
func (this *ConstructorContract) getMaxPrices(highPrice decimal.Decimal, prices []decimal.Decimal) []decimal.Decimal {
res := make([]decimal.Decimal, 0)
for _, price := range prices {
if price.GreaterThan(highPrice) {
res = append(res, price)
}
}
return res
}
func (this *ConstructorContract) getMaxPrice(highPrice decimal.Decimal, prices []decimal.Decimal) decimal.Decimal {
for _, price := range prices {
if price.GreaterThan(highPrice) {
highPrice = price
}
}
return highPrice
}
// 获取最低价
func (this *ConstructorContract) getMinPrices(lowPrice decimal.Decimal, prices []decimal.Decimal) []decimal.Decimal {
res := make([]decimal.Decimal, 0)
for _, price := range prices {
if price.LessThan(lowPrice) {
res = append(res, price)
}
}
return res
}
func (this *ConstructorContract) getMinPrice(lowPrice decimal.Decimal, prices []decimal.Decimal) decimal.Decimal {
for _, price := range prices {
if price.LessThan(lowPrice) {
lowPrice = price
}
}
return lowPrice
}

300
cmd/selfMarketSpot/aggregation.go

@ -0,0 +1,300 @@
package selfMarketSpot
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"math/rand"
"time"
"wss-pool/cmd/common"
"wss-pool/cmd/websocketservice"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/market"
"wss-pool/pkg/model/stock"
)
const (
maxRand float64 = 2000 //成交笔数 随机最大值
minRand float64 = 0.1
minLeastRand float64 = 1
DepthNum int = 80 //深度数据只要
DepthMaxNum int = 300 //深度数据只要
defaultMaxStep float64 = 0.003
defaultMinStep float64 = 0.000001
)
var (
TotalAmount decimal.Decimal // 当前总成交量
TotalCount decimal.Decimal //总成交笔数
oldAsks int64
oldBids int64
)
func ClearTotal() {
TotalAmount = decimal.NewFromFloat(0)
TotalCount = decimal.NewFromFloat(0)
}
func CreateDepth(old, closePrice decimal.Decimal) {
chStep0 := fmt.Sprintf("market-%s-depth-step0", SelfSymbol)
rand.New(rand.NewSource(time.Now().UnixNano()))
tick := market.CtDepthTick{
Mrid: rand.Int63n(99999999999) + int64(99999),
Id: common.TimeToNow(),
Ts: common.TimeToNow(),
}
//卖单,买单
tick.Asks, tick.Bids = DepthStep(closePrice, old)
//applogger.Info("asks ", tick.Asks, tick.Bids)
//成交记录
CreateTradeDetail(tick)
//升序
tick.Asks = insertionSort(tick.Asks)
//降序
tick.Bids = quickSort(tick.Bids)
res := market.SubscribeCtDepthResponse{
Channel: chStep0,
Timestamp: common.TimeToNow(),
Tick: &tick,
}
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: res.Channel,
Content: res,
Symbol: res.Channel})
OneDayContractDepth(res)
red.RedisClient.Publish(res.Channel, string(jsonMessage))
}
// 卖单 买单
func DepthStep(closePrice, old decimal.Decimal) ([][]decimal.Decimal, [][]decimal.Decimal) {
data := make([][]decimal.Decimal, 0)
datas := make([][]decimal.Decimal, 0)
if old.Equal(closePrice) {
return data, datas
}
amaxRand := float64(0)
bmaxRand := float64(0)
//跌
if old.GreaterThan(closePrice) {
diff := old.Sub(closePrice)
amaxRand, _ = diff.Div(old).Mul(decimal.NewFromFloat(maxRand)).Round(2).Float64()
bmaxRand = amaxRand / float64(3)
} else {
diff := closePrice.Sub(old)
bmaxRand, _ = diff.Div(old).Mul(decimal.NewFromFloat(maxRand)).Round(2).Float64()
amaxRand = bmaxRand / float64(3)
}
asksMap := make(map[string]int)
bidsMap := make(map[string]int)
minRands := minRand
if closePrice.LessThan(decimal.NewFromInt(int64(1))) {
minRands = minLeastRand
}
for i := 0; i < DepthNum; i++ {
item := make([]decimal.Decimal, 0)
price := calculateMaxMinContractPrice(closePrice, false)
if _, ok := asksMap[price.String()]; ok {
continue
}
item = append(item, price)
item = append(item, decimal.NewFromFloat(common.RandFloats(minRands, amaxRand)))
data = append(data, item)
asksMap[price.String()] = 1
if len(data) >= 20 {
break
}
}
for i := 0; i < DepthNum; i++ {
item := make([]decimal.Decimal, 0)
price := calculateMaxMinContractPrice(closePrice, true)
if _, ok := bidsMap[price.String()]; ok {
continue
}
item = append(item, price)
item = append(item, decimal.NewFromFloat(common.RandFloats(minRands, bmaxRand)))
datas = append(datas, item)
bidsMap[price.String()] = 1
if len(datas) >= 20 {
break
}
}
return data, datas
}
// 生成一天聚合深度
func OneDayContractDepth(item market.SubscribeCtDepthResponse) {
result := market.SubscribeCtDepthTempResponse{}
result.Tick = &market.CtDepthTick{}
title := fmt.Sprintf("market-%s-depth-step0", SelfSymbol)
rand.New(rand.NewSource(time.Now().UnixNano()))
result.Tick.Mrid = rand.Int63n(99999999999) + int64(99999)
result.Tick.Id = common.TimeToNow() * 1000
result.Tick.Ts = common.TimeToNow() * 1000
result.Tick.Asks = insertionSort(item.Tick.Asks)
result.Tick.Bids = quickSort(item.Tick.Bids)
result.Channel = fmt.Sprintf("market.%s.depth.step0", SelfSymbol)
resultJsons, _ := json.Marshal(result)
red.Set_Cache_Value(title, string(resultJsons))
}
// TODO: 生成 聚合行情/市场概要
func OneDayDetailMerged(param market.SubscribeCandlestickResponse) {
result := market.TickerWebsocketResponse{}
tick := &market.TickR{}
tick.Count = int(param.Tick.Count)
tick.High = param.Tick.High
tick.Open = param.Tick.Open
tick.Vol = param.Tick.Vol
tick.Close = param.Tick.Close
tick.Low = param.Tick.Low
tick.Amount = param.Tick.Amount
result.Tick = tick
result.Timestamp = common.TimeToNow()
result.Channel = fmt.Sprintf("market.%s.ticker", SelfSymbol)
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: result.Channel,
Content: result,
Symbol: result.Channel})
red.RedisClient.Publish(result.Channel, string(jsonMessage))
res := market.TickerWebsocketResponses{}
title := fmt.Sprintf("market-%s-detail-merged", SelfSymbol)
res.Tick = tick
res.Channel = title
jsonMessages, _ := json.Marshal(res)
if err := red.Set_Cache_Value(title, string(jsonMessages)); err != nil {
applogger.Error(title, err)
}
//applogger.Info("subscribeTicker data,ServersId:%v,Sender:%v,Content:%v-%v", result.Channel, result.Tick, result.Data)
//applogger.Info("OneDayContractDepth ", string(resultJson))
}
// 生成详情
func CreateTradeDetail(tick market.CtDepthTick) {
res := market.SubscribeTradeResponse{}
res.Tick = &market.TickTrade{}
res.Tick.Data = make([]market.Trade, 0)
TotalCount = decimal.NewFromInt(0)
//最近交易接口
tradeDetailAPI := make([]stock.MarketTrade, 0)
for key, v := range tick.Asks {
if key >= 1 {
break
}
item := market.Trade{
Amount: v[1],
Timestamp: common.TimeToNow() * 1000,
TradeId: common.TimeToNow() * 1000,
Price: v[0],
Direction: "sell",
}
res.Tick.Data = append(res.Tick.Data, item)
apiItem := stock.MarketTrade{
ID: common.TimeToNow() * 1000,
OrderNumber: item.Amount.String(),
DealPrice: item.Price.String(),
OrderTime: common.TimeToNow() * 1000,
TradeType: 2,
}
tradeDetailAPI = append(tradeDetailAPI, apiItem)
}
res.Channel = fmt.Sprintf("market.%s.trade.detail", SelfSymbol)
res.Timestamp = common.TimeToNow() * 1000
if len(res.Tick.Data) > 0 {
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: res.Channel,
Content: res,
Symbol: res.Channel})
//applogger.Info("CreateTradeDetail sell:", string(jsonMessage))
red.RedisClient.Publish(res.Channel, string(jsonMessage))
}
res = market.SubscribeTradeResponse{}
res.Tick = &market.TickTrade{}
res.Tick.Data = make([]market.Trade, 0)
res.Channel = fmt.Sprintf("market.%s.trade.detail", SelfSymbol)
for key, v := range tick.Bids {
if key >= 1 {
break
}
item := market.Trade{
Amount: v[1], //CHEN
Timestamp: common.TimeToNow() * 1000,
TradeId: common.TimeToNow() * 1000,
Price: v[0],
Direction: "buy",
}
//总成交量
TotalAmount = TotalAmount.Add(item.Amount)
res.Tick.Data = append(res.Tick.Data, item)
apiItem := stock.MarketTrade{
ID: common.TimeToNow() * 1000,
OrderNumber: item.Amount.String(),
DealPrice: item.Price.String(),
OrderTime: common.TimeToNow() * 1000,
TradeType: 1,
}
tradeDetailAPI = append(tradeDetailAPI, apiItem)
}
res.Timestamp = common.TimeToNow() * 1000
//总成交笔数
TotalCount = TotalCount.Add(decimal.NewFromInt(int64(len(tick.Bids))))
if len(res.Tick.Data) > 0 {
//fmt.Println(res.Tick.Data)
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: res.Channel,
Content: res,
Symbol: res.Channel})
//applogger.Info("CreateTradeDetail buy:", string(jsonMessage))
go func(channel string, jsonMessage []byte) {
time.Sleep(1 * time.Second)
red.RedisClient.Publish(channel, string(jsonMessage))
}(res.Channel, jsonMessage)
}
//缓存
title := fmt.Sprintf("market-%s-trade-detail", SelfSymbol)
var resultJson []byte
resultJson, _ = json.Marshal(tradeDetailAPI)
if err := red.Set_Cache_Value(title, string(resultJson)); err != nil {
applogger.Error(title, err)
}
}
func calculateMaxMinContractPrice(basePrice decimal.Decimal, isNegative bool) decimal.Decimal {
rand.New(rand.NewSource(time.Now().UnixNano()))
max := basePrice.Mul(decimal.NewFromFloat(rand.Float64()*defaultMinStep + rand.Float64()*(defaultMaxStep-defaultMinStep))).Round(digits)
// fmt.Println(max)
if isNegative {
return basePrice.Sub(max).Round(digits)
}
return basePrice.Add(max).Round(digits)
}
// 升序
func insertionSort(nums [][]decimal.Decimal) [][]decimal.Decimal {
n := len(nums)
for i := 0; i < n-1; i++ {
for j := 0; j < n-i-1; j++ {
if nums[j][0].GreaterThan(nums[j+1][0]) {
nums[j], nums[j+1] = nums[j+1], nums[j]
}
}
}
return nums
}
// 降序
func quickSort(nums [][]decimal.Decimal) [][]decimal.Decimal {
n := len(nums)
for i := 0; i < n-1; i++ {
minIdx := i
for j := i + 1; j < n; j++ {
if nums[j][0].GreaterThan(nums[minIdx][0]) {
minIdx = j
}
}
nums[i], nums[minIdx] = nums[minIdx], nums[i]
}
return nums
}

59
cmd/selfMarketSpot/model.go

@ -0,0 +1,59 @@
package selfMarketSpot
import (
"fmt"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"wss-pool/internal/data"
)
func GetNewPrice(contractCode string) []data.MongoTick {
filter := bson.M{"channel": fmt.Sprintf("market.%s.kline.1min", contractCode)}
tableName := data.GetStockKLineTableName("1min")
pagedData, _ := data.MgoLimitFind(tableName, filter, int64(1))
return pagedData
}
func GetNewPriceAll(contractCode, period string) []data.MongoTick {
filter := bson.M{"channel": fmt.Sprintf("market.%s.kline.%s", contractCode, period)}
tableName := data.GetStockKLineTableName(period)
pagedData, _ := data.MgoLimitFind(tableName, filter, int64(1))
return pagedData
}
func GetTimeNewPrice(contractCode string, from, to int64, period string) (decimal.Decimal, decimal.Decimal, decimal.Decimal, decimal.Decimal, decimal.Decimal) {
filter := bson.M{"channel": fmt.Sprintf("market.%s.kline.%s", contractCode, period), "code": bson.M{"$gte": from, "$lte": to}}
tableName := data.GetStockKLineTableName(period)
pagedData, _ := data.MgoLimitFind(tableName, filter, int64(0))
var low, high, vol, amount, count decimal.Decimal
for key, v := range pagedData {
lows, _ := decimal.NewFromString(v.Low)
highs, _ := decimal.NewFromString(v.High)
vols, _ := decimal.NewFromString(v.Vol)
amounts, _ := decimal.NewFromString(v.Amount)
var counts decimal.Decimal
if countItem, ok := v.Count.(string); ok {
counts, _ = decimal.NewFromString(countItem)
} else if countItem, ok := v.Count.(int64); ok {
counts = decimal.NewFromInt(countItem)
}
if key == 0 {
low = lows
high = highs
vol = vols
amount = amounts
count = counts
continue
}
vol = vol.Add(vols)
amount = vol.Add(amounts)
count = vol.Add(counts)
if low.GreaterThan(lows) {
low = lows
}
if high.LessThan(highs) {
high = highs
}
}
return low, high, vol, amount, count
}

698
cmd/selfMarketSpot/virtualContract.go

@ -0,0 +1,698 @@
package selfMarketSpot
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"math/rand"
"strings"
"sync"
"time"
"wss-pool/cmd/common"
"wss-pool/cmd/websocketservice"
"wss-pool/dictionary"
"wss-pool/internal/data/business"
"wss-pool/internal/model"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/market"
)
const (
proportion int32 = 10000 //调价原价比例
step int32 = 20 //调价指数
digits int32 = 4 //保留小数位数
numPrices int = 20 //生成随机价格数量
defaultStep float64 = 0.001 // 默认波动率
PushFrequency int = 3 //S
TimeRemaining int64 = 20 //s
)
var (
contractChan = make(chan string)
SelfSymbol string //现货
InitialPrice decimal.Decimal // 初始价格
KLineMap = make(map[string]KlineLowHigh)
//ClosePrices decimal.Decimal //当前价格 //做调价终止值使用
OldFiveMin int64 //记录K线时间搓
OldFifteenMin int64
OldThirtyMin int64
OldOneHour int64
OldFourHour int64
OldDay int64
OldWeek int64
OldMon int64
IsRun bool
MarketSpotMap = make(map[string]bool) //保存领取的任务
lock sync.Mutex
EndChan = make(chan string)
)
type KlineLowHigh struct {
Low decimal.Decimal
High decimal.Decimal
ID int64
Vol decimal.Decimal
Amount decimal.Decimal
Count decimal.Decimal
Open decimal.Decimal
}
type ConstructorContract struct {
SelfSymbol string `json:"selfSymbol"` //现货
BeginTime string `json:"beginTime"` //开始时间
EndTime string `json:"endTime"` //结束时间
MaxPrice decimal.Decimal `json:"maxPrices"`
MinPrice decimal.Decimal `json:"minPrice"`
}
func NewSelfMarketSpot() {
//首次启动
go func() {
this := new(ConstructorContract)
this.MinPrice = InitialPrice
this.SelfSymbol = SelfSymbol
this.defaultContract()
}()
for {
t := time.NewTimer(1 * time.Minute)
<-t.C
go func() {
if IsRun {
applogger.Info("已有调价在运行, 该次调价不能运行")
return
}
result := getData()
fmt.Println(result)
for _, v := range result {
v.TradeName = common.ToLower(v.TradeName)
if v.TradeName != SelfSymbol {
applogger.Info("parametric inequality")
continue
}
end, _ := common.TimeStrToTimes(v.EndTime)
if end.Unix() <= common.TimeToNow() {
applogger.Info("该调价已过期")
continue
}
if _, ok := MarketSpotMap[fmt.Sprintf("%s-%d", v.TradeName, end.Unix())]; ok {
applogger.Info("该任务正在运行", fmt.Sprintf("%s-%d", v.TradeName, end.Unix()))
continue
}
MarketSpotMap[fmt.Sprintf("%s-%d", v.TradeName, end.Unix())] = true //保存任务
begin, _ := common.TimeStrToTimes(v.BeginTime)
if begin.Unix() < common.TimeToNow() {
applogger.Info("该调价已过期")
continue
} else if end.Unix() <= begin.Unix() {
applogger.Info("begin end 有误")
continue
}
maxPrice, _ := decimal.NewFromString(v.MaxPrice)
//if maxPrice.LessThan(InitialPrice) {
// applogger.Info("调价有误",v.MaxPrice)
// continue
//}
//等待到开始时间
applogger.Info("等待到begin time ", v.BeginTime, v.EndTime)
IsRun = true // 调价任务已开启
time.Sleep(begin.Sub(common.TimeToNows()))
// startd := time.Now()
contractChan <- "constructorStart"
this := new(ConstructorContract)
this.SelfSymbol = SelfSymbol
this.EndTime = v.EndTime
this.MinPrice = InitialPrice
this.MaxPrice = maxPrice
this.BeginTime = v.BeginTime
// fmt.Println("断开 默认波动 时间 Run time: ", time.Since(startd))
this.constructor(begin, end)
//更改状态
fmt.Println("调价结束")
contract := model.NewContractMarket()
contract.ID = v.ID
contract.UpdateIsGetOne()
}
if IsRun && len(result) > 0 {
IsRun = false
applogger.Debug("开启新的默认携程")
this := new(ConstructorContract)
this.MinPrice = InitialPrice
this.SelfSymbol = SelfSymbol
this.defaultContract()
}
}()
}
}
func getData() []model.ContractMarket {
contract := model.NewContractMarket()
contract.IsType = model.Market
symbol := strings.Split(SelfSymbol, "usdt")
contract.TradeName = strings.ToUpper(symbol[0])
result := contract.List()
return result
}
// 规定启动
func (this *ConstructorContract) constructor(begin, end time.Time) {
applogger.Info("开始调价。。。", "结束时间:", this.EndTime, "当前价格:", this.MinPrice, "最终价格", this.MaxPrice)
totalDuration := end.Sub(common.TimeToNows())
closePrice := this.MinPrice
highPrice := this.MinPrice
lowPrice := this.MinPrice
oldPrice := this.MinPrice
var openPrice decimal.Decimal
//temporalFrequency := time.Duration(PushFrequency) * time.Second
timeInterval := float64(totalDuration) / float64(time.Minute)
delta := this.MaxPrice.Sub(closePrice).Div(decimal.NewFromFloat(timeInterval))
fluctuation := closePrice.Mul(decimal.NewFromInt32(step).Div(decimal.NewFromInt32(proportion))).Round(digits)
applogger.Info("timeInterval", timeInterval, "delta", delta, "fluctuation", fluctuation)
// 开始进行调价
for !this.MaxPrice.Equal(InitialPrice) || end.Unix() > common.TimeToNows().Unix() {
fmt.Println("当前价格", InitialPrice)
prices := this.generateRandomPrices(InitialPrice, fluctuation, delta)
openPrice = InitialPrice
this.GetAllLowHigh(highPrice, lowPrice)
nonVanishing(lowPrice)
numFrequency := int(60) / PushFrequency
ClearTotal()
for i := 1; i <= numFrequency; i++ {
//go func(prices []decimal.Decimal, closePrice, openPrice, highPrice, lowPrice, oldPrice decimal.Decimal, this *ConstructorContract) {
start := time.Now() // 获取当前时间
rand.New(rand.NewSource(time.Now().UnixNano()))
key := rand.Intn(len(prices))
closePrice = prices[key]
// applogger.Info("实际落盘价:", closePrice)
//if (closePrice.Sub(this.MaxPrice)).Mul(delta).GreaterThan(decimal.NewFromInt32(0)) {
// closePrice = this.MaxPrice
//}
//当只剩 20 秒
if (end.Unix() - common.TimeToNows().Unix()) < TimeRemaining {
closePrice = this.MaxPrice
}
////生成深度、Trade Detail 数据
CreateDepth(oldPrice, closePrice)
////k 线 详情
this.pullStorage(closePrice, openPrice, highPrice, lowPrice, oldPrice, prices)
applogger.Debug("目标价格", this.MaxPrice, "当前价格", closePrice, "调价结束时间", this.EndTime, "i", i, time.Now().Format("2006-01-02 15:04:05"))
oldPrice = closePrice
//更新市价
lock.Lock()
InitialPrice = closePrice
lock.Unlock()
if end.Unix() <= common.TimeToNows().Unix() {
break
}
fmt.Println("Run time: ", time.Since(start))
s := float64(PushFrequency) - time.Since(start).Seconds()
if s > float64(0) {
applogger.Debug("停留 秒", s)
time.Sleep(time.Duration(s) * time.Second)
}
}
}
}
func nonVanishing(low decimal.Decimal) {
for k, v := range KLineMap {
if v.Low.IsZero() {
v.Low = low
}
KLineMap[k] = v
}
}
func (this *ConstructorContract) defaultContract() {
closePrice := this.MinPrice
highPrice := this.MinPrice
lowPrice := this.MinPrice
oldPrice := this.MinPrice
var openPrice decimal.Decimal
//var temporalFrequency = time.Duration(PushFrequency) * time.Second
for {
select {
case _, ok := <-contractChan: // 从管道接收值
if ok {
applogger.Info("calculateContractPrice start,defaultContract")
return
}
default:
//fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
rand.New(rand.NewSource(time.Now().UnixNano()))
openPrice = InitialPrice
Loop:
prices := calculateContractPrice(InitialPrice)
fmt.Println("开盘价", InitialPrice)
// 更新落盘价、开盘价、最高价和最低价
key := rand.Intn(numPrices)
highPrices := this.getMaxPrices(openPrice, prices)
lowPrices := this.getMinPrices(openPrice, prices)
//被2整除 涨
if key%2 == 0 {
highPrice = this.getMaxPrice(openPrice, highPrices)
lowPrice = openPrice
prices = highPrices
} else {
highPrice = openPrice
lowPrice = this.getMinPrice(openPrice, lowPrices)
prices = lowPrices
}
if len(prices) <= 0 {
goto Loop
}
this.GetAllLowHigh(highPrice, lowPrice)
nonVanishing(lowPrice)
numFrequency := int(60) / PushFrequency
ClearTotal()
for i := 1; i <= numFrequency; i++ {
// start := time.Now() // 获取当前时间
// fmt.Println("Run time: ", time.Since(start))
go func(prices []decimal.Decimal, closePrice, openPrice, highPrice, lowPrice, oldPrice decimal.Decimal, this *ConstructorContract) {
////更新市价
//fmt.Println(closePrice)
rand.New(rand.NewSource(time.Now().UnixNano()))
key := rand.Intn(len(prices))
closePrice = prices[key]
lock.Lock()
InitialPrice = closePrice
lock.Unlock()
//// 生成深度、Trade Detail 数据
CreateDepth(oldPrice, closePrice)
//fmt.Println("Run time: ", time.Since(start))
this.pullStorage(closePrice, openPrice, highPrice, lowPrice, oldPrice, prices)
//fmt.Println("123123123 ", closePrice,openPrice, highPrice, lowPrice, oldPrice)
oldPrice = closePrice
}(prices, closePrice, openPrice, highPrice, lowPrice, oldPrice, this)
time.Sleep(time.Duration(int64(PushFrequency)) * time.Second)
}
}
}
}
func generateRandomStep(min, max decimal.Decimal) decimal.Decimal {
rand.New(rand.NewSource(time.Now().UnixNano()))
return min.Add(decimal.NewFromFloat(rand.Float64()).Mul(max.Sub(min)))
}
// 计算现货价格
func calculateContractPrice(basePrice decimal.Decimal) []decimal.Decimal {
prices := make([]decimal.Decimal, 0)
max := basePrice.Mul(decimal.NewFromFloat(defaultStep)).Round(digits)
min := max.Neg()
for i := 0; i < numPrices; i++ {
price := basePrice.Add(generateRandomStep(max, min)).Round(digits)
prices = append(prices, price)
}
return prices
}
func getOpen(timestamp int64, period string) decimal.Decimal {
tick := GetNewPriceAll(SelfSymbol, period)
open := InitialPrice
if len(tick) > 0 {
switch tick[0].Code {
case timestamp:
open, _ = decimal.NewFromString(tick[0].Open)
default:
open, _ = decimal.NewFromString(tick[0].Close)
}
}
return open
}
func (this *ConstructorContract) GetAllLowHigh(high, low decimal.Decimal) {
//初始化
if len(KLineMap) == 0 {
KLineMap["1min"] = KlineLowHigh{
ID: common.GenerateSingaporeMinuteTimestamp(),
Low: low,
High: high,
Vol: decimal.NewFromInt(0),
Amount: decimal.NewFromInt(0),
Count: decimal.NewFromInt(0),
Open: InitialPrice,
}
to := common.GenerateSingaporeFiveMinTimestamp()
from := to - int64(5*60)
low, high, vol, amount, count := GetTimeNewPrice(SelfSymbol, from, to, "1min")
KLineMap["5min"] = KlineLowHigh{
ID: to,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(to, "5min"),
}
OldFiveMin = to
to = common.GenerateSingaporeFifteenMinTimestamp()
from = to - int64(15*60)
low, high, vol, amount, count = GetTimeNewPrice(SelfSymbol, from, to, "5min")
KLineMap["15min"] = KlineLowHigh{
ID: to,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(to, "15min"),
}
OldFifteenMin = to
to = common.GenerateSingaporeThirtyMinTimestamp()
from = to - int64(30*60)
low, high, vol, amount, count = GetTimeNewPrice(SelfSymbol, from, to, "15min")
KLineMap["30min"] = KlineLowHigh{
ID: to,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(to, "30min"),
}
OldThirtyMin = to
from = common.GenerateSingaporeHourTimestamp()
to = from + int64(60*59)
low, high, vol, amount, count = GetTimeNewPrice(SelfSymbol, from, to, "30min")
KLineMap["60min"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(from, "60min"),
}
OldOneHour = from
to = common.GenerateSingaporeFourHourTimestamp()
from = to - (4 * 60 * 60)
low, high, vol, amount, count = GetTimeNewPrice(SelfSymbol, from, to, "60min")
KLineMap["4hour"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(from, "4hour"),
}
OldFourHour = from
from = common.GenerateSingaporeDayTimestamp("")
to = from + int64(60*60*24-1)
low, high, vol, amount, count = GetTimeNewPrice(SelfSymbol, from, to, "4hour")
KLineMap["1day"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(from, "1day"),
}
OldDay = from
from = common.GenerateSingaporeMonTimestamp()
to = common.TimeToNow()
low, high, vol, amount, count = GetTimeNewPrice(SelfSymbol, from, to, "1week")
KLineMap["1mon"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(from, "1mon"),
}
OldMon = from
from = common.GetWeekTimestamp()
to = common.TimeToNow()
low, high, vol, amount, count = GetTimeNewPrice(SelfSymbol, from, to, "1day")
KLineMap["1week"] = KlineLowHigh{
ID: from,
Low: low,
High: high,
Vol: vol,
Amount: amount,
Count: count,
Open: getOpen(from, "1week"),
}
OldWeek = from
}
for k, v := range KLineMap {
switch k {
case "1min":
v.ID = common.GenerateSingaporeMinuteTimestamp()
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.High = high
v.Low = low
v.Open = InitialPrice
case "5min":
v.ID = common.GenerateSingaporeFiveMinTimestamp()
if v.ID > OldFiveMin {
//新时间节点更新
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldFiveMin = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "15min":
v.ID = common.GenerateSingaporeFifteenMinTimestamp()
if v.ID > OldFifteenMin {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldFifteenMin = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "30min":
v.ID = common.GenerateSingaporeThirtyMinTimestamp()
if v.ID > OldThirtyMin {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldThirtyMin = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "60min":
v.ID = common.GenerateSingaporeHourTimestamp()
if v.ID > OldOneHour {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldOneHour = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "4hour":
v.ID = common.GenerateSingaporeFourHourTimestamp() - (4 * 60 * 60)
if v.ID > OldFourHour {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldFourHour = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "1day":
v.ID = common.GenerateSingaporeDayTimestamp("")
if v.ID > OldDay {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldDay = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "1week":
v.ID = common.GetWeekTimestamp()
if v.ID > OldWeek {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldWeek = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
case "1mon":
v.ID = common.GenerateSingaporeMonTimestamp()
if v.ID > OldMon {
v.Vol = decimal.NewFromFloat(0)
v.Amount = decimal.NewFromFloat(0)
v.Count = decimal.NewFromFloat(0)
v.Low = low
v.High = high
v.Open = getOpen(v.ID, k)
OldMon = v.ID
}
if v.Low.GreaterThan(low) {
v.Low = low
}
if v.High.LessThan(high) {
v.High = high
}
}
KLineMap[k] = v
}
}
func (this *ConstructorContract) pullStorage(close, open, high, low, oldPrice decimal.Decimal, prices []decimal.Decimal) {
for _, v := range dictionary.ContractPriceTime {
resp := market.SubscribeCandlestickResponse{
Channel: fmt.Sprintf("market.%s.kline.%s", this.SelfSymbol, v),
Timestamp: KLineMap[v].ID,
Tick: &market.Tick{
Open: KLineMap[v].Open,
High: KLineMap[v].High,
Low: KLineMap[v].Low,
Close: close,
Id: KLineMap[v].ID,
Count: int(TotalCount.Add(KLineMap[v].Count).IntPart()),
Amount: TotalAmount.Add(KLineMap[v].Amount),
},
}
if v == "1day" {
go OneDayDetailMerged(resp)
}
jsonMessage, _ := json.Marshal(websocketservice.Message{
ServersId: resp.Channel,
Content: resp,
Symbol: resp.Channel})
//applogger.Info("SubscribeKline %s:", string(jsonMessage), v)
red.RedisClient.Publish(resp.Channel, string(jsonMessage))
go business.UpdateWsMgo(resp)
}
//详情
detail := market.SubscribeCtDetailResponse{
Channel: fmt.Sprintf("market.%s.detail", this.SelfSymbol),
Timestamp: time.Now().Unix(),
Tick: &market.CtDetailTick{
Open: open,
High: high,
Low: low,
Close: close,
Id: time.Now().Unix(),
Count: TotalCount.IntPart(),
Amount: TotalAmount,
},
}
jsonMessages, _ := json.Marshal(websocketservice.Message{
ServersId: detail.Channel,
Content: detail,
Symbol: detail.Channel})
//applogger.Info("detail :", string(jsonMessages))
red.RedisClient.Publish(detail.Channel, string(jsonMessages))
}
// 生成随机价格波动
func (this *ConstructorContract) generateRandomPrices(currentPrice, fluctuation, delta decimal.Decimal) []decimal.Decimal {
prices := make([]decimal.Decimal, 0)
rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < numPrices; i++ {
price := currentPrice.Add(delta.Add(fluctuation.Mul(decimal.NewFromFloat(2*rand.Float64() - 1)))).Round(digits)
prices = append(prices, price)
}
return prices
}
// 获取最高价
func (this *ConstructorContract) getMaxPrices(highPrice decimal.Decimal, prices []decimal.Decimal) []decimal.Decimal {
res := make([]decimal.Decimal, 0)
for _, price := range prices {
if price.GreaterThan(highPrice) {
res = append(res, price)
}
}
return res
}
func (this *ConstructorContract) getMaxPrice(highPrice decimal.Decimal, prices []decimal.Decimal) decimal.Decimal {
for _, price := range prices {
if price.GreaterThan(highPrice) {
highPrice = price
}
}
return highPrice
}
// 获取最低价
func (this *ConstructorContract) getMinPrices(lowPrice decimal.Decimal, prices []decimal.Decimal) []decimal.Decimal {
res := make([]decimal.Decimal, 0)
for _, price := range prices {
if price.LessThan(lowPrice) {
res = append(res, price)
}
}
return res
}
func (this *ConstructorContract) getMinPrice(lowPrice decimal.Decimal, prices []decimal.Decimal) decimal.Decimal {
for _, price := range prices {
if price.LessThan(lowPrice) {
lowPrice = price
}
}
return lowPrice
}

13
cmd/servicemanager/currencyWss.go

@ -0,0 +1,13 @@
package servicemanager
import (
"wss-pool/cmd/websocketservice"
"wss-pool/config"
red "wss-pool/internal/redis"
)
// Currency TODO: 优化需要多个广播通道
func Currency(ipServer, addrServer string) {
red.RedisClient = red.RedisInit(config.Config.Redis.DbEleven)
websocketservice.Connect(ipServer, addrServer)
}

31
cmd/servicemanager/gather.go

@ -0,0 +1,31 @@
package servicemanager
import (
"fmt"
"github.com/gin-gonic/gin"
"wss-pool/cmd/marketwsscliert"
"wss-pool/config"
"wss-pool/internal/data"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
)
// Gather
func Gather(checkStr, ipServer, addrServer string) {
red.RedisClient = red.RedisInit(config.Config.Redis.DbEleven)
// Enable collaborative data collection
data.Mgo_init(config.Config.Mongodb)
//binance
//go bamarketwsscliert.RunBaDataRedis(checkStr)
go marketwsscliert.RunHBDataRedis(checkStr)
// Register Route
router := gin.Default()
addr := fmt.Sprintf("%v%v", ipServer, addrServer)
applogger.Info("intService---addr:%v", addr)
// Start Service
if err := router.Run(addr); err != nil {
applogger.Error("Failed to start Gin data collection service:%v", err)
}
}

68
cmd/servicemanager/gin.go

@ -0,0 +1,68 @@
package servicemanager
import (
"fmt"
"github.com/gin-gonic/gin"
"wss-pool/api"
"wss-pool/cmd/common"
"wss-pool/cmd/websocketservice"
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/internal/data/business"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
)
// GinServer
func GinServer(ipServer, addrServer, serverName, project string) {
data.Mgo_init(config.Config.Mongodb)
var server = &gin.Engine{}
if serverName != "gin" {
red.RedisInitMap(common.GetRedisDBMore(config.Config.Redis.DbMore))
// TODO: 插针(股票市场名称),目前p6,p7不要插针
go business.NewPinStock(common.GetRedisNoPin(config.Config.Redis.NoPinAss))
}
switch serverName {
case "gin": // 数字币市场
red.RedisClient = red.RedisInit(config.Config.Redis.DbTen)
data.InitMsqlDB(config.Config.Bourse)
server = api.RouterApiServer(project)
case "stockIndex": // 股票指数
server = api.RouterStockIndexApiServer()
case "indiaOption": // 印度期权市场
server = api.RouterIndiaOptionApiServer()
case "usStock": // 美股市场
server = api.RouterUSApiServer()
case "indonesiaStock": // 印尼市场
server = api.RouterIndonesiaApiServer()
case "thailandStock": // 泰国市场
server = api.RouterThailandApiServer()
case "indiaStock": // 印度市场
server = api.RouterIndiaApiServer()
case "malaysiaStock": // 马股市场
server = api.RouterMalaysiaApiServer()
case "singaporeStock": // 新加坡市场
server = api.RouterSingaporeApiServer()
case "hongkongStock": // 港股市场
server = api.RouterHongKongApiServer()
case "ukStock": // 英股市场
server = api.RouterUKApiServer()
case "franceStock": // 法股市场
server = api.RouterFranceApiServer()
case "germanyStock": // 德股市场
server = api.RouterGermanyApiServer()
case "brazilStock": // 巴西市场
server = api.RouterBrazilApiServer()
case "japanStock": // 日本市场
server = api.RouterJapanApiServer()
}
addr := fmt.Sprintf("%v%v", ipServer, addrServer)
applogger.Info("intService---addr:%v", addr)
// TODO: 缓存交易对数据
if project == common.CoinProject || config.Config.ServerLevel == "test" {
websocketservice.SubscriptionCache()
}
if err := server.Run(addr); err != nil {
applogger.Error("Failed to start Gin data collection service:%v", err)
}
}

38
cmd/servicemanager/selfContract.go

@ -0,0 +1,38 @@
package servicemanager
import (
"github.com/shopspring/decimal"
"os"
"wss-pool/cmd/selfContract"
"wss-pool/config"
"wss-pool/internal/data"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
)
// Gather
func SelfContract(checkStr, ipServer, addrServer, contractCode string) {
red.RedisClient = red.RedisInit(config.Config.Redis.DbEleven)
data.Mgo_init(config.Config.Mongodb)
data.InitGorm(config.Config.Bourse)
if contractCode == "" {
applogger.Error("Lack of contract code")
os.Exit(400)
}
//初始价格优先数据库
res := selfContract.GetNewPrice(contractCode)
if len(res) > 0 {
selfContract.InitialPrice = decimal.RequireFromString(res[0].Close)
}
if selfContract.InitialPrice.IsZero() {
selfContract.InitialPrice, _ = decimal.NewFromString(checkStr)
}
if selfContract.InitialPrice.IsZero() {
applogger.Error("Lack of initial price")
os.Exit(400)
}
selfContract.SelfContractCode = contractCode
applogger.Info("合约", selfContract.SelfContractCode, "初始价格", selfContract.InitialPrice)
// Enable collaborative data collection
selfContract.NewSelfContract()
}

38
cmd/servicemanager/selfMarketSpot.go

@ -0,0 +1,38 @@
package servicemanager
import (
"github.com/shopspring/decimal"
"os"
"wss-pool/cmd/selfMarketSpot"
"wss-pool/config"
"wss-pool/internal/data"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
)
// Gather
func SelfMarketSpot(checkStr, ipServer, addrServer, symbol string) {
red.RedisClient = red.RedisInit(config.Config.Redis.DbEleven)
data.Mgo_init(config.Config.Mongodb)
data.InitGorm(config.Config.Bourse)
if symbol == "" {
applogger.Error("Lack of symbol")
os.Exit(400)
}
//初始价格优先数据库
res := selfMarketSpot.GetNewPrice(symbol)
if len(res) > 0 {
selfMarketSpot.InitialPrice = decimal.RequireFromString(res[0].Close)
}
if selfMarketSpot.InitialPrice.IsZero() {
selfMarketSpot.InitialPrice, _ = decimal.NewFromString(checkStr)
}
if selfMarketSpot.InitialPrice.IsZero() {
applogger.Error("Lack of initial price")
os.Exit(400)
}
selfMarketSpot.SelfSymbol = symbol
applogger.Info("现货", selfMarketSpot.SelfSymbol, "初始价格", selfMarketSpot.InitialPrice)
// Enable collaborative data collection
selfMarketSpot.NewSelfMarketSpot()
}

79
cmd/servicemanager/shareWss.go

@ -0,0 +1,79 @@
package servicemanager
import (
"fmt"
"github.com/gin-gonic/gin"
"wss-pool/cmd/common"
"wss-pool/cmd/marketwsscliert"
"wss-pool/config"
"wss-pool/internal/data"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
)
// GatherUS
//
// @Description: 美股采集
// @param checkStr
// @param ipServer
// @param addrServer
func GatherUS(checkStr, ipServer, addrServer string) {
// 初始化相同项目mongodb和redis的ip对应关系
//data.MgoDbToRedisMap = common.GetMgoDbToRedisMap(config.Config.Mongodb.RedisToMongodb)
// 初始化所有项目MongoDB
//data.Mgo_initMap(config.Config.Mongodb, common.GetMongodbAddrList(config.Config.Mongodb.AddrList))
// 初始化所有项目Redis
red.RedisInitMapList(common.GetRedisAddrList(config.Config.Redis.AddrList))
// Enable collaborative data collection
go marketwsscliert.ShareMarketBak(checkStr)
// Register Route
router := gin.Default()
addr := fmt.Sprintf("%v%v", ipServer, addrServer)
applogger.Info("intService---addr:%v", addr)
// Start Service
if err := router.Run(addr); err != nil {
applogger.Error("Failed to start Gin data collection service:%v", err)
}
}
// GatherForex
//
// @Description: 外汇采集
// @param checkStr
// @param ipServer
// @param addrServer
func GatherForex(checkStr, ipServer, addrServer string) {
data.Mgo_init(config.Config.Mongodb)
// init Redis
red.RedisInitMapList(common.GetRedisAddrList(config.Config.Redis.AddrList))
// 外汇插针配置加载
go marketwsscliert.GetModifyForex()
// 外汇实时报价
go marketwsscliert.ForexMarketBak(checkStr)
// 外汇实时天报价
go marketwsscliert.ForexMarketDayBak(checkStr)
// 外汇买一卖一报价
go marketwsscliert.ForexMarketQuoteBak(checkStr)
// 外汇成交报价
go marketwsscliert.ForexMarketTradeBak(checkStr)
// 外汇成交报价存储
go marketwsscliert.ForexMarketTradeBak2(checkStr)
// 外汇成交报价清理
go marketwsscliert.ForexMarketClearTradeBak2(checkStr)
// 外汇交易对每天0点0分0秒更新数据
go marketwsscliert.ForexUpdateClosePrice()
// Register Route
router := gin.Default()
addr := fmt.Sprintf("%v%v", ipServer, addrServer)
applogger.Info("intService---addr:%v", addr)
// Start Service
if err := router.Run(addr); err != nil {
applogger.Error("Failed to start Gin data collection service:%v", err)
}
}

20
cmd/servicemanager/shareus.go

@ -0,0 +1,20 @@
package servicemanager
import (
"wss-pool/cmd/closingMarket"
"wss-pool/cmd/websocketservice"
"wss-pool/config"
red "wss-pool/internal/redis"
)
// shareWs
func ShareWss(ipServer, addrServer string) {
red.RedisClient = red.RedisInit(config.Config.Redis.DbUser)
websocketservice.ShareConnect(ipServer, addrServer)
}
// TODO: 针对插针
func PinWs(ipServer, addrServer string) {
red.RedisClient = red.RedisInit(config.Config.Redis.DbUser)
closingMarket.ShareConnect(ipServer, addrServer)
}

55
cmd/servicemanager/tickdb.go

@ -0,0 +1,55 @@
package servicemanager
import (
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/internal/data/business"
red "wss-pool/internal/redis"
)
// TickDB
func TickDB(checkStr, ipServer, addrServer, contractCode string) {
data.Mgo_init(config.Config.Mongodb)
switch checkStr {
case "spotKline": // 现货历史数据
business.TickUpdateSpotKline()
case "all": // 合约全部历史数据
business.TickUpdateContractKline(true)
case "allUs": // 美股全部历史数据
business.TickUpdateStockUs(true)
case "Us": // 实时数据
business.TickUpdateStockUs(false)
case "previousClose": // TODO: 更新美股上次比盘价
business.UpdateStockUS()
case "UsNewPrice":
business.UsNewPrice(contractCode)
case "usOpenPrice":
business.UpdateOpenPrice()
case "southAsiaStock": // TODO: 自动聚合东南亚国家数据
red.RedisClient = red.RedisInit(config.Config.Redis.DbTen)
business.TickSouthAsiaSpotKline(contractCode)
case "stockIndex": // TODO: 自动聚合指数
business.TickSpotIndexKline()
case "deleteSpot": // TODO: 自动数据清理:contractCode(true:清理数据,false:清理并优化多余数据)
business.DeleteSpot(contractCode)
case "stockCloseData": // TODO: 自动检测并推送插针数据
business.StockClosedData()
case "checkStock": // TODO: 自动机器人行情检查
business.CheckStock()
case "updateStockCode": // 更新股票code和交易所
business.UpdateStockCode()
case "updateStockUsCode": // 更新美股股票code和交易所
business.UpdateStockUsCode()
case "updateStockExchange": // 更新印度股票(Exchange字段)
business.UpdateStockExchange()
case "forexClosePrice": // TODO: 更新外汇闭盘价
business.ForexUpdateCode()
case "deleteForexTrade": // TODO: 清理外汇成交报价
business.DeleteForexTrade()
default:
//抓取美股
//business.InitStockList()
//查询美股交易所
//business.UpdateStockUSTape()
}
}

161
cmd/websocketcollect/cache/leveldb.go

@ -0,0 +1,161 @@
package cache
import (
"encoding/json"
"fmt"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/opt"
"go.mongodb.org/mongo-driver/bson"
"strings"
"wss-pool/config"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
"wss-pool/pkg/model/stock"
)
var db *leveldb.DB
// initDB
//
// @Description:
func InitDB(name string) {
o := &opt.Options{
Filter: filter.NewBloomFilter(10),
}
var tableNamePath string
switch config.Config.ServerLevel {
case "test":
tableNamePath = fmt.Sprintf("./cmd/websocketcollect/db/%v", name)
default:
tableNamePath = fmt.Sprintf("/home/ubuntu/wss-server/db/%v", name)
}
dba, err := leveldb.OpenFile(tableNamePath, o)
if err != nil {
applogger.Error("OpenFile err:%v", err)
return
}
db = dba
}
// WriteDB
//
// @Description:
// @param key
// @param value
// @return error
func WriteDB(key, value string) error {
batch := new(leveldb.Batch)
batch.Put([]byte(key), []byte(value))
if err := db.Write(batch, nil); err != nil {
fmt.Printf("Write err:%v\n", err)
return err
}
return nil
}
// ReadDB
//
// @Description:
// @param key
// @return []byte
func ReadDB(key string) []byte {
data, err := db.Get([]byte(key), nil)
if err != nil {
fmt.Printf("OpenFile err:%v\n", err)
return []byte{}
}
return data
}
// ReadListDB
//
// @Description:
// @return []string
func ReadListDB() []string {
var privateKey []string
iter := db.NewIterator(nil, nil)
for iter.Next() {
value := iter.Value()
privateKey = append(privateKey, string(value))
}
iter.Release()
if err := iter.Error(); err != nil {
fmt.Printf("iter.Error:%v\n", err)
return privateKey
}
return privateKey
}
// DeleteByKey
//
// @Description:
// @param key
// @return error
func DeleteByKey(key string) error {
if err := db.Delete([]byte(key), nil); err != nil {
return err
}
return nil
}
// InitShareUsCode
//
// @Description: 初始化美股股票代码列表
func InitShareUsCode() {
filter := bson.M{"Country": "US", "YesterdayClose": bson.M{"$ne": ""}}
projection := bson.M{"Code": 1}
for _, mongoClient := range data.MgoDbClientMap {
stockRes := make([]stock.StockPolygon, 0)
data.MgoPagingFindStructProjectionNew(mongoClient, data.StockList, filter, projection, 20000, 1, -1, &stockRes)
for _, v := range stockRes {
if err := WriteDB(v.Code, v.Code); err != nil {
applogger.Error("sendUsCodeNew err:%v", err)
continue
}
}
applogger.Info("写入美股股票代码列表:%v", ReadListDB())
applogger.Debug("写入美股股票代码列表总数:%v", len(ReadListDB()))
}
}
// InitForexCode
//
// @Description: 初始化外汇代码列表
func InitForexCode() {
// https://finnhub.io/api/v1/forex/symbol?exchange=oanda&token=cqt0409r01qvdch2gnpgcqt0409r01qvdch2gnq0
url := fmt.Sprintf("https://%vforex/symbol?exchange=oanda&token=%v", config.Config.FinnhubUs.FinnhubHost, config.Config.FinnhubUs.FinnhubKey)
applogger.Debug("url data info:%v", url)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("WriteShareUs err info:%v", err)
return
}
applogger.Debug("new add shareUs code info:%v", bodyStr)
var dataForex []model.ForexCodeList
if err = json.Unmarshal([]byte(bodyStr), &dataForex); err != nil {
applogger.Error("json.Unmarshal err info:%v", err)
return
}
for _, v := range dataForex {
code := strings.Replace(v.DisplaySymbol, "/", "", -1)
if err := WriteDB(code, code); err != nil {
applogger.Error("sendUsCodeNew err:%v", err)
continue
}
}
applogger.Info("写入外汇代码列表:%v", ReadListDB())
applogger.Debug("写入外汇代码列表总数:%v", len(ReadListDB()))
}

1
cmd/websocketcollect/db/forex/CURRENT

@ -0,0 +1 @@
MANIFEST-000003

1
cmd/websocketcollect/db/forex/CURRENT.bak

@ -0,0 +1 @@
MANIFEST-000000

0
cmd/websocketcollect/db/forex/LOCK

15
cmd/websocketcollect/db/forex/LOG

@ -0,0 +1,15 @@
=============== Oct 23, 2024 (CST) ===============
16:49:09.927634 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
16:49:09.929200 db@open opening
16:49:09.929718 version@stat F·[] S·0B[] Sc·[]
16:49:09.930859 db@janitor F·2 G·0
16:49:09.930859 db@open done T·1.6589ms
=============== Oct 23, 2024 (CST) ===============
16:50:25.834513 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
16:50:25.835027 version@stat F·[] S·0B[] Sc·[]
16:50:25.835027 db@open opening
16:50:25.835027 journal@recovery F·1
16:50:25.835565 journal@recovery recovering @1
16:50:25.836251 version@stat F·[] S·0B[] Sc·[]
16:50:25.839923 db@janitor F·2 G·0
16:50:25.839923 db@open done T·4.8966ms

BIN
cmd/websocketcollect/db/forex/MANIFEST-000003

Binary file not shown.

BIN
cmd/websocketcollect/db/us/000016.ldb

Binary file not shown.

BIN
cmd/websocketcollect/db/us/000033.ldb

Binary file not shown.

1
cmd/websocketcollect/db/us/CURRENT

@ -0,0 +1 @@
MANIFEST-000037

1
cmd/websocketcollect/db/us/CURRENT.bak

@ -0,0 +1 @@
MANIFEST-000035

0
cmd/websocketcollect/db/us/LOCK

161
cmd/websocketcollect/db/us/LOG

@ -0,0 +1,161 @@
=============== Oct 21, 2024 (CST) ===============
14:05:51.117469 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:05:51.125464 db@open opening
14:05:51.125464 version@stat F·[] S·0B[] Sc·[]
14:05:51.126502 db@janitor F·2 G·0
14:05:51.127014 db@open done T·1.0377ms
=============== Oct 21, 2024 (CST) ===============
14:18:26.670497 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:18:26.676241 version@stat F·[] S·0B[] Sc·[]
14:18:26.676241 db@open opening
14:18:26.676241 journal@recovery F·1
14:18:26.676241 journal@recovery recovering @1
14:18:26.677423 version@stat F·[] S·0B[] Sc·[]
14:18:26.693376 db@janitor F·2 G·0
14:18:26.693376 db@open done T·17.1353ms
=============== Oct 21, 2024 (CST) ===============
14:18:58.327918 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:18:58.333562 version@stat F·[] S·0B[] Sc·[]
14:18:58.333562 db@open opening
14:18:58.333562 journal@recovery F·1
14:18:58.334153 journal@recovery recovering @2
14:18:58.334656 version@stat F·[] S·0B[] Sc·[]
14:18:58.349518 db@janitor F·2 G·0
14:18:58.349518 db@open done T·15.9557ms
=============== Oct 21, 2024 (CST) ===============
14:19:32.429756 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:19:32.435404 version@stat F·[] S·0B[] Sc·[]
14:19:32.435404 db@open opening
14:19:32.435404 journal@recovery F·1
14:19:32.435404 journal@recovery recovering @4
14:19:32.436432 version@stat F·[] S·0B[] Sc·[]
14:19:32.459359 db@janitor F·2 G·0
14:19:32.459359 db@open done T·23.955ms
=============== Oct 21, 2024 (CST) ===============
14:20:20.289425 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:20:20.294887 version@stat F·[] S·0B[] Sc·[]
14:20:20.294887 db@open opening
14:20:20.294887 journal@recovery F·1
14:20:20.295433 journal@recovery recovering @6
14:20:20.296072 version@stat F·[] S·0B[] Sc·[]
14:20:20.310833 db@janitor F·2 G·0
14:20:20.310833 db@open done T·15.9465ms
=============== Oct 21, 2024 (CST) ===============
14:20:36.927623 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:20:36.934195 version@stat F·[] S·0B[] Sc·[]
14:20:36.934195 db@open opening
14:20:36.934281 journal@recovery F·1
14:20:36.934281 journal@recovery recovering @8
14:20:36.935338 version@stat F·[] S·0B[] Sc·[]
14:20:36.938053 db@janitor F·2 G·0
14:20:36.938053 db@open done T·3.8583ms
=============== Oct 21, 2024 (CST) ===============
14:22:23.986904 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:22:23.992574 version@stat F·[] S·0B[] Sc·[]
14:22:23.992574 db@open opening
14:22:23.992574 journal@recovery F·1
14:22:23.992574 journal@recovery recovering @10
14:22:23.993680 version@stat F·[] S·0B[] Sc·[]
14:22:24.008658 db@janitor F·2 G·0
14:22:24.008658 db@open done T·16.0838ms
=============== Oct 21, 2024 (CST) ===============
14:24:51.467782 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:24:51.473434 version@stat F·[] S·0B[] Sc·[]
14:24:51.473434 db@open opening
14:24:51.473434 journal@recovery F·1
14:24:51.473938 journal@recovery recovering @12
14:24:51.474495 version@stat F·[] S·0B[] Sc·[]
14:24:51.477297 db@janitor F·2 G·0
14:24:51.477297 db@open done T·3.8623ms
=============== Oct 21, 2024 (CST) ===============
14:46:39.100971 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:46:39.106984 version@stat F·[] S·0B[] Sc·[]
14:46:39.106984 db@open opening
14:46:39.106984 journal@recovery F·1
14:46:39.106984 journal@recovery recovering @14
14:46:39.115173 memdb@flush created L0@16 N·10706 S·117KiB "A,v10697":"ZZZ,v3592"
14:46:39.115693 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
14:46:39.118621 db@janitor F·3 G·0
14:46:39.118621 db@open done T·11.6369ms
=============== Oct 21, 2024 (CST) ===============
14:49:38.789393 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:49:38.795391 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
14:49:38.795391 db@open opening
14:49:38.795391 journal@recovery F·1
14:49:38.795981 journal@recovery recovering @17
14:49:38.797010 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
14:49:38.799205 db@janitor F·3 G·0
14:49:38.799205 db@open done T·3.8134ms
=============== Oct 22, 2024 (CST) ===============
13:43:35.290331 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:43:35.296999 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:43:35.296999 db@open opening
13:43:35.296999 journal@recovery F·1
13:43:35.296999 journal@recovery recovering @19
13:43:35.297504 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:43:35.312184 db@janitor F·3 G·0
13:43:35.312184 db@open done T·15.1845ms
=============== Oct 22, 2024 (CST) ===============
13:44:56.419488 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:44:56.425242 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:44:56.425242 db@open opening
13:44:56.425242 journal@recovery F·1
13:44:56.425242 journal@recovery recovering @21
13:44:56.426359 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:44:56.441861 db@janitor F·3 G·0
13:44:56.441861 db@open done T·16.6193ms
=============== Oct 22, 2024 (CST) ===============
13:46:42.392191 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:46:42.398184 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:46:42.398184 db@open opening
13:46:42.398184 journal@recovery F·1
13:46:42.398706 journal@recovery recovering @23
13:46:42.399245 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:46:42.402856 db@janitor F·3 G·0
13:46:42.402856 db@open done T·4.6718ms
=============== Oct 22, 2024 (CST) ===============
13:47:34.914821 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:47:34.920792 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:47:34.920792 db@open opening
13:47:34.920792 journal@recovery F·1
13:47:34.920792 journal@recovery recovering @25
13:47:34.921296 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:47:34.924466 db@janitor F·3 G·0
13:47:34.924466 db@open done T·3.6741ms
=============== Oct 22, 2024 (CST) ===============
13:48:23.970763 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:48:23.976619 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:48:23.976619 db@open opening
13:48:23.976619 journal@recovery F·1
13:48:23.977144 journal@recovery recovering @27
13:48:23.977671 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:48:23.979746 db@janitor F·3 G·0
13:48:23.979746 db@open done T·3.1275ms
=============== Oct 22, 2024 (CST) ===============
13:50:04.249322 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:50:04.255596 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:50:04.255596 db@open opening
13:50:04.255596 journal@recovery F·1
13:50:04.255596 journal@recovery recovering @29
13:50:04.256320 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:50:04.259469 db@janitor F·3 G·0
13:50:04.259469 db@open done T·3.8727ms
=============== Oct 22, 2024 (CST) ===============
13:56:29.194742 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:56:29.200860 version@stat F·[1] S·117KiB[117KiB] Sc·[0.25]
13:56:29.200860 db@open opening
13:56:29.200860 journal@recovery F·1
13:56:29.200860 journal@recovery recovering @31
13:56:29.202562 memdb@flush created L0@33 N·1 S·185B "JBKMM,v10708":"JBKMM,v10708"
13:56:29.203065 version@stat F·[2] S·117KiB[117KiB] Sc·[0.50]
13:56:29.204707 db@janitor F·4 G·0
13:56:29.204707 db@open done T·3.847ms
=============== Oct 22, 2024 (CST) ===============
13:58:29.143667 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
13:58:29.150187 version@stat F·[2] S·117KiB[117KiB] Sc·[0.50]
13:58:29.150187 db@open opening
13:58:29.150187 journal@recovery F·1
13:58:29.150187 journal@recovery recovering @34
13:58:29.150993 version@stat F·[2] S·117KiB[117KiB] Sc·[0.50]
13:58:29.165607 db@janitor F·4 G·0
13:58:29.165607 db@open done T·15.4202ms

BIN
cmd/websocketcollect/db/us/MANIFEST-000037

Binary file not shown.

36
cmd/websocketcollect/forex/route.go

@ -0,0 +1,36 @@
package forex
import (
"fmt"
"net/http"
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
)
// SubscribeForex
//
// @Description: 外汇行情采集分发
// @param ipServer
// @param addrServer
func SubscribeForex(ipServer, addrServer string) {
data.Mgo_init(config.Config.Mongodb)
codeList := GetMongodbForexCode()
applogger.Debug("foreList data:%v", codeList)
hub := newHub()
go hub.ForexMarketBBOSwitcher(codeList) // 订阅盘口(买一卖一)报价
go hub.ForexMarketTradeSwitcher(codeList) // 订阅成交报价
go hub.ForexMarketBatchPrice(codeList) // 订阅实时报价
go hub.ForexMarketBatchDayPrice(codeList) // 订阅每天报价-数据(实时-最高-最低-开盘价)
go hub.run()
http.HandleFunc("/forexWs", func(w http.ResponseWriter, r *http.Request) {
handleConnection(hub, w, r)
})
addr := fmt.Sprintf("%v%v", ipServer, addrServer)
applogger.Info("wss-pool server start at %v", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
applogger.Error("ListenAndServe:%v", err)
}
}

725
cmd/websocketcollect/forex/wss.go

@ -0,0 +1,725 @@
package forex
import (
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"log"
"net/http"
"strings"
"sync"
"time"
"wss-pool/config"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var topic = "forex" // 实时报价
var topic_d = "forexDay" // 实时天报价
var topic_t = "trade" // 成交报价
var topic_s = "tradeStorage" // 成交报价存储
var topic_c = "quotes" // 盘口(买一卖一)报价
var upGrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// Verification request`
CheckOrigin: func(r *http.Request) bool {
// If it is not a get request, return an error
if r.Method != "GET" {
fmt.Println("Request method error")
return false
}
// If chat is not included in the path, an error is returned
if r.URL.Path != "/forexWs" {
fmt.Println("Request path error")
return false
}
// Verification rules can also be customized according to other needs
return true
},
}
var forexLastKline = make(chan []model.ForexJsonData)
var forexDayLastKline = make(chan []model.ForexJsonData)
// 订阅数据类型
type Symbol struct {
Code string `json:"code"`
DepthLevel int `json:"depth_level"`
}
type Request struct {
CmdID int `json:"cmd_id"`
SeqID int `json:"seq_id"`
Trace string `json:"trace"`
Data DataList `json:"data"`
}
type DataList struct {
SymbolList []Symbol `json:"symbol_list"`
}
type ResultBbo struct {
CmdID int `json:"cmd_id"`
Data struct {
Code string `json:"code"`
Seq string `json:"seq"`
TickTime string `json:"tick_time"`
Bids []struct {
Price string `json:"price"`
Volume string `json:"volume"`
} `json:"bids"`
Asks []struct {
Price string `json:"price"`
Volume string `json:"volume"`
} `json:"asks"`
} `json:"data"`
}
type ResultsTrade struct {
CmdID int `json:"cmd_id"`
Data struct {
Code string `json:"code"`
Seq string `json:"seq"`
TickTime string `json:"tick_time"`
Price string `json:"price"`
Volume string `json:"volume"`
Turnover string `json:"turnover"`
TradeDirection int `json:"trade_direction"`
} `json:"data"`
}
// Client represents a WebSocket client.
type Client struct {
conn *websocket.Conn
subscriptions map[string]bool // Tracks which topics the client is subscribed to
}
// Hub maintains the set of active clients and broadcasts messages to the clients.
type Hub struct {
clients map[*Client]bool // All connected clients
broadcast chan Message // Broadcast channel for messages
topics map[string][]*Client // Topic to clients mapping for subscriptions
mu sync.Mutex // Mutex for safe concurrent access
}
// Message structure to hold the message content and the topic
type Message struct {
Topic string `json:"topic"`
Content string `json:"content"`
}
// newHub Initialize a new Hub
func newHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan Message),
topics: make(map[string][]*Client),
}
}
// run Start the Hub to listen for messages
func (h *Hub) run() {
for {
msg := <-h.broadcast
// Lock the hub to safely access the topics map
h.mu.Lock()
if clients, ok := h.topics[msg.Topic]; ok {
for _, client := range clients {
err := client.conn.WriteJSON(msg)
if err != nil {
log.Printf("Error writing message to client: %v", err)
client.conn.Close()
delete(h.clients, client)
}
}
}
h.mu.Unlock()
}
}
// Handle WebSocket connections
func handleConnection(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upGrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Error during connection upgrade:", err)
return
}
client := &Client{conn: conn, subscriptions: make(map[string]bool)}
hub.clients[client] = true
go func() {
defer func() {
delete(hub.clients, client)
for topic := range client.subscriptions {
hub.mu.Lock()
hub.topics[topic] = removeClientFromTopic(hub.topics[topic], client)
hub.mu.Unlock()
}
}()
for {
var msg Message
if err = conn.ReadJSON(&msg); err != nil {
log.Println("Error reading message:", err)
break
}
applogger.Info("message info:%v---%v", msg.Topic, msg.Content)
if msg.Content == "ping" || msg.Content == "subscribe" {
client.subscriptions[msg.Topic] = true
hub.mu.Lock()
hub.topics[msg.Topic] = append(hub.topics[msg.Topic], client)
hub.mu.Unlock()
}
switch msg.Content {
case "subscribe":
hub.broadcast <- Message{Topic: msg.Topic, Content: "subscribe success"}
case "ping":
hub.broadcast <- Message{Topic: msg.Topic, Content: "pong"}
default:
// TODO: 广播客户端订阅消息
//hub.broadcast <- msg
}
}
}()
}
// subscribeTradeSwitcher content new
func subscribeTradeSwitcher() *websocket.Conn {
url := "wss://quote.tradeswitcher.com/quote-b-ws-api?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
applogger.Info("subscribePolygon info Url:%v", url)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
applogger.Error("Failed to link to wss server:%v", err)
return nil
}
return conn
}
func (h *Hub) ForexMarketBBOSwitcher(codeList []string) {
var dataList DataList // 构造订阅参数
for _, value := range codeList {
dataList.SymbolList = append(dataList.SymbolList, Symbol{
Code: value,
DepthLevel: 1,
})
}
ticker := time.NewTicker(time.Second * 1)
defer ticker.Stop()
for {
select {
case <-ticker.C:
applogger.Info("Execute automatic subscription........")
if conn := subscribeTradeSwitcher(); conn != nil {
// Send heartbeat every 10 seconds
go func() {
for range time.NewTicker(10 * time.Second).C {
req := Request{
CmdID: 22000,
SeqID: 1447560092,
Trace: "b9e14618-b43f-4fb3-bf08-0bd1b60f285d",
Data: DataList{},
}
messageBytes, err := json.Marshal(req)
if err != nil {
applogger.Error("json.Marshal err:%v", err)
return
}
applogger.Debug("Message send info:%v", string(messageBytes))
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
applogger.Error("write heartbeat err:%v", err)
}
}
}()
// Construct subscription parameters
req := Request{
CmdID: 22002,
SeqID: 1447560092,
Trace: uuid.New().String(),
Data: dataList,
}
messageBytes, err := json.Marshal(req)
if err != nil {
applogger.Error("json.Marshal err:%v", err)
return
}
applogger.Debug("Message send info:%v", string(messageBytes))
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
applogger.Debug("write err:%v", err)
return
}
// 客户端broadcast,消息推送
h.SendAllClientBBOSwitcher(conn)
}
}
}
}
func (h *Hub) ForexMarketTradeSwitcher(codeList []string) {
var dataList DataList // 构造订阅参数
for _, value := range codeList {
dataList.SymbolList = append(dataList.SymbolList, Symbol{
Code: value,
DepthLevel: 1,
})
}
ticker := time.NewTicker(time.Second * 1)
defer ticker.Stop()
for {
select {
case <-ticker.C:
applogger.Info("Execute automatic subscription........")
if conn := subscribeTradeSwitcher(); conn != nil {
// Send heartbeat every 10 seconds
go func() {
for range time.NewTicker(10 * time.Second).C {
req := Request{
CmdID: 22000,
SeqID: 1148509458,
Trace: "a5e37d07-168f-403a-b4cb-08be813e0b91",
Data: DataList{},
}
messageBytes, err := json.Marshal(req)
if err != nil {
applogger.Error("json.Marshal err:%v", err)
return
}
applogger.Debug("Message send info:%v", string(messageBytes))
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
applogger.Error("heartbeat write err:%v", err)
}
}
}()
// Construct subscription parameters
req := Request{
CmdID: 22004,
SeqID: 1148509458,
Trace: uuid.New().String(),
Data: dataList,
}
messageBytes, err := json.Marshal(req)
if err != nil {
applogger.Error("json.Marshal err:%v", err)
return
}
applogger.Debug("Message send info:%v", string(messageBytes))
err = conn.WriteMessage(websocket.TextMessage, messageBytes)
if err != nil {
applogger.Debug("write err:%v", err)
return
}
// 客户端broadcast,消息推送
h.SendAllClientTradeSwitcher(conn)
}
}
}
}
func (h *Hub) ForexMarketBatchPrice(codeList []string) {
var dataList model.ConstructParametersPost
for _, value := range codeList {
dataList.Trace = uuid.New().String()
dataList.Data.DataList = append(dataList.Data.DataList, model.DataParameters{
Code: value,
KlineType: 1,
KlineTimestampEnd: 0,
QueryKlineNum: 1,
AdjustType: 0,
})
}
go func() {
for {
UrlBatchKline := "https://quote.tradeswitcher.com/quote-b-api/batch-kline?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
queryStr, err := json.Marshal(&dataList)
if err != nil {
return
}
bodyStr, err := internal.HttpPost(UrlBatchKline, string(queryStr))
if err != nil {
return
}
//applogger.Debug("klineBatchPrice data:%v", bodyStr)
var klineNew model.KlinePostReturnStruct
if err = json.Unmarshal([]byte(bodyStr), &klineNew); err != nil {
time.Sleep(3 * time.Second)
continue
}
//applogger.Debug("klineNew count:%v", len(klineNew.Data.KlineList))
// 将数据写入缓存用来推送K线
for _, value := range klineNew.Data.KlineList {
if len(value.KlineData) == 0 {
continue
}
code := model.Check_Code[value.Code]
if len(code) == 0 {
code = value.Code
}
openPrice := decimal.RequireFromString(value.KlineData[0].OpenPrice).InexactFloat64()
closePrice := decimal.RequireFromString(value.KlineData[0].ClosePrice).InexactFloat64()
highPrice := decimal.RequireFromString(value.KlineData[0].HighPrice).InexactFloat64()
lowPrice := decimal.RequireFromString(value.KlineData[0].LowPrice).InexactFloat64()
volume := decimal.RequireFromString(value.KlineData[0].Volume).InexactFloat64()
timestamp := decimal.RequireFromString(value.KlineData[0].Timestamp).IntPart()
forexJson := []model.ForexJsonData{
{
Event: "CAS",
Pair: code,
Open: openPrice,
Close: closePrice,
High: highPrice,
Low: lowPrice,
Volume: int(volume),
Timestamp: timestamp,
},
}
forexLastKline <- forexJson
}
time.Sleep(200 * time.Millisecond)
}
}()
h.ForexMarketBatchPriceBatch()
}
func (h *Hub) ForexMarketBatchDayPrice(codeList []string) {
var dataList model.ConstructParametersPost
for _, value := range codeList {
dataList.Trace = uuid.New().String()
dataList.Data.DataList = append(dataList.Data.DataList, model.DataParameters{
Code: value,
KlineType: 8,
KlineTimestampEnd: 0,
QueryKlineNum: 1,
AdjustType: 0,
})
}
go func() {
for {
UrlBatchKline := "https://quote.tradeswitcher.com/quote-b-api/batch-kline?token=bf8f33c446c4494286eccaa57a2e6fac-c-app"
queryStr, err := json.Marshal(&dataList)
if err != nil {
return
}
bodyStr, err := internal.HttpPost(UrlBatchKline, string(queryStr))
if err != nil {
return
}
//applogger.Debug("klineBatchPrice data:%v", bodyStr)
var klineNew model.KlinePostReturnStruct
if err = json.Unmarshal([]byte(bodyStr), &klineNew); err != nil {
time.Sleep(3 * time.Second)
continue
}
//applogger.Debug("klineNew count:%v", len(klineNew.Data.KlineList))
// 将数据写入缓存用来推送K线
for _, value := range klineNew.Data.KlineList {
if len(value.KlineData) == 0 {
continue
}
code := model.Check_Code[value.Code]
if len(code) == 0 {
code = value.Code
}
openPrice := decimal.RequireFromString(value.KlineData[0].OpenPrice).InexactFloat64()
closePrice := decimal.RequireFromString(value.KlineData[0].ClosePrice).InexactFloat64()
highPrice := decimal.RequireFromString(value.KlineData[0].HighPrice).InexactFloat64()
lowPrice := decimal.RequireFromString(value.KlineData[0].LowPrice).InexactFloat64()
volume := decimal.RequireFromString(value.KlineData[0].Volume).InexactFloat64()
timestamp := decimal.RequireFromString(value.KlineData[0].Timestamp).IntPart()
forexJson := []model.ForexJsonData{
{
Event: "CAS-D",
Pair: code,
Open: openPrice,
Close: closePrice,
High: highPrice,
Low: lowPrice,
Volume: int(volume),
Timestamp: timestamp,
},
}
forexDayLastKline <- forexJson
}
time.Sleep(200 * time.Millisecond)
}
}()
h.ForexMarketBatchDayPriceBatch()
}
// SendAllClientTradeSwitcher send message to all clients
func (h *Hub) SendAllClientBBOSwitcher(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
applogger.Error("IsUnexpectedCloseError err:%v", err)
}
break
}
var messageToJson ResultBbo
if err = json.Unmarshal([]byte(string(msg)), &messageToJson); err != nil {
applogger.Error("SendAllClientTradeSwitcher err:%v", err)
time.Sleep(5 * time.Second)
continue
}
switch messageToJson.CmdID {
case 22001: // 心跳
// {"ret":200,"msg":"ok","cmd_id":22001,"seq_id":123,"trace":"3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462"}
applogger.Debug("Heartbeat results:%v", string(msg))
case 22999: // 处理订阅数据
// {"cmd_id":22999,"data":{"code":"EURUSD","seq":"114101723","tick_time":"1732168153591","bids":[{"price":"1.05478","volume":"100000.00"}],"asks":[{"price":"1.05479","volume":"100000.00"}]}}
code := model.Check_Code[messageToJson.Data.Code]
if len(code) == 0 {
code = messageToJson.Data.Code
}
tickTime := decimal.RequireFromString(messageToJson.Data.TickTime).IntPart()
bids := decimal.RequireFromString(messageToJson.Data.Bids[0].Price).InexactFloat64()
asks := decimal.RequireFromString(messageToJson.Data.Asks[0].Price).InexactFloat64()
volume := decimal.RequireFromString(messageToJson.Data.Asks[0].Volume).IntPart()
quotesMsg := &[]model.ForexLastQuote{
{
Ev: "C",
P: code,
T: tickTime,
B: bids,
A: asks,
X: int(volume),
},
}
msgStr, err := json.Marshal(quotesMsg)
if err != nil {
applogger.Error("json.Marshal err:%v", err)
time.Sleep(5 * time.Second)
continue
}
applogger.Info("Message result:%v", string(msgStr))
// 广播数据
h.broadcast <- Message{Topic: topic_c, Content: string(msgStr)}
default:
applogger.Debug("Message result:%v", string(msg))
}
}
}
func (h *Hub) SendAllClientTradeSwitcher(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
applogger.Error("IsUnexpectedCloseError err:%v", err)
}
break
}
var messageToJson ResultsTrade
if err = json.Unmarshal(msg, &messageToJson); err != nil {
applogger.Error("json.Unmarshal err:%v", err)
continue
}
switch messageToJson.CmdID {
case 22001: // 处理心跳
// {"ret":200,"msg":"ok","cmd_id":22001,"seq_id":123456,"trace":"3380a7a-3e1f-c3a5-5ee3-9e5be0ec8c241692805462787878"}
applogger.Debug("Heartbeat results:%v", string(msg))
case 22998: // 处理订阅数据
// {"cmd_id":22998,"data":{"code":"XAUUSD","seq":"65087341","tick_time":"1732267727182","price":"2694.84","volume":"95.00","turnover":"0.00","trade_direction":0}}
code := model.Check_Code[messageToJson.Data.Code]
if len(code) == 0 {
code = messageToJson.Data.Code
}
tradeMsg := &[]model.ForexTrade{
{
Ev: "T",
Code: code,
Seq: messageToJson.Data.Seq,
TickTime: messageToJson.Data.TickTime,
Price: messageToJson.Data.Price,
Volume: messageToJson.Data.Volume,
Turnover: messageToJson.Data.Turnover,
TradeDirection: messageToJson.Data.TradeDirection,
},
}
msgStr, err := json.Marshal(tradeMsg)
if err != nil {
applogger.Error("json.Marshal err:%v", err)
time.Sleep(5 * time.Second)
continue
}
applogger.Info("Message result:%v", string(msgStr))
// 广播数据
h.broadcast <- Message{
Topic: topic_t,
Content: string(msgStr),
}
h.broadcast <- Message{
Topic: topic_s,
Content: string(msgStr),
}
default:
applogger.Debug("Message result:%v", string(msg))
}
}
}
func (h *Hub) ForexMarketBatchPriceBatch() {
for dataValue := range forexLastKline {
applogger.Info("Message result:%v", dataValue)
message, err := json.Marshal(&dataValue)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
// 广播数据
h.broadcast <- Message{Topic: topic, Content: string(message)}
}
}
func (h *Hub) ForexMarketBatchDayPriceBatch() {
for dataValue := range forexDayLastKline {
applogger.Info("Message result:%v", dataValue)
message, err := json.Marshal(&dataValue)
if err != nil {
time.Sleep(5 * time.Second)
continue
}
// 广播数据
h.broadcast <- Message{Topic: topic_d, Content: string(message)}
}
}
// ForexMarket content
func (h *Hub) ForexMarket() {
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
applogger.Info("Execute automatic subscription........")
if conn := subscribePolygon(); conn != nil {
h.SendAllClientNew(conn) // 客户端broadcast,消息推送
}
}
}
}
// subscribePolygon Link to third-party services
func subscribePolygon() *websocket.Conn {
url := fmt.Sprintf("wss://%v/forex", config.Config.ShareGather.PolygonWss)
applogger.Info("subscribePolygon info Url:%v", url)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
applogger.Error("Failed to link to wss server:%v", err)
return nil
}
return conn
}
// SendAllClientNew send message to all clients
func (h *Hub) SendAllClientNew(conn *websocket.Conn) {
for {
_, msg, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
applogger.Error("IsUnexpectedCloseError error: %v", err)
}
break
}
applogger.Debug("ReadMessage data info:%v", string(msg))
message := string(msg)
if strings.Contains(message, "Connected Successfully") {
// 鉴权
// {"action":"auth","params":"vG4tCD5emAFPkS4kWtXxJntMASyN4dnv"}
keyStr := model.SendAuthority{
Action: "auth",
Params: config.Config.ShareGather.PolygonKey,
}
authStr, err := JsonMarshal(keyStr)
if err != nil {
break
}
if err = Send(conn, string(authStr)); err != nil {
break
}
} else if strings.Contains(message, "authenticated") {
// 发起实时价格订阅
// {"action":"subscribe", "params":"CAS.*"}
subStr := model.SendAuthority{Action: "subscribe", Params: "CAS.*"}
resultStr, err := JsonMarshal(subStr)
if err != nil {
break
}
if err = Send(conn, string(resultStr)); err != nil {
break
}
// 发起报价(卖一|买一)订阅
// {"action":"subscribe", "params":"C.*"}
subStrC := model.SendAuthority{Action: "subscribe", Params: "C.*"}
resultStrC, err := JsonMarshal(subStrC)
if err != nil {
break
}
if err = Send(conn, string(resultStrC)); err != nil {
break
}
} else {
// 广播数据
//[{"ev":"CAS","sym":"C.C:EUR-USD","i":"50578","x":4,"p":215.9721,"s":100,"t":1611082428813,"z":3}]
//[{"ev":"C","p":"PLN/NOK","i":0,"a":2.7225,"b":2.7214,"x":48,"t":1730194133000}]
var topicTop string
if strings.Contains(message, "\"ev\":\"CAS\"") {
topicTop = topic
} else if strings.Contains(message, "\"ev\":\"C\"") {
topicTop = topic_c
}
h.broadcast <- Message{Topic: topicTop, Content: message}
}
}
}
// Send Subscribe send message
func Send(conn *websocket.Conn, data string) error {
if conn == nil {
applogger.Error("WebSocket sent error: no connection available")
return errors.New("WebSocket sent error: no connection available")
}
if err := conn.WriteMessage(websocket.TextMessage, []byte(data)); err != nil {
applogger.Error("WebSocket sent error: data=%s, error=%s", data, err)
return err
}
return nil
}
// JsonMarshal marshal json
func JsonMarshal(v interface{}) ([]byte, error) {
str, err := json.Marshal(v)
if err != nil {
applogger.Error("json.Marshal error: %v", err)
return []byte{}, err
}
return str, nil
}
// removeClientFromTopic Remove a client from a topic
func removeClientFromTopic(clients []*Client, c *Client) []*Client {
for i, client := range clients {
if client == c {
return append(clients[:i], clients[i+1:]...)
}
}
return clients
}
// GetMongodbForexCode select forex code
func GetMongodbForexCode() []string {
dateList, err := data.MgoFind(data.ForexListBak, bson.M{})
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return []string{}
}
//applogger.Info("MgoFind info len: %v", dateList)
var dateListStr []string
for _, value := range dateList.([]primitive.M) {
code := value["code"].(string)
dateListStr = append(dateListStr, code)
}
return dateListStr
}

75
cmd/websocketcollect/us/route.go

@ -0,0 +1,75 @@
package us
import (
"encoding/json"
"fmt"
"net/http"
"wss-pool/cmd/websocketcollect/cache"
"wss-pool/logging/applogger"
)
// SubscribeShareUs
//
// @Description: 广播美股实时行情/新增code
// @param addr
func SubscribeShareUs(ipServer, addrServer string) {
// 初始化相同项目mongodb和redis的ip对应关系
//data.MgoDbToRedisMap = common.GetMgoDbToRedisMap(config.Config.Mongodb.RedisToMongodb)
// 初始化所有项目MongoDB
//data.Mgo_initMap(config.Config.Mongodb, common.GetMongodbAddrList(config.Config.Mongodb.AddrList))
// 初始化美股
//InitShareUsCode()
cache.InitDB("us")
hub := newHub()
go hub.ShareMarket()
go hub.run()
http.HandleFunc("/usWss/add/code", serveHome)
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
handleConnection(hub, w, r)
})
addr := fmt.Sprintf("%v%v", ipServer, addrServer)
applogger.Info("wss-pool server start at %v", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
applogger.Error("ListenAndServe:%v", err)
}
}
// serveHome
//
// @Description: 新增美股code
// @param w
// @param r
func serveHome(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/usWss/add/code" {
http.Error(w, "Not found", http.StatusNotFound)
return
}
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
value := r.FormValue("code")
if value == "" {
http.Error(w, "code is empty", http.StatusBadRequest)
return
}
applogger.Info("subscribe share us code:%v", value)
if err := cache.WriteDB(value, value); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 确保返回 JSON 内容类型
w.Header().Set("Content-Type", "application/json")
// 设置状态码
w.WriteHeader(http.StatusOK)
// 将响应编码为 JSON 并写入响应体
if err := json.NewEncoder(w).Encode(Message{
Topic: value,
Content: "新增美股code成功!",
}); err != nil {
http.Error(w, "Failed to encode JSON", http.StatusInternalServerError)
}
}

241
cmd/websocketcollect/us/wss.go

@ -0,0 +1,241 @@
package us
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/gorilla/websocket"
"log"
"net/http"
"sync"
"time"
"wss-pool/cmd/websocketcollect/cache"
"wss-pool/config"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var (
UsNewCode = make(map[string]bool)
ctx, cancel = context.WithCancel(context.Background())
wg sync.WaitGroup
)
var upGrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// Verification request`
CheckOrigin: func(r *http.Request) bool {
// If it is not a get request, return an error
if r.Method != "GET" {
fmt.Println("Request method error")
return false
}
// If chat is not included in the path, an error is returned
if r.URL.Path != "/ws" {
fmt.Println("Request path error")
return false
}
// Verification rules can also be customized according to other needs
return true
},
}
// Client represents a WebSocket client.
type Client struct {
conn *websocket.Conn
subscriptions map[string]bool // Tracks which topics the client is subscribed to
}
// Hub maintains the set of active clients and broadcasts messages to the clients.
type Hub struct {
clients map[*Client]bool // All connected clients
broadcast chan Message // Broadcast channel for messages
topics map[string][]*Client // Topic to clients mapping for subscriptions
mu sync.Mutex // Mutex for safe concurrent access
}
// Message structure to hold the message content and the topic
type Message struct {
Topic string `json:"topic"`
Content string `json:"content"`
}
// Initialize a new Hub
func newHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan Message),
topics: make(map[string][]*Client),
}
}
// Start the Hub to listen for messages
func (h *Hub) run() {
for {
msg := <-h.broadcast
// Lock the hub to safely access the topics map
h.mu.Lock()
if clients, ok := h.topics[msg.Topic]; ok {
for _, client := range clients {
err := client.conn.WriteJSON(msg)
if err != nil {
log.Printf("Error writing message to client: %v", err)
client.conn.Close()
delete(h.clients, client)
}
}
}
h.mu.Unlock()
}
}
// Handle WebSocket connections
func handleConnection(hub *Hub, w http.ResponseWriter, r *http.Request) {
conn, err := upGrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Error during connection upgrade:", err)
return
}
client := &Client{conn: conn, subscriptions: make(map[string]bool)}
hub.clients[client] = true
go func() {
defer func() {
delete(hub.clients, client)
for topic := range client.subscriptions {
hub.mu.Lock()
hub.topics[topic] = removeClientFromTopic(hub.topics[topic], client)
hub.mu.Unlock()
}
}()
for {
var msg Message
if err = conn.ReadJSON(&msg); err != nil {
log.Println("Error reading message:", err)
break
}
applogger.Info("message info:%v---%v", msg.Topic, msg.Content)
if msg.Content == "ping" || msg.Content == "subscribe" {
client.subscriptions[msg.Topic] = true
hub.mu.Lock()
hub.topics[msg.Topic] = append(hub.topics[msg.Topic], client)
hub.mu.Unlock()
}
switch msg.Content {
case "subscribe":
hub.broadcast <- Message{Topic: msg.Topic, Content: "subscribe success"}
case "ping":
hub.broadcast <- Message{Topic: msg.Topic, Content: "pong"}
default:
// TODO: 广播客户端订阅消息
//hub.broadcast <- msg
}
}
}()
}
// ShareMarket content
func (h *Hub) ShareMarket() {
ticker := time.NewTicker(time.Second * 10)
defer ticker.Stop()
for {
select {
case <-ticker.C:
applogger.Info("Execute automatic subscription........")
if conn := subscribeFinnHub(); conn != nil {
// 客户端broadcast,消息推送
if err := h.SendAllClientNew(conn); err != nil {
break
}
}
}
}
}
func (h *Hub) SendAllClientNew(conn *websocket.Conn) error {
sendUsCodeNew(conn, false) // 初始化所有项目股票code订阅
go singleSendUsCodeNew(conn) // 单线程 订阅后续新增的股票
for {
_, msg, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
applogger.Error("IsUnexpectedCloseError error: %v", err)
}
return errors.New("Error reading message")
}
applogger.Debug("ReadMessage data info:%v", string(msg))
var subModel model.FinnhubMessage
if err := json.Unmarshal(msg, &subModel); err != nil {
applogger.Error("subModel Unmarshal info err: %v", err)
continue
}
// send to websocket
h.broadcast <- Message{Topic: "us", Content: string(msg)}
}
}
// Link to third-party services
func subscribeFinnHub() *websocket.Conn {
url := fmt.Sprintf("wss://%v?token=%v", config.Config.FinnhubUs.FinnhubWss, config.Config.FinnhubUs.FinnhubKey)
applogger.Info("subscribeFinnHub info Url:%v", url)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
applogger.Error("Failed to link to wss server:%v", err)
return nil
}
return conn
}
func singleSendUsCodeNew(conn *websocket.Conn) {
for {
time.Sleep(10 * time.Minute)
//发送测试数据检查该连接是否有效
//if err := Send(conn, fmt.Sprintf("{\"type\":\"subscribe\",\"symbol\":\"%v\"}", "testcode.ping")); err != nil {
// applogger.Error("ws:%v", err)
// return
//}
applogger.Debug("send new us stock start")
sendUsCodeNew(conn, true)
}
}
func sendUsCodeNew(conn *websocket.Conn, isSingle bool) {
stockRes := cache.ReadListDB()
for _, code := range stockRes {
sendPing := fmt.Sprintf("{\"type\":\"subscribe\",\"symbol\":\"%v\"}", code)
if !isSingle { //订阅全部美股
fmt.Println("subscribe:", code)
Send(conn, sendPing)
UsNewCode[code] = true
continue
}
if !UsNewCode[code] { //追加订阅新股票
applogger.Debug("new us stock:%v", sendPing)
Send(conn, sendPing)
UsNewCode[code] = true
}
}
}
func Send(conn *websocket.Conn, data string) error {
if conn == nil {
applogger.Error("WebSocket sent error: no connection available")
return errors.New("WebSocket sent error: no connection available")
}
if err := conn.WriteMessage(websocket.TextMessage, []byte(data)); err != nil {
applogger.Error("WebSocket sent error: data=%s, error=%s", data, err)
return err
}
return nil
}
// Remove a client from a topic
func removeClientFromTopic(clients []*Client, c *Client) []*Client {
for i, client := range clients {
if client == c {
return append(clients[:i], clients[i+1:]...)
}
}
return clients
}

540
cmd/websocketservice/websocketclient.go

@ -0,0 +1,540 @@
package websocketservice
import (
"encoding/json"
"errors"
"fmt"
"github.com/gorilla/websocket"
"github.com/satori/go.uuid"
"go.uber.org/zap"
"log"
"net/http"
"strconv"
"strings"
"sync"
"time"
"wss-pool/config"
"wss-pool/internal"
"wss-pool/internal/gzip"
"wss-pool/internal/model"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/market"
"wss-pool/pkg/model/stock"
)
const (
// Time allowed to write a message to the peer.
writeWait = 10 * time.Second
// Time allowed to read the next pong message from the peer.
pongWait = 60 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
SpotsStatus int = 1
ContractStatus int = 2
RedisCONTRACT string = "CONTRACT:LIST"
RedisDIGITAL string = "DIGITAL:LIST"
StatusPass int = 1
)
// Define a websocket connection object that contains information for each connection
type User struct {
Id string // Client ID
conn *websocket.Conn // Define websocket link objects
msg chan []byte // Define messages received and distributed
symbol sync.Map // Define a collection of user subscription types
stop chan string
start chan string
mux sync.Mutex
}
var (
wsConMap = map[string][]*websocket.Conn{}
mutex = sync.RWMutex{}
msgChan = make(chan []byte)
mutexSpotConn = sync.RWMutex{}
)
// Define the message body for the output
type Message struct {
ServersId string `json:"serversId,omitempty"` // Service ID (used to receive the client push ID of the third-party push server)
Sender string `json:"sender,omitempty"` // sender
Recipient string `json:"recipient,omitempty"` // recipient
Content any `json:"content,omitempty"` // send content
Symbol string `json:"symbol,omitempty"` // Subscription type
Logo string `json:"logo_link,omitempty"` // Subscription type
KeepDecimal string `json:"keep_decimal"`
}
var SpotMarketCache = sync.Map{}
var ContractCache = sync.Map{}
// Define an UpGrader to upgrade a regular HTTP connection to a websocket connection
var up = &websocket.Upgrader{
// Define read/write buffer size
WriteBufferSize: 1024,
ReadBufferSize: 1024,
// Verification request
CheckOrigin: func(r *http.Request) bool {
// If it is not a get request, return an error
if r.Method != "GET" {
fmt.Println("Request method error")
return false
}
// If chat is not included in the path, an error is returned
if r.URL.Path != "/quotes-wss" {
fmt.Println("Request path error")
return false
}
// Verification rules can also be customized according to other needs
return true
},
}
func Connect(host, addr string) {
//广播启动
go write()
http.HandleFunc("/quotes-wss", wsHandle)
url := fmt.Sprintf("%v%v", host, addr)
fmt.Println(url)
err := http.ListenAndServe(url, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func wsHandle(w http.ResponseWriter, r *http.Request) {
// Obtain a link through the upgraded upgrade tool
conn, err := up.Upgrade(w, r, nil)
if err != nil {
applogger.Info("获取连接失败:%v", err)
return
}
// Register users after successful connection
user := &User{
Id: uuid.NewV4().String(),
conn: conn,
msg: make(chan []byte),
stop: make(chan string),
start: make(chan string),
symbol: sync.Map{},
mux: sync.Mutex{},
}
read(user)
//go hearBeat(user)
}
// Read the message sent by the client and process the return response
func read(user *User) {
defer func() {
user.conn.Close()
}()
for {
_, msg, err := user.conn.ReadMessage()
if err != nil {
offLine(user.conn)
applogger.Info("user exit:%v", user.conn.RemoteAddr().String())
return
}
// Process business logic
psgMsg := model.SubMessage(string(msg))
fmt.Println(psgMsg)
if psgMsg != nil {
switch psgMsg.Type {
case "ping": // Receiving ping
aloneSend(user.conn, []byte(model.ReturnValue("pong")))
case "subscribe": // Receive subscription
applogger.Info("接收到订阅消息体:", string(msg))
aloneSend(user.conn, []byte(model.ReturnValue("subscribe success")))
//获取 服务是否 订阅该数据
mutex.RLock()
conns, ok := wsConMap[psgMsg.Symbol]
mutex.RUnlock()
if ok {
//查询client是否订阅
conns = checkClient(conns, user.conn)
} else {
//添加订阅
conns = make([]*websocket.Conn, 0)
conns = append(conns, user.conn)
}
applogger.Info("psgMsg.Symbol", psgMsg.Symbol)
mutex.Lock()
wsConMap[psgMsg.Symbol] = conns
mutex.Unlock()
if !ok {
go user.userSubscribe(psgMsg.Symbol)
}
case "unSubscribe": // Receive unsubscribe
applogger.Info("Received unsubscribe message body:", string(msg))
mutex.RLock()
conns, ok := wsConMap[psgMsg.Symbol]
mutex.RUnlock()
if ok {
//取消订阅
removeWsconn(conns, user.conn, psgMsg.Symbol)
}
aloneSend(user.conn, []byte(model.ReturnValue("unSubscribe success")))
applogger.Debug("Subscription type after current user deletion:%v", ok)
default:
// TODO: Handling other situations transmitted by customers
}
}
}
}
// 清理客户端
func removeWsconn(conn []*websocket.Conn, userConn *websocket.Conn, symbol string) error {
index := -1
for i, v := range conn {
if v == userConn {
index = i
break
}
}
if index >= 0 {
conn = append(conn[:index], conn[index+1:]...)
mutex.Lock()
wsConMap[symbol] = conn
mutex.Unlock()
}
return nil
}
func deleteWsconn(symbol string) {
mutex.Lock()
defer mutex.Unlock()
delete(wsConMap, symbol)
}
// 检查是否订阅过
func checkClient(conns []*websocket.Conn, conn *websocket.Conn) []*websocket.Conn {
for _, v := range conns {
if v == conn {
return conns
}
}
conns = append(conns, conn)
return conns
}
// 广播
func broadcastWebSocket(msg []byte, symbol string) {
mutex.RLock()
conns, ok := wsConMap[symbol]
mutex.RUnlock()
if !ok {
applogger.Error("Parsing data information:%v")
return
}
for _, val := range conns {
if err := aloneSend(val, msg); err != nil {
removeWsconn(conns, val, symbol)
}
}
}
// 断开连接 清理订阅
func offLine(userConn *websocket.Conn) {
for key, v := range wsConMap {
removeWsconn(v, userConn, key)
}
}
// send message
func aloneSend(conn *websocket.Conn, message []byte) error {
mutexSpotConn.Lock()
defer mutexSpotConn.Unlock()
conn.SetWriteDeadline(time.Now().Add(writeWait))
w, err := conn.NextWriter(websocket.TextMessage) // Write data in the form of io, with parameters of data type
if err != nil {
//发送失败
applogger.Error("Failed to conn.NextWriter :%v", err)
return err
}
if _, err := w.Write(message); err != nil { // Write data, this function is truly used to transmit data to the foreground
applogger.Error("Failed Write message :%v", err)
return err
}
if err := w.Close(); err != nil { // Close write stream
applogger.Error("Failed to close write stream:%v", err)
return nil
}
return nil
}
//func hearBeat(user *User) {
// defer func() {
// user.conn.Close()
// }()
// ticker := time.NewTicker(writeWait) // Set timing
// for {
// select {
// case <-ticker.C:
// TODO: Need to add a server to ping the client
// //发送心跳
// if err := aloneSend(user.conn, []byte(model.ReturnValue("ping"))); err != nil {
// applogger.Error("Failed to send heartbeat message: ", err)
// //ping 不通 清理客户端
// offLine(user.conn)
// return
// }
// }
// }
//}
// Producer sends messages
func write() {
for message := range msgChan {
// TODO: 延迟200毫秒
time.Sleep(20 * time.Millisecond)
var subMsg Message
json.Unmarshal(message, &subMsg)
//广播
broadcastWebSocket(message, subMsg.Symbol)
applogger.Info("broadcast WebSocket Sub message info:%v", subMsg.Symbol)
}
}
// User subscription and cancellation
func (u *User) userSubscribe(symbol string) {
applogger.Info("新交易对", symbol)
pubSub := red.RedisClient.Subscribe(symbol)
defer func() {
pubSub.Close()
}()
_, err := pubSub.Receive()
if err != nil {
applogger.Error("failed to receive from control PubSub,%v", zap.Error(err))
return
}
//fmt.Println(pubSub)
ch := pubSub.Channel()
for msg := range ch {
var subMsg Message
//合约深度解压包
isSubscribeCtDepth := strings.Contains(symbol, "USDT.depth.step")
switch isSubscribeCtDepth {
case true:
content, err := gzip.GZipDecompress([]byte(msg.Payload))
if err != nil {
applogger.Error("UnGZip data error: %s", err)
return
}
result := market.SubscribeCtDepthResponse{}
if err := json.Unmarshal([]byte(content), &result); err != nil {
applogger.Error("Depth Unmarshal ", err)
close(u.msg)
return
}
//fmt.Println(result)
subMsg.Symbol = result.Channel
subMsg.Content = result
subMsg.ServersId = result.Channel
message, _ := json.Marshal(subMsg)
msg.Payload = string(message)
default:
//applogger.Info("Subscribe date:%v", msg.Payload)
if err := json.Unmarshal([]byte(msg.Payload), &subMsg); err != nil {
applogger.Error("Parsing data information:%v", err)
close(u.msg)
return
}
}
//fmt.Println("返回数据 : ", msg.Payload)
//交易全被被取消订阅
mutex.RLock()
conns := wsConMap[subMsg.Symbol]
mutex.RUnlock()
if len(conns) > 0 {
msgChan <- []byte(msg.Payload)
} else {
applogger.Debug("Starting unsubscribe.....", symbol)
deleteWsconn(symbol)
pubSub.Unsubscribe(symbol)
return
}
}
}
func HashValue(hashListName string) []stock.PHPData {
keys := red.Scan(hashListName)
result := make([]stock.PHPData, 0)
for _, v := range keys {
res, _ := red.HGetAll(v)
fmt.Println(res)
item := stock.PHPData{}
for field, value := range res {
switch field {
case "name":
item.Name = value
case "code":
item.Code = value
case "keep_decimal":
item.KeepDecimal = value
case "face_value":
item.FaceValue, _ = strconv.Atoi(value)
case "max_pry":
item.MaxPry, _ = strconv.Atoi(value)
case "min_pry":
item.MinPry, _ = strconv.Atoi(value)
case "logo_link":
item.LogoLink = value
case "status":
item.Status, _ = strconv.Atoi(value)
}
}
//if item.Status == StatusPass {
result = append(result, item)
//}
}
return result
}
func HashValueOnce(key string) (stock.PHPData, error) {
res, _ := red.HGetAll(key)
item := stock.PHPData{}
for field, value := range res {
switch field {
case "name":
item.Name = value
case "code":
item.Code = value
case "keep_decimal":
item.KeepDecimal = value
case "face_value":
item.FaceValue, _ = strconv.Atoi(value)
case "max_pry":
item.MaxPry, _ = strconv.Atoi(value)
case "min_pry":
item.MinPry, _ = strconv.Atoi(value)
case "logo_link":
item.LogoLink = value
case "status":
item.Status, _ = strconv.Atoi(value)
}
}
if item.Status == StatusPass {
return item, nil
}
return stock.PHPData{}, errors.New("data is null")
}
// market_type //1现货,2合约,3美股
func PHPResutl(market_type int) stock.PHPRes {
var eodModel stock.PHPRes
url := fmt.Sprintf("https://%v/bs/market",
config.Config.HbApi.PHPHost)
bodyStr, err := internal.HttpPost(url, fmt.Sprintf(`{"market_type":"%d","trade_name":"","page":1,"page_size":1000}`, market_type))
if err != nil {
applogger.Error("Failed to query data:%v", err)
return eodModel
}
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return eodModel
}
return eodModel
}
// market_type 1现货,2合约,3美股
func PHPMarketTrade(market_type, num int, trade_name string) stock.PHPMarketTradeList {
var eodModel stock.PHPMarketTradeList
url := fmt.Sprintf("https://%v/bs/market_trade",
config.Config.HbApi.PHPHost)
fmt.Println(url)
bodyStr, err := internal.HttpPost(url, fmt.Sprintf(`{"market_type":%d,"trade_name":"%s","num":%d}`, market_type, trade_name, num))
fmt.Println(bodyStr)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return eodModel
}
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return eodModel
}
return eodModel
}
func SubscriptionCache() {
spot := HashValue(RedisDIGITAL)
contract := HashValue(RedisCONTRACT)
applogger.Info("spot :", spot, "contract :", contract)
for _, v := range spot {
symbol := fmt.Sprintf("market.%susdt.kline.1day", strings.ToLower(v.Name))
go UserMainSubscribe(symbol, SpotsStatus, v.Name)
}
for _, v := range contract {
symbol := fmt.Sprintf("market.%s.kline.1day", v.Name)
go UserMainSubscribe(symbol, ContractStatus, v.Name)
}
}
// 1 合约 2 现货
func UserMainSubscribe(symbol string, market_type int, name string) {
applogger.Info("新交易对", symbol)
var subMsg Message
pubSub := red.RedisClient.Subscribe(symbol)
defer func() {
pubSub.Close()
}()
_, err := pubSub.Receive()
if err != nil {
applogger.Error("failed to receive from control PubSub,%v", zap.Error(err))
return
}
ch := pubSub.Channel()
for msg := range ch {
//applogger.Info("Subscribe date:%v", msg.Payload)
if err := json.Unmarshal([]byte(msg.Payload), &subMsg); err != nil {
applogger.Error("Parsing data information:%v", err)
return
}
if subMsg.Symbol == "" {
applogger.Error("symbol not data :%v", err)
continue
}
if market_type == SpotsStatus {
val, err := HashValueOnce(fmt.Sprintf("%s:%s", RedisDIGITAL, name))
if err != nil {
//applogger.Error(name, err.Error())
SpotMarketCache.Delete(strings.ToLower(name))
continue
}
subMsg.Logo = val.LogoLink
subMsg.Symbol = strings.ToLower(val.Name)
subMsg.KeepDecimal = val.KeepDecimal
SpotMarketCache.Store(strings.ToLower(name), subMsg)
} else {
val, err := HashValueOnce(fmt.Sprintf("%s:%s", RedisCONTRACT, name))
if err != nil {
//applogger.Error(name, err.Error())
ContractCache.Delete(name)
continue
}
subMsg.Logo = val.LogoLink
subMsg.Symbol = val.Name
subMsg.KeepDecimal = val.KeepDecimal
ContractCache.Store(name, subMsg)
}
}
}
// Send data to websocket websocketServer
func (u *User) send(data string) (err error) {
if u.conn == nil {
applogger.Error("WebSocket sent error: no connection available")
return err
}
u.mux.Lock()
err = u.conn.WriteMessage(websocket.TextMessage, []byte(data))
u.mux.Unlock()
return err
}

412
cmd/websocketservice/websocketusclient.go

@ -0,0 +1,412 @@
package websocketservice
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
"github.com/satori/go.uuid"
"go.uber.org/zap"
"log"
"math"
"net/http"
"strings"
"sync"
"time"
"wss-pool/cmd/common"
"wss-pool/dictionary"
"wss-pool/internal/model"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
mol "wss-pool/pkg/model"
)
// Define a websocket connection object that contains information for each connection
type Client struct {
Id string // Client ID
conn *websocket.Conn // Define websocket link objects
msg chan []byte // Define messages received and distributed
symbol sync.Map // Concurrent Security - Manage User Subscription Types
mux sync.Mutex
}
type StockMessage struct {
S string `json:"s,omitempty"` // 股票代码
Country string `json:"country"` //国家
StockCode string `json:"stock_code" bson:"stock_code"` // 股票代码
Symbol string `json:"symbol"`
Stock string `json:"stock"` // 期权代码
IsStockIndex bool `json:"is_stock_index"`
IsOptionList bool `json:"is_option_list"`
IsOptionInfo bool `json:"is_option_info"`
}
var (
wsStockConMap = map[string][]*websocket.Conn{}
mutexStock = sync.RWMutex{}
msgStockChan = make(chan []byte)
mutexConn = sync.RWMutex{}
TotalNum int
mutexTotal = sync.RWMutex{}
countryMap = make(map[string][]string)
mutexCountry = sync.RWMutex{}
clearClientChan = make(chan *websocket.Conn)
)
const (
stockConnNum int = 20
)
// Define an UpGrader to upgrade a regular HTTP connection to a websocket connection
var upServer = &websocket.Upgrader{
// Define read/write buffer size
WriteBufferSize: 1024,
ReadBufferSize: 1024,
// Verification request
CheckOrigin: func(r *http.Request) bool {
// If it is not a get request, return an error
if r.Method != "GET" {
fmt.Println("Request method error")
return false
}
// If chat is not included in the path, an error is returned
if r.URL.Path != "/quotes-share-wss" {
fmt.Println("Request path error")
return false
}
token := r.URL.Query().Get("token")
if !common.CheckToken(token) {
applogger.Debug("token expired")
return false
}
// Verification rules can also be customized according to other needs
return true
},
}
// ShareConnect
func ShareConnect(host, addr string) {
go writeShare()
go offLineStock()
http.HandleFunc("/quotes-share-wss", wsHandleShare)
url := fmt.Sprintf("%v%v", host, addr)
err := http.ListenAndServe(url, nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func wsHandleShare(w http.ResponseWriter, r *http.Request) {
// Obtain a link through the upgraded upgrade tool
conn, err := upServer.Upgrade(w, r, nil)
if err != nil {
applogger.Info("Failed to obtain connection:%v", err)
return
}
// Register users after successful connection
client := &Client{
Id: uuid.NewV4().String(),
conn: conn,
msg: make(chan []byte),
symbol: sync.Map{},
mux: sync.Mutex{},
}
readShare(client)
}
func setTotalNum(num int) {
mutexTotal.Lock()
defer mutexTotal.Unlock()
TotalNum += num
}
func getTotalNum() {
mutexTotal.RLock()
defer mutexTotal.RUnlock()
applogger.Debug("number of colleagues online :%v", TotalNum)
}
// Read the message sent by the client and process the return response
func readShare(cl *Client) {
defer cl.conn.Close()
setTotalNum(1)
getTotalNum()
for {
_, msg, err := cl.conn.ReadMessage()
if err != nil {
clearClientChan <- cl.conn
applogger.Debug("user exit:%v", cl.conn.RemoteAddr().String())
return
}
// Process business logic
psgMsg := model.SubMessage(string(msg))
if psgMsg != nil {
switch psgMsg.Type {
case "ping": // Receiving ping
aloneSendStock(cl.conn, []byte(model.ReturnValue("pong")))
case "subscribe": // Receive subscription
country, stock := getCountry(psgMsg.Symbol)
if !dictionary.StockCountryMap[country] {
applogger.Error(country, "incorrect subscription information 不属于合规的股票市场")
aloneSendStock(cl.conn, []byte(model.ReturnValue("incorrect subscription information")))
clearClientChan <- cl.conn
return
}
aloneSendStock(cl.conn, []byte(model.ReturnValue("subscribe success")))
//获取 服务是否 订阅该数据
mutexStock.RLock()
conns, ok := wsStockConMap[psgMsg.Symbol]
mutexStock.RUnlock()
if ok {
//查询client是否订阅
conns = checkClient(conns, cl.conn)
} else {
//添加订阅
conns = make([]*websocket.Conn, 0)
conns = append(conns, cl.conn)
}
mutexStock.Lock()
wsStockConMap[psgMsg.Symbol] = conns
mutexStock.Unlock()
applogger.Info("psgMsg.Symbol:%v,wsStockClient:%v", psgMsg.Symbol, wsStockConMap)
if !ok {
go cl.userPSubscribeUs(country, stock)
}
case "unSubscribe": // Receive unsubscribe
applogger.Info("Received unsubscribe message body:", string(msg))
mutexStock.RLock()
conns, ok := wsStockConMap[psgMsg.Symbol]
mutexStock.RUnlock()
if ok {
//取消订阅
removeWsconnStock(conns, cl.conn, psgMsg.Symbol)
}
aloneSendStock(cl.conn, []byte(model.ReturnValue("unSubscribe success")))
applogger.Debug("Subscription type after current user deletion:%v", ok)
default:
// TODO: Handling other situations transmitted by customers
applogger.Debug("Please provide accurate instructions......")
}
}
}
}
// 废弃 客户端
func offLineStock() {
for userConn := range clearClientChan {
setTotalNum(-1)
getTotalNum()
for key, v := range wsStockConMap {
removeWsconnStock(v, userConn, key)
}
}
}
// send message
func aloneSendStock(conn *websocket.Conn, message []byte) error {
mutexConn.Lock()
defer mutexConn.Unlock()
//applogger.Debug("aloneSendStock :%v",conn,message)
conn.SetWriteDeadline(time.Now().Add(writeWait))
w, err := conn.NextWriter(websocket.TextMessage) // Write data in the form of io, with parameters of data type
if err != nil {
//发送失败
applogger.Error("Failed to conn.NextWriter :%v", err)
return err
}
if _, err := w.Write(message); err != nil { // Write data, this function is truly used to transmit data to the foreground
applogger.Error("Failed Write message :%v", err)
return err
}
if err := w.Close(); err != nil { // Close write stream
applogger.Error("Failed to close write stream:%v", err)
return nil
}
return nil
}
// 清理客户端
func removeWsconnStock(conn []*websocket.Conn, userConn *websocket.Conn, symbol string) error {
index := -1
for i, v := range conn {
if v == userConn {
index = i
break
}
}
if index >= 0 {
conn = append(conn[:index], conn[index+1:]...)
mutexStock.Lock()
wsStockConMap[symbol] = conn
mutexStock.Unlock()
}
return nil
}
func deleteWsconnStock(symbol string) {
mutexStock.Lock()
defer mutexStock.Unlock()
delete(wsStockConMap, symbol)
}
// 广播
func broadcastWebSocketStock(msg []byte, symbol string) {
//applogger.Debug("订阅客户端:%v---%v", wsStockConMap, symbol)
mutexStock.RLock()
conns, ok := wsStockConMap[symbol]
mutexStock.RUnlock()
if !ok {
applogger.Error("Parsing data information:%v")
return
}
total := len(conns)
if total <= 0 {
return
}
start := time.Now()
connDiv := int(math.Ceil(float64(total) / float64(stockConnNum)))
//分批并发推送
for i := 0; i < connDiv; i++ {
startIndex := i * stockConnNum
endIndex := (i + 1) * stockConnNum
if endIndex > total {
endIndex = total
}
wg := sync.WaitGroup{}
for _, val := range conns[startIndex:endIndex] {
wg.Add(1)
go func(val *websocket.Conn, msg []byte) {
defer wg.Done()
aloneSendStock(val, msg)
}(val, msg)
}
wg.Wait()
}
applogger.Info("broadcast WebSocket info : %v ;total:%v;time-consuming %v ", symbol, total, time.Since(start))
}
func writeShare() {
for message := range msgStockChan {
if strings.Contains(string(message), "\"ev\":\"CAS\"") { // 外汇行情订阅
var subMsg mol.ForexJsonData
if err := json.Unmarshal(message, &subMsg); err != nil {
applogger.Error(err.Error())
}
broadcastWebSocketStock(message, fmt.Sprintf("%s.Forex", subMsg.Pair))
} else if strings.Contains(string(message), "\"ev\":\"CAS-D\"") { // 外汇行情天订阅
var subMsg mol.ForexJsonData
if err := json.Unmarshal(message, &subMsg); err != nil {
applogger.Error(err.Error())
}
broadcastWebSocketStock(message, fmt.Sprintf("%s.DayForex", subMsg.Pair))
} else if strings.Contains(string(message), "\"ev\":\"C\"") { // 外汇买一卖一报价
var subMsg mol.ForexLastQuote
if err := json.Unmarshal(message, &subMsg); err != nil {
applogger.Error(err.Error())
}
broadcastWebSocketStock(message, fmt.Sprintf("%s.LastForex", subMsg.P))
} else if strings.Contains(string(message), "\"ev\":\"T\"") {
var subMsg mol.ForexTrade
if err := json.Unmarshal(message, &subMsg); err != nil {
applogger.Error(err.Error())
}
broadcastWebSocketStock(message, fmt.Sprintf("%s.TradeForex", subMsg.Code))
} else {
var subMsg StockMessage
if err := json.Unmarshal(message, &subMsg); err != nil {
applogger.Error(err.Error())
}
if subMsg.S == "" {
if subMsg.IsStockIndex { // 指数行情订阅
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.StockCode, common.StockIndexPrefix))
} else if subMsg.IsOptionList { // 期权列表行情订阅
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.Stock, fmt.Sprintf("%s%s%s", common.StockOption, common.CapitalizeFirstLetter(subMsg.Country), common.StockOptionList)))
} else if subMsg.IsOptionInfo { // 期权详情订阅
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.Stock, fmt.Sprintf("%s%s%s", common.StockOption, common.CapitalizeFirstLetter(subMsg.Country), common.StockOptionInfo)))
} else { // 东南亚股票市场行情订阅(tradingView)
broadcastWebSocketStock(message, fmt.Sprintf("%s.%s", subMsg.Symbol, common.CapitalizeFirstLetter(subMsg.Country)))
}
continue
}
// 美股行情订阅
broadcastWebSocketStock(message, fmt.Sprintf("%s.US", subMsg.S))
}
}
}
func getCountry(symbol string) (string, string) {
symbolArr := strings.Split(symbol, ".")
if len(symbolArr) < 2 {
applogger.Error("symbol 有误")
return "", ""
}
county := symbolArr[len(symbolArr)-1]
return county, symbol[0 : strings.Index(symbol, county)-1]
}
// 按市场订阅
func (cl *Client) userPSubscribeUs(country, symbol string) {
mutexCountry.RLock()
symbols, ok := countryMap[country]
mutexCountry.RUnlock()
//提加
mutexCountry.Lock()
countryMap[country] = append(symbols, symbol)
mutexCountry.Unlock()
if ok {
//applogger.Error(country, "已订阅")
return
}
applogger.Debug(country, "start a stock subscription")
pubSub := red.RedisClient.PSubscribe(fmt.Sprintf("*.%s", country))
defer func() {
pubSub.Close()
}()
_, err := pubSub.Receive()
if err != nil {
applogger.Error("failed to receive from control PubSub,%v", zap.Error(err))
return
}
ch := pubSub.Channel()
for msg := range ch {
mutexStock.RLock()
conns, ok := wsStockConMap[msg.Channel]
mutexStock.RUnlock()
//未订阅股票跳出
if !ok {
continue
}
if len(conns) > 0 {
msgStockChan <- []byte(msg.Payload)
} else {
deleteWsconnStock(msg.Channel)
mutexCountry.RLock()
symbols := countryMap[country]
mutexCountry.RUnlock()
index := -1
msgChannel := strings.Split(msg.Channel, ".")
for i, v := range symbols {
if v == msgChannel[0] {
index = i
break
}
}
if index >= 0 {
symbols = append(symbols[:index], symbols[index+1:]...)
mutexCountry.Lock()
countryMap[country] = symbols
mutexCountry.Unlock()
}
// 退订
if len(symbols) <= 0 {
mutexCountry.Lock()
delete(countryMap, country)
mutexCountry.Unlock()
applogger.Debug("Starting unsubscribe.....", country)
pubSub.PUnsubscribe(fmt.Sprintf("*.%s", country))
return
}
}
}
}

25
config/config.go

@ -0,0 +1,25 @@
package config
import (
"gopkg.in/yaml.v3"
"os"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var Config model.Config
// LoadConfig Load config
func LoadConfig(urlPath string) {
dataBytes, err := os.ReadFile(urlPath)
if err != nil {
applogger.Info("fail to read file:%v", err)
return
}
err = yaml.Unmarshal(dataBytes, &Config)
if err != nil {
applogger.Info("Failed to parse yaml file:%v", string(dataBytes), err.Error())
return
}
applogger.Info("config---->:%v", Config)
}

84
dictionary/publickey.go

@ -0,0 +1,84 @@
package dictionary
// usdt wbtc dai busd
// 计量单位
var TypeUnit = "usdt"
// TODO: 系统数字火币-后期需要做个字典管理功能
var Symbol = []string{"btc", "eth", "bnb", "usdc", "xrp", "eos", "ada", "doge", "sol", "trx", "ltc", "dot", "matic", "bch", "ton", "avax", "shib", "invu", "osel", "fmd", "dten", "dten", "xnsl", "kools"}
// 火币K线
var TimeCycle = []string{"1min", "5min", "15min", "30min", "60min", "4hour", "1day", "1mon", "1week", "1year"}
// 币安K线
var BaTimeCycle = []string{"1m", "5m", "15m", "30m", "1h", "4h", "1d", "1M", "1w"}
var BaToBaMap = map[string]string{
"1m": "1min",
"5m": "5min",
"15m": "15min",
"30m": "30min",
"1h": "60min",
"4h": "4hour",
"1d": "1day",
"1M": "1mon",
"1w": "1week",
}
// 币安深度
var BaDepth = []string{"5", "10", "20"}
// 深度
var Depth = []string{"step0", "step1", "step2", "step3", "step4", "step5"}
// 市场深度MBP行情数据
var LevelsRefresh = []int{5, 10, 20}
var LevelsMbp = []int{5, 20, 400}
/*
合约订阅接口
1永续:BTC-USDT永续合约代码
2交割BTC-USDT-210625交割合约代码
3BTC-USDT-CW当周合约标识
4BTC-USDT-NW次周合约标识
5BTC-USDT-CQ当季合约标识
6BTC-USDT-NQ次季合约标识
*/
var ContractCodeList = []string{"BTC-USDT", "ETH-USDT", "BCH-USDT", "XRP-USDT", "EOS-USDT", "LTC-USDT", "TRX-USDT", "ETC-USDT", "LINK-USDT", "BNB-USDT", "ADA-USDT", "DOGE-USDT", "SOL-USDT", "DOT-USDT", "MATIC-USDT", "AVAX-USDT", "SHIB-USDT", "BNBS-USDT", "INVU-USDT", "OSEL-USDT", "FMD-USDT", "DTEN-USDT", "XNSL-USDT", "KOOLS-USDT"}
var ContractCode = []string{"-USDT", "-USDT-CW", "-USDT-NW", "-USDT-CQ", "-USDT-NQ"}
var ContractTime = []string{"1min", "5min", "15min", "30min", "60min", "4hour", "1day", "1week", "1mon"}
var ContractPriceTime = []string{"1min", "5min", "15min", "30min", "60min", "4hour", "1day", "1week", "1mon"}
var ContractDepth = []string{"step0", "step1", "step2", "step3", "step4", "step5", "step6", "step7", "step8", "step9", "step10", "step11", "step12", "step13", "step14", "step15", "step16", "step17", "step18", "step19"}
var ContractAddDepth = []string{"20", "150"}
// 美股市场
var StockUsListTime = []string{"5min", "15min", "30min", "1hour", "1day", "1week", "1mon"}
var StockUsListDayTime = []string{"1day", "1week", "1mon"}
var StockUsListTimeMap = map[string]string{"5min": "5", "15min": "15", "30min": "30", "1hour": "hour", "1day": "day", "1week": "week", "1mon": "month"}
// 股票(马股|泰股|印尼股|印度股|新加坡股|港股|英股|法股|德股|巴西股|日本股)市场
var StockCodeList = []string{"Malaysia", "Thailand", "Indonesia", "India", "Singapore", "HongKong", "UK", "France", "Germany", "Brazil", "Japan"}
var StockSouthAsiaListTime = []string{"5min", "15min", "30min", "1hour", "1day", "1week", "1mon"}
var StockSouthAsiaListTimes = []string{"1hour", "1day", "1week", "1mon"}
var StockCountryMap = map[string]bool{
"Thailand": true,
"Indonesia": true,
"India": true,
"US": true,
"StockIndex": true,
"Malaysia": true,
"Singapore": true,
"HongKong": true,
"OptionIndiaList": true,
"OptionIndiaInfo": true,
"UK": true,
"France": true,
"Germany": true,
"Brazil": true,
"Japan": true,
"Forex": true,
"DayForex": true,
"LastForex": true,
"TradeForex": true,
}
var OptionCodeList = []string{"US", "India"}

106
internal/Publicmethods.go

@ -0,0 +1,106 @@
package internal
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"math/rand"
"strings"
"time"
"wss-pool/config"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var (
pr = fmt.Sprintf
topIckLine = "market.kline"
)
const (
OneMin = "1min"
FiveTime = "5min"
FifteenTime = "15min"
ThirtyTime = "30min"
SixtyTime = "60min"
fourTime = "4hour"
OneDay = "1day"
OneMon = "1mon"
OneWeek = "1week"
OneYear = "1year"
)
// CheckKLineTable
func CheckKLineTable(suffix string) string {
// "1min", "5min", "15min", "30min", "60min", "4hour", "1day", "1mon", "1week", "1year"
if strings.Contains(suffix, OneMin) {
return pr("%v.%v", topIckLine, OneMin)
}
if strings.Contains(suffix, FiveTime) {
return pr("%v.%v", topIckLine, FiveTime)
}
if strings.Contains(suffix, FifteenTime) {
return pr("%v.%v", topIckLine, FifteenTime)
}
if strings.Contains(suffix, ThirtyTime) {
return pr("%v.%v", topIckLine, ThirtyTime)
}
if strings.Contains(suffix, SixtyTime) {
return pr("%v.%v", topIckLine, SixtyTime)
}
if strings.Contains(suffix, fourTime) {
return pr("%v.%v", topIckLine, fourTime)
}
if strings.Contains(suffix, OneDay) {
return pr("%v.%v", topIckLine, OneDay)
}
if strings.Contains(suffix, OneMon) {
return pr("%v.%v", topIckLine, OneMon)
}
if strings.Contains(suffix, OneWeek) {
return pr("%v.%v", topIckLine, OneWeek)
}
if strings.Contains(suffix, OneYear) {
return pr("%v.%v", topIckLine, OneYear)
}
return ""
}
// Md5 token
func Md5(s string) string {
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}
// Captcha
func Captcha(n int) string {
code := ""
rand.Seed(time.Now().Unix())
for i := 0; i < n; i++ {
code = fmt.Sprintf("%s%d", code, rand.Intn(10))
}
return code
}
// GetToken
func GetToken() (string, error) {
var tm model.Token
url := fmt.Sprintf("http://%v:9999/v1/getcomm/get-comm/token.html", config.Config.DomainName)
jsonP, err := HttpPost(url, "")
if err != nil {
applogger.Error("HttpPost err: %v", err)
return "", err
}
if err := json.Unmarshal([]byte(jsonP), &tm); err != nil {
applogger.Error("json Unmarshal err: %v", err)
return "", err
}
token := tm.Data
return token, nil
}

58
internal/common.go

@ -0,0 +1,58 @@
package internal
import (
"strings"
"time"
)
// GetBetweenDates 根据开始日期和结束日期计算出时间段内所有日期
// 参数为日期格式,如:2020-01-01
func GetBetweenDates(sdate, edate string) []string {
d := []string{}
timeFormatTpl := "2006-01-02 15:04:05"
if len(timeFormatTpl) != len(sdate) {
timeFormatTpl = timeFormatTpl[0:len(sdate)]
}
date, err := time.Parse(timeFormatTpl, sdate)
if err != nil {
// 时间解析,异常
return d
}
date2, err := time.Parse(timeFormatTpl, edate)
if err != nil {
// 时间解析,异常
return d
}
if date2.Before(date) {
// 如果结束时间小于开始时间,异常
return d
}
// 输出日期格式固定
timeFormatTpl = "2006-01-02"
date2Str := date2.Format(timeFormatTpl)
d = append(d, date.Format(timeFormatTpl))
for {
date = date.AddDate(0, 0, 1)
dateStr := date.Format(timeFormatTpl)
d = append(d, dateStr)
if dateStr == date2Str {
break
}
}
return d
}
func StringReplace(str string) string {
return strings.Replace(str, " ", "", -1)
}
func ConversionTime(beforeTime string) int64 {
afterTime, err := time.ParseInLocation("2006-01-02", beforeTime, time.Local)
if err != nil {
afterTime, err = time.ParseInLocation("2006-01-02", beforeTime, time.Local)
if err != nil {
afterTime, err = time.ParseInLocation("2006-01-02 15:04:05", beforeTime, time.Local)
}
}
return afterTime.Unix()
}

48
internal/data/business/forextoexcel.go

@ -0,0 +1,48 @@
package business
import (
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
"go.mongodb.org/mongo-driver/bson"
"strconv"
"wss-pool/config"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/stock"
)
// TickerToExcel
//
// @Description: 导出excel
func TickerToExcel() {
data.Mgo_init(config.Config.Mongodb)
filter := bson.M{"ticker": bson.M{"$regex": "USD$"}}
res := make([]stock.ForexData, 0)
data.MgoFindRes(data.ForexList, filter, &res)
applogger.Debug("TickerToExcel to info:", len(res))
// 创建Excel文件
file := excelize.NewFile()
sheetName := "Sheet1"
// 写入表头
file.SetCellValue(sheetName, "A1", "code")
file.SetCellValue(sheetName, "B1", "Name")
file.SetCellValue(sheetName, "C1", "Country")
file.SetCellValue(sheetName, "D1", "PrimaryExchange")
file.SetCellValue(sheetName, "E1", "Symbol")
file.SetCellValue(sheetName, "F1", "NumericCode")
// 写入数据
row := 2 // 从第二行开始写入数据
for _, val := range res {
file.SetCellValue(sheetName, "A"+strconv.Itoa(row), val.Ticker)
file.SetCellValue(sheetName, "B"+strconv.Itoa(row), "")
file.SetCellValue(sheetName, "C"+strconv.Itoa(row), "")
file.SetCellValue(sheetName, "D"+strconv.Itoa(row), "")
file.SetCellValue(sheetName, "E"+strconv.Itoa(row), "")
file.SetCellValue(sheetName, "F"+strconv.Itoa(row), "")
row++
}
err := file.SaveAs(fmt.Sprintf("./cmd/%s.xlsx", "forex"))
if err != nil {
applogger.Error("TickerToExecl to info err:", err)
}
}

245
internal/data/business/mgocontract.go

@ -0,0 +1,245 @@
package business
import (
"fmt"
"wss-pool/config"
"wss-pool/dictionary"
"wss-pool/logging/applogger"
"wss-pool/pkg/hbwssclient/marketwssclient"
"wss-pool/pkg/model"
"wss-pool/pkg/model/market"
)
// k线数据
func MgoSubscribeCtKline() {
symbolList := model.SymbolCtListString(dictionary.ContractTime)
client := new(marketwssclient.ContractKLineWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, period := range symbolList {
for _, value := range period {
client.Subscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtKlineResponse)
if ok {
if &resp != nil {
if resp.Tick != nil || resp.Data != nil {
applogger.Info("subscribeCtKline data,ServersId:%v,Sender:%v,Content:%v-%v", resp.Channel, resp.Tick, resp.Data)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// 深度信息
func MgoSubscribeCtDepth() {
symbolList := model.SymbolCtListString(dictionary.ContractDepth)
client := new(marketwssclient.ContractDepthWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, period := range symbolList {
for _, value := range period {
client.Subscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtDepthResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
applogger.Info("subscribeCtDepth data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// 新增深度信息
func MgoSubscribeCtAddDepth() {
symbolList := model.SymbolCtListString(dictionary.ContractAddDepth)
client := new(marketwssclient.ContractDepthSizeWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, period := range symbolList {
for _, value := range period {
client.Subscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtAddDepthResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
applogger.Info("subscribeCtAddDepth data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbContract.HbContractSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// 买一卖一行情数据
func MgoSubscribeCtBbo() {
symbolList := model.SymbolCtListString([]string{})
client := new(marketwssclient.ContractBBOWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtBboResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
applogger.Info("subscribeCtBbo data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// 合约详情数据
func MgoSubscribeCtDetail() {
symbolList := model.SymbolCtListString([]string{})
client := new(marketwssclient.ContractDetailWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtDetailResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
applogger.Info("subscribeCtDetail data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// 合约贸易详情数据
func MgoSubscribeCtTradeDetail() {
symbolList := model.SymbolCtListString([]string{})
client := new(marketwssclient.ContractTradeDetailWebSocketClient).Init(config.Config.HbContract.HbContractHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCtTradeDetailResponse)
if ok {
if &resp != nil {
if resp.Tick != nil {
applogger.Info("subscribeCtTradeDetail data,ServersId:%v,Sender:%v,Content:%v", resp.Channel, resp.Tick)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbContract.HbContractSubUids)
}
client.Close()
applogger.Info("Client closed")
}

119
internal/data/business/mgohistoricalus.go

@ -0,0 +1,119 @@
package business
import (
"encoding/json"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"strconv"
"strings"
"sync"
"wss-pool/config"
"wss-pool/dictionary"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/stock"
)
/*
采集美股历史数据
*/
const (
UsPageSize int64 = 500
)
func UpdateStockUs(start, end interface{}, state string) {
fmt.Println("updateStockUs", start, end, state)
stocks, _, pageTotal := GetStockAll("US", 1, UsPageSize)
wg := sync.WaitGroup{}
for _, value := range stocks {
wg.Add(1)
go func(start, end interface{}, state, code string) {
defer wg.Done()
param := fmt.Sprintf("apiKey=%v", config.Config.ShareGather.PolygonKey)
multiplier, _ := strconv.Atoi(dictionary.StockUsListTimeMap[state])
timespan := "minute"
if !strings.Contains(state, "min") {
multiplier = 1
timespan = dictionary.StockUsListTimeMap[state]
}
url := fmt.Sprintf("https://%v/v2/aggs/ticker/%v/range/%d/%v/%v/%v?adjusted=true&sort=asc&%v",
config.Config.ShareGather.PolygonHost, code, multiplier, timespan, start, end, param)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var eodModel stock.PreviousCloseResponse
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
applogger.Info("info", eodModel)
UpdateUs(eodModel, state)
}(start, end, state, value.Code)
}
wg.Wait()
for i := int64(2); i <= pageTotal; i++ {
stocks, _, _ := GetStockAll("US", i, UsPageSize)
wg := sync.WaitGroup{}
for _, value := range stocks {
wg.Add(1)
go func(start, end interface{}, state, code string) {
defer wg.Done()
param := fmt.Sprintf("apiKey=%v", config.Config.ShareGather.PolygonKey)
multiplier, _ := strconv.Atoi(dictionary.StockUsListTimeMap[state])
timespan := "minute"
if !strings.Contains(state, "min") {
multiplier = 1
timespan = dictionary.StockUsListTimeMap[state]
}
url := fmt.Sprintf("https://%v/v2/aggs/ticker/%v/range/%d/%v/%v/%v?adjusted=true&sort=asc&%v",
config.Config.ShareGather.PolygonHost, code, multiplier, timespan, start, end, param)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var eodModel stock.PreviousCloseResponse
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
UpdateUs(eodModel, state)
applogger.Info("info", eodModel)
}(start, end, state, value.Code)
}
wg.Wait()
}
}
func UpdateUs(result stock.PreviousCloseResponse, period string) {
if len(result.Results) <= 0 {
applogger.Error("us data is null %v", result)
return
}
var dataList []mongo.WriteModel
for _, val := range result.Results {
filter := bson.M{"code": bson.M{"$eq": result.Ticker}, "timestamp": bson.M{"$eq": val.T}}
update := bson.D{{"$set", bson.D{
{"code", result.Ticker},
{"timestamp", val.T},
{"vw", val.VW.String()},
{"o", val.O.String()},
{"c", val.C.String()},
{"h", val.H.String()},
{"n", val.N},
}}}
applogger.Info("UpdateUs info:%v", update)
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
tableName := data.GetStockUsTableName(period)
if len(dataList) > 0 {
if err := data.MgoBulkWrite(tableName, dataList); err != nil {
applogger.Error("UpdateUs MgoInsertMany err:%v", err)
}
}
}

993
internal/data/business/mgoinitdata.go

@ -0,0 +1,993 @@
package business
import (
"encoding/json"
"errors"
"fmt"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"math"
"strconv"
"strings"
"sync"
"time"
"wss-pool/cmd/common"
"wss-pool/config"
"wss-pool/dictionary"
"wss-pool/internal"
"wss-pool/internal/data"
red "wss-pool/internal/redis"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
"wss-pool/pkg/model/stock"
)
/*
TODO: 股票列表更新规则(注意每种股票的开盘时间进行更新)
1股票列表数据采集-定时任务(新增和更新)
2美股列表数据-定时任务(更新数据)
3马股列表数据-定时任务(更新数据)
*/
const (
ContracttTime int = 1 //数字币数据年限
StockTime int = 5 //美股数据年限
PageSize int64 = 120 //聚合股票条数
StockStatusOn int = 1
StockStatusOff int = 2
)
var (
OldNextUrl string
StockClosedDataList = map[string]int{
"US": 3, // 美股
"Malaysia": 5, // 马股
"Thailand": 6, // 泰股
"Indonesia": 4, // 印尼股
"India": 7, // 印度股
"Singapore": 9, // 新加坡股
"HongKong": 12, // 港股
"UK": 14, // 英股
"France": 15, // 法股
"Germany": 16, // 德股
"Brazil": 17, // 巴西股
"Japan": 18, // 日股
}
)
var CountryStartTime = map[string]int64{
"India": 42300000,
"Thailand": 39000000,
"Indonesia": 36000000,
"Malaysia": 32400000,
"Singapore": 32400000,
"UK": 54000000,
"France": 54000000,
"Germany": 54000000,
"Brazil": 75600000,
"Japan": 42300000,
}
var pinStock = map[string]map[string]map[string]bool{}
var pinStockMutex = sync.RWMutex{}
// InitStockList Stock List Collection
func InitStockList() {
url := fmt.Sprintf("https://%s/v3/reference/tickers?market=stocks&active=true&limit=1000&sort=ticker&apiKey=%s",
config.Config.ShareGather.PolygonHost, config.Config.ShareGather.PolygonKey)
for url != "" {
applogger.Debug("url info:%v", url)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var shareModel stock.StockPolygonParam
if err := json.Unmarshal([]byte(bodyStr), &shareModel); err != nil {
applogger.Error("Failed to parse stock list information:%v", err)
return
}
//调用失败
if shareModel.Status == "ERROR" {
fmt.Printf("%+v", shareModel)
time.Sleep(10 * time.Second)
continue
}
url = ""
if shareModel.NextUrl != "" && shareModel.NextUrl != OldNextUrl {
OldNextUrl = shareModel.NextUrl
url = fmt.Sprintf("%s&apiKey=%s&market=stocks", shareModel.NextUrl, config.Config.ShareGather.PolygonKey)
}
var dataList []mongo.WriteModel
for _, value := range shareModel.Results {
// TODO: 更新本地股票列表查
value.YesterdayClose = ""
value.BeforeClose = ""
filter := bson.D{{"Code", bson.M{
"$eq": value.Code,
}}, {"Country", bson.M{
"$eq": "US",
}}}
update := bson.D{{"$set", bson.D{
{"Code", value.Code},
{"Name", value.Name},
{"Country", strings.ToUpper(value.Locale)},
{"Exchange", value.PrimaryExchange},
{"Currency", value.Currency},
{"Type", value.Type},
{"Cik", value.CIK},
{"CompositeFigi", value.CompositeFigi},
{"ShareClassFigi", value.ShareClassFigi},
{"YesterdayClose", value.YesterdayClose},
{"BeforeClose", value.BeforeClose}}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
if err := data.MgoBulkWrite(data.StockList, dataList); err != nil {
applogger.Error("MgoInsertMany err:%v", err)
return
}
}
}
// TickUpdateStockUS US Stock List Data - Scheduled Tasks (Update Data)
func TickUpdateStockUS() {
for {
now := time.Now()
next := now.Add(time.Hour * 24)
next = time.Date(next.Year(), next.Month(), next.Day(), 6, 0, 0, 0, next.Location())
t := time.NewTimer(next.Sub(now))
<-t.C
UpdateStockUS()
}
}
// TickUpdateStockKLSE Equities List Data - Scheduled Tasks (Update Data)
func TickUpdateStockKLSE() {
for {
now := time.Now()
next := now.Add(time.Hour * 24)
next = time.Date(next.Year(), next.Month(), next.Day(), 6, 0, 0, 0, next.Location())
t := time.NewTimer(next.Sub(now))
<-t.C
UpdateStockKLSE()
}
}
// 火币现货只允许拉取当前2000条数据
func TickUpdateSpotKline() {
for _, value := range dictionary.TimeCycle {
UpdateSpotKline(value)
}
return
for {
t := time.NewTimer(1 * time.Hour)
<-t.C
for _, value := range dictionary.TimeCycle {
UpdateSpotKline(value)
}
}
}
// 火币K线价格只允许拉取当前2000条数据
func TickUpdateContractPriceKline() {
for _, value := range dictionary.ContractPriceTime {
UpdatePriceKline(value)
}
for {
t := time.NewTimer(1 * time.Hour)
<-t.C
for _, value := range dictionary.ContractPriceTime {
UpdatePriceKline(value)
}
}
}
// 合约
func TickUpdateContractKline(isAll bool) {
if isAll {
applogger.Info("start TickUpdateContractKline")
//endTime := time.Now().Unix()
//startTime := time.Now().AddDate(-0, -6, 0).Unix()
for _, value := range dictionary.ContractTime {
UpdateContractKline(value)
}
return
}
//测试专用
//for _, value := range dictionary.ContractTime {
// start := time.Now().Add(-2 * time.Hour).Unix()
// end := time.Now().Unix()
// UpdateContractKline(value, start, end)
//}
//for {
// t := time.NewTimer(2 * time.Hour)
// <-t.C
// for _, value := range dictionary.ContractTime {
// start := time.Now().Add(-2 * time.Hour).Unix()
// end := time.Now().Unix()
// UpdateContractKline(value, start, end)
// }
//
//}
}
// 美股
func TickUpdateStockUs(isAll bool) {
if isAll {
start := common.TimeToNows().AddDate(0, 0, -20).Format("2006-01-02")
end := common.TimeToNows().AddDate(0, 0, 0).Format("2006-01-02")
for _, value := range dictionary.StockUsListDayTime {
UpdateStockUs(start, end, value)
}
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), "run us stock -----------------------------end")
return
}
//nextFiveMinute := common.GenerateSingaporeFifteenMinTimestampOrigins()
//waitDuration := nextFiveMinute.Sub(common.TimeToNows())
//time.Sleep(waitDuration) // 60 min
runTime := time.Now() // 获取当前时间
if !common.IsOpeningUS() {
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), "us it's not opening time -----------------------------end")
return
}
end := common.GenerateSingaporeFifteenMinTimestampOrigin() * 1000
start := end - int64(60*60*1000)
for _, value := range dictionary.StockUsListTime {
UpdateStockUs(start, end, value)
}
fmt.Println("Run time: ", time.Since(runTime))
}
func GetTimeNewPrice(symbol string, from, to int64, country, period string) error {
//fmt.Println("country", country, "period", period, "from", common.ConvertToTimeStr(from/1000), from, "to", common.ConvertToTimeStr(to/1000), to)
filter := bson.M{"symbol": symbol, "timestamp": bson.M{"$gte": from, "$lte": to}}
tableName := data.GetStockTableName(common.CapitalizeFirstLetter(country))
highRes := make([]model.StockMogoParam, 0)
projection := bson.M{"price": 1}
sort := bson.M{"price": -1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &highRes, 1)
if len(highRes) <= 0 {
applogger.Error(symbol+" no data", period)
return errors.New(symbol + " no data")
}
high := highRes[0].Price
lowRes := make([]model.StockMogoParam, 0)
sort = bson.M{"price": 1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &lowRes, 1)
low := lowRes[0].Price
openRes := make([]model.StockMogoParam, 0)
sort = bson.M{"timestamp": 1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &openRes, 1)
open := openRes[0].Price
closeRes := make([]model.StockMogoParam, 0)
projection = bson.M{}
sort = bson.M{"timestamp": -1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &closeRes, 1)
var dataList []mongo.WriteModel
filter = bson.M{"timestamp": bson.M{"$eq": from}, "symbol": bson.M{"$eq": symbol}}
update := bson.D{{"$set", bson.D{
{"symbol", closeRes[0].Symbol},
{"stock_code", closeRes[0].StockCode},
{"stock_name", closeRes[0].StockName},
{"open_price", fmt.Sprintf("%f", open)},
{"high_price", fmt.Sprintf("%f", high)},
{"low_price", fmt.Sprintf("%f", low)},
{"close_price", fmt.Sprintf("%f", closeRes[0].Price)},
{"up_down_rate", closeRes[0].UpDownRate},
{"up_down", closeRes[0].UpDown},
{"trade_v", closeRes[0].TradeV},
{"trade_k", closeRes[0].TradeK},
{"vol", closeRes[0].Vol},
{"turnover_price_total", closeRes[0].TurnoverPriceTotal},
{"price_total", closeRes[0].PriceTotal},
//{"p_e", res[l].PE},
//{"eps", res[l].Eps},
//{"employees_number", res[l].EmployeesNumber},
//{"plate", res[l].Plate},
//{"desc", res[l].Desc},
{"price_code", closeRes[0].PriceCode},
{"country", closeRes[0].Country},
{"timestamp", from},
}}}
//applogger.Info("GetTimeNewPrice info: %v", update)
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
if err := data.MgoBulkWrite(data.GetStockSouthAsiaTableName(country, period), dataList); err != nil {
applogger.Error("stock MgoInsertMany err:%v", err)
return err
}
return nil
}
func GetTimeNewPriceAll(symbol string, from, to int64, country, period, periodPre string) {
//fmt.Println("country", country, "period", period, "from", common.ConvertToTimeStr(from/1000), "to", common.ConvertToTimeStr(to/1000))
filter := bson.M{"symbol": symbol, "timestamp": bson.M{"$gte": from, "$lte": to}}
tableName := data.GetStockSouthAsiaTableName(country, periodPre)
projection := bson.M{"stringVal": 1}
sort := bson.M{"stringVal": -1}
highRes := data.MgoFindProjectionAggregate(tableName, "high_price", filter, projection, sort, 1)
if len(highRes) <= 0 {
applogger.Error(symbol+" no data", period)
return
}
if highRes[0].StringVal <= 0 {
applogger.Error(symbol+" no data", period)
return
}
high := highRes[0].StringVal
sort = bson.M{"stringVal": 1}
lowRes := data.MgoFindProjectionAggregate(tableName, "low_price", filter, projection, sort, 1)
low := lowRes[0].StringVal
sort = bson.M{"timestamp": 1}
openRes := data.MgoFindProjectionAggregate(tableName, "open_price", filter, projection, sort, 1)
open := openRes[0].StringVal
sort = bson.M{"timestamp": -1}
closeRes := data.MgoFindProjectionAggregate(tableName, "close_price", filter, projection, sort, 1)
var dataList []mongo.WriteModel
ts := from
if period == "1day" || period == "1week" || period == "1mon" {
ts = from + CountryStartTime[country]
if country == "Brazil" && (period == "1day" || period == "1mon") {
ts = from
}
}
filter = bson.M{"timestamp": bson.M{"$eq": ts}, "symbol": bson.M{"$eq": symbol}}
update := bson.D{{"$set", bson.D{
{"symbol", closeRes[0].Symbol},
{"stock_code", closeRes[0].StockCode},
{"stock_name", closeRes[0].StockName},
{"open_price", fmt.Sprintf("%f", open)},
{"high_price", fmt.Sprintf("%f", high)},
{"low_price", fmt.Sprintf("%f", low)},
{"close_price", fmt.Sprintf("%f", closeRes[0].StringVal)},
//{"up_down_rate", res[l].UpDownRate},
//{"up_down", res[l].UpDown},
//{"trade_v", res[l].TradeV},
//{"trade_k", res[l].TradeK},
{"vol", closeRes[0].Vol},
{"turnover_price_total", closeRes[0].TurnoverPriceTotal},
{"price_total", closeRes[0].PriceTotal},
//{"p_e", res[l].PE},
//{"eps", res[l].Eps},
//{"employees_number", res[l].EmployeesNumber},
//{"plate", res[l].Plate},
// {"desc", res[l].Desc},
{"price_code", closeRes[0].PriceCode},
{"country", closeRes[0].Country},
{"timestamp", ts},
}}}
//applogger.Info("GetTimeNewPriceAll info: %v", update)
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
if err := data.MgoBulkWrite(data.GetStockSouthAsiaTableName(country, period), dataList); err != nil {
applogger.Error("stock MgoInsertMany err:%v", err)
}
}
func GetStockAll(country string, pageNum, pageSize int64) ([]stock.StockPolygon, int64, int64) {
filter := bson.M{"Country": country, "YesterdayClose": bson.M{"$ne": ""}}
projection := bson.M{"Code": 1, "Country": 1}
res := make([]stock.StockPolygon, 0)
total, _ := data.MgoFindTotal(data.StockList, filter)
data.MgoPagingFindStructProjection(data.StockList, filter, projection, pageSize, pageNum, -1, &res)
//印度股票过多 现在只画后台有权限得股票
//if country == "India" {
// data := make([]stock.StockPolygon, 0)
// for k, v := range res {
// if common.IsExistStock(v.Locale, v.Code) {
// data = append(data, res[k])
// }
// }
// return data, total, int64(math.Ceil(float64(total) / float64(pageSize)))
//}
return res, total, int64(math.Ceil(float64(total) / float64(pageSize)))
}
func getOpen(timestamp int64, country, period, symbol string, price string) string {
filter := bson.M{"stock_code": symbol}
projection := bson.M{"timestamp": 1, "open_price": 1, "close_price": 1}
sort := bson.M{"timestamp": -1}
res, _ := data.MgoFindProjection(data.GetStockSouthAsiaTableName(country, period), filter, projection, sort, int64(1))
open := price
if len(res) > 0 {
timestamps, _ := res[0]["timestamp"].(int64)
switch timestamps {
case timestamp:
open = res[0]["open_price"].(string)
default:
open = res[0]["close_price"].(string)
}
}
return open
}
func DeleteUs() {
data.Mgo_init(config.Config.Mongodb)
red.RedisClient = red.RedisInit(config.Config.Redis.DbEleven)
filter := bson.M{"Country": "US", "YesterdayClose": ""}
projection := bson.M{"Code": 1, "Country": 1}
sort := bson.M{}
result, _ := data.MgoFindProjection(data.StockList, filter, projection, sort, 0)
fmt.Println(len(result))
country := "US"
for _, v := range result {
code := v["Code"].(string)
red.Hset(StockClosingPrice[country], code, "0")
red.Hset(StockClosingPrice[fmt.Sprintf("%sNew", country)], code, "0")
red.Hset(StockClosingPrice[fmt.Sprintf("%sBeforeClose", country)], code, "0")
}
}
// 只保留前半天数据
func DeleteSpot(param string) {
red.RedisClient = red.RedisInit(config.Config.Redis.DbEleven)
times := common.TimeToNows().Add(-10 * time.Minute).UnixMilli()
filter := bson.M{"timestamp": bson.M{"$lte": times}}
for _, country := range dictionary.StockCodeList {
tableName := data.GetStockTableName(common.CapitalizeFirstLetter(country))
if err := data.MgoDeleteMany(tableName, filter); err != nil {
applogger.Error(country, "err :", err.Error())
}
//只清理数据
if param == "true" {
continue
}
for _, v := range dictionary.StockSouthAsiaListTime {
tableNames := data.GetStockSouthAsiaTableName(country, v)
applogger.Debug(tableNames, "start")
timeHour := common.TimeToNows().Add(-48 * time.Hour).UnixMilli()
switch v {
case "15min":
timeHour = common.TimeToNows().Add(-75 * time.Hour).UnixMilli()
case "30min":
timeHour = common.TimeToNows().Add(-75 * time.Hour * 2).UnixMilli()
case "1hour":
timeHour = common.TimeToNows().Add(-7 * time.Hour * 24 * 2).UnixMilli()
case "1day":
timeHour = common.TimeToNows().Add(-365 * time.Hour * 24).UnixMilli()
case "1week":
timeHour = common.TimeToNows().Add(-365 * time.Hour * 24).UnixMilli()
case "1mon":
timeHour = common.TimeToNows().Add(-365 * time.Hour * 24 * 2).UnixMilli()
}
fmt.Println(timeHour)
filter = bson.M{"timestamp": bson.M{"$lte": timeHour}}
if err := data.MgoDeleteMany(tableNames, filter); err != nil {
applogger.Error(country, "err :", err.Error())
}
}
}
applogger.Debug("delete run end")
}
func DeleteSpotDay(times string, ts int64) {
data.Mgo_init(config.Config.Mongodb)
// var stockTime = []string{ "1day", "1week", "1mon"}
stocks, _, _ := GetStockAll("India", 1, 7001)
for _, stock := range stocks {
// for _,times:= range stockTime {
tableNames := data.GetStockSouthAsiaTableName("India", times)
filter := bson.M{"symbol": stock.Code, "timestamp": ts}
projection := bson.M{"stringVal": 1}
sort := bson.M{"stringVal": -1}
highRes := data.MgoFindProjectionAggregate(tableNames, "high_price", filter, projection, sort, 1)
fmt.Printf("%+v", highRes)
if len(highRes) <= 0 {
applogger.Info(times, ts, stock.Code, "id empty ")
continue
}
if highRes[0].ID.String() == "" {
applogger.Info(times, ts, stock.Code, "id kong ")
continue
}
filter = bson.M{"symbol": stock.Code, "timestamp": ts, "_id": bson.M{"$ne": highRes[0].ID}}
if err := data.MgoDeleteMany(tableNames, filter); err != nil {
applogger.Error(times, ts, stock.Code, "err :", err.Error())
}
applogger.Info(times, ts, stock.Code)
// os.Exit(111)
}
}
// 清理外汇成交报价数据信息
func DeleteForexTrade() {
data.Mgo_init(config.Config.Mongodb)
// 当前时间
now := time.Now()
// 时间戳格式化函数
timestamp := func(t time.Time) int64 {
return t.UnixNano() / int64(time.Millisecond)
}
// 计算5分钟前的时间戳
fiveMinutesAgo := now.Add(time.Duration(-3) * time.Minute)
fiveMinutesAgoMillis := timestamp(fiveMinutesAgo)
filter := bson.M{"tick_time": bson.M{"$lte": fiveMinutesAgoMillis}}
if err := data.MgoDeleteMany(data.ForexTradeList, filter); err != nil {
applogger.Error("DeleteForexTrade MgoDeleteMany err :", err.Error())
}
}
// 推送插针数据:[3:美股 4:印尼 5:马股 6:泰股 9:新加坡 11:期权印度 12:港股 14:英国 15:法国 16:德国 17:巴西 18:日本]
func StockClosedData() {
red.RedisClient = red.RedisInit(config.Config.Redis.DbTen)
for k, v := range StockClosedDataList {
// TODO: 盘前数据功能 实行全天更改数据
//if k == "US" && common.IsOpeningUS() {
// applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), k, " it's opening time -----------------------------end")
// continue
//} else if (k == "Thailand" || k == "Indonesia" || k == "India" || k == "Singapore" || k == "Malaysia" || k == "HongKong" || k == "UK" || k == "France" || k == "Germany" || k == "Brazil") && common.IsOpening(k) {
// applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), k, " it's opening time -----------------------------end")
// continue
//}
// TODO: 插针
hashListName := fmt.Sprintf("STOCK_PRICES:%d", v)
keys := red.Scan(hashListName)
stockCodes := make(map[string]bool)
for _, key := range keys {
res, _ := red.HGetAll(key)
status, _ := strconv.Atoi(res["status"])
code := res["stock_code"]
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), res)
if status != StockStatusOn {
continue
}
if k == "US" {
UsStock(code, res["price"], k, true)
} else {
stockCode := common.GetOldCode(code)
SouthAsiaSpot(code, stockCode, res["price"], k, true)
}
stockCodes[code] = true
}
FullPush(stockCodes, k, v)
}
}
// 只全量推送旧数据,对数据不做修改
func FullPush(stockCodes map[string]bool, country string, countryNum int) {
if config.Config.Redis.FullPush != 1 {
return
}
res, _ := red.HGetAll(fmt.Sprintf("STOCK_MARKET:LIST:%d", countryNum))
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), country, res)
status, _ := strconv.Atoi(res["status"])
if status != StockStatusOn {
return
}
if !common.IsPullOpen(country, res["am_open_time"], res["am_close_time"], res["pm_open_time"], res["pm_close_time"]) {
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), country, " it's not pull push time -----------------------------end")
return
}
filter := bson.M{"Country": country, "YesterdayClose": bson.M{"$ne": ""}}
projection := bson.M{"Code": 1, "YesterdayClose": 1}
stockRes := make([]stock.StockPolygon, 0)
data.MgoPagingFindStructProjection(data.StockList, filter, projection, 11000, 1, -1, &stockRes)
for _, v := range stockRes {
if !common.IsExistStock(country, v.Code) || stockCodes[v.Code] {
applogger.Debug(country, v.Code, "not pin code")
continue
}
key := StockClosingPrice[fmt.Sprintf("%sNew", country)]
closePrice, _ := red.Hget(key, v.Code)
if closePrice == "0" || closePrice == "" {
closePrice = v.YesterdayClose
}
if country == "US" {
UsStock(v.Code, closePrice, country, false)
} else {
stockCode := common.GetOldCode(v.Code)
SouthAsiaSpot(v.Code, stockCode, closePrice, country, false)
}
}
}
func CheckNewPrice(code, country, price string) {
key := StockClosingPrice[fmt.Sprintf("%sNew", country)]
value, _ := red.Hget(key, code)
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), code, country, price, value, "CheckNewPrice")
if value != "" && value != "0" && value == price { //value 不是 0 并且 跟盘前价格一致
red.Hset(key, code, "0")
}
}
// 推送目标美股插针数据行情
func UsStock(code, price, country string, isUpdate bool) {
message := &model.ClientMessage{
S: code, // 股票代码
C: []decimal.Decimal{decimal.NewFromInt(0), decimal.NewFromFloat(1)}, // 条件,有关更多信息,请参阅贸易条件术语表
V: common.CalculateContractPrices(decimal.NewFromInt(int64(100)), float64(0.02), 0, 1)[0].IntPart(), // 交易量,代表在相应时间戳处交易的股票数量 -- 报价交易量
Dp: true, // 暗池真/假
Ms: "open", // 市场状态,指示股票市场的当前状态(“开盘”、“收盘”、“延长交易时间”)
T: time.Now().UnixMilli(), // 以毫秒为单位的时间戳 -- 此聚合窗口的结束时钟周期的时间戳(以 Unix 毫秒为单位)
Cl: decimal.RequireFromString(price), // 此聚合窗口的收盘价
A: decimal.NewFromInt(11), // 今天的成交量加权平均价格
Se: time.Now().UnixMilli(),
H: decimal.RequireFromString(price), // 此聚合窗口的最高逐笔报价
L: decimal.RequireFromString(price), // 此聚合窗口的最低价格变动价格
Op: decimal.RequireFromString(price), // 今天正式开盘价格
// P: decimal.RequireFromString(price),
ClosingMarket: true,
}
msgStr, err := json.Marshal(message)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
return
}
if isUpdate {
red.Hset(StockClosingPrice[fmt.Sprintf("%sNew", country)], code, price)
}
applogger.Info("last date info: %v", string(msgStr))
red.RedisClient.Publish(fmt.Sprintf("%s.US", message.S), string(msgStr))
}
// 推送目标市场股票插针数据行情
func SouthAsiaSpot(symbol, stockCode, price, country string, isUpdate bool) {
prices, _ := strconv.ParseFloat(price, 64)
param := model.StockParam{
Symbol: symbol,
StockCode: stockCode,
StockName: "",
Price: prices,
UpDownRate: decimal.NewFromInt(0),
UpDown: decimal.NewFromInt(0),
TradeV: decimal.NewFromInt(0),
TradeK: "买入",
Country: strings.ToLower(country),
Ts: time.Now().UnixMilli(),
ClosingMarket: true,
}
param.Token = ""
msgStr, err := json.Marshal(param)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
return
}
applogger.Info("last date info: %v", string(msgStr))
// Write to Redis for broadcasting
red.RedisClient.Publish(fmt.Sprintf("%s.%s", param.Symbol, country), string(msgStr))
if isUpdate {
red.Hset(StockClosingPrice[fmt.Sprintf("%sNew", country)], symbol, price)
}
}
// 东南亚聚合行情
func TickSouthAsiaSpotKline(stockName string) {
if !common.IsOpening(stockName) {
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), "it's not opening time -----------------------------end")
return
}
start := time.Now() // 获取当前时间
applogger.Info(common.TimeToNows().Format("2006-01-02 15:04:05"), "start TickSouthAsiaSpotKline")
// 五分钟聚合
FiveMinTo := common.GenerateSingaporeFiveMinTimestampOrigin() * 1000
FiveMinFrom := FiveMinTo - int64(5*60*1000)
// 十五分钟聚合
FifteenMinTo := common.GenerateSingaporeFifteenMinTimestampOrigin() * 1000
FifteenMinFrom := FifteenMinTo - int64(15*60*1000)
// 三十分钟聚合
ThirtyMinTo := common.GenerateSingaporeThirtyMinTimestampOrigin() * 1000
ThirtyMinToForm := ThirtyMinTo - int64(30*60*1000)
// 小时聚合
HourFrom := common.GenerateSingaporeHourTimestampOrigin() * 1000
HourTo := HourFrom + int64(60*60*1000)
// 天聚合
DayFrom := common.GenerateSingaporeDayTimestamp(stockName) * 1000
DayTo := common.TimeToNow() * 1000
// 周聚合
WeekFrom := common.GetWeekTimestamp() * 1000
WeekTo := common.TimeToNow() * 1000
// 月聚合
MonFrom := common.GenerateSingaporeMonTimestampStock(stockName) * 1000
MonTo := common.TimeToNow() * 1000
//并发过高 拆分
stocks, _, pageTotal := GetStockAll(stockName, 1, PageSize)
wg := sync.WaitGroup{}
for _, value := range stocks {
wg.Add(1)
go func(FiveMinTo, FiveMinFrom, FifteenMinTo, FifteenMinFrom, ThirtyMinTo, ThirtyMinToForm, HourFrom, HourTo, DayFrom, DayTo, WeekFrom, WeekTo, MonFrom, MonTo int64, value stock.StockPolygon) {
defer wg.Done()
applogger.Info(common.TimeToNows().Format("2006-01-02 15:04:05"), "start ", value.Code, value.Locale)
if err := GetTimeNewPrice(value.Code, FiveMinFrom, FiveMinTo, value.Locale, "5min"); err != nil {
applogger.Error(err.Error(), "run end")
return
}
GetTimeNewPriceAll(value.Code, FifteenMinFrom, FifteenMinTo, value.Locale, "15min", "5min")
GetTimeNewPriceAll(value.Code, ThirtyMinToForm, ThirtyMinTo, value.Locale, "30min", "15min")
GetTimeNewPriceAll(value.Code, HourFrom, HourTo, value.Locale, "1hour", "30min")
GetTimeNewPriceAll(value.Code, DayFrom, DayTo, value.Locale, "1day", "1hour")
GetTimeNewPriceAll(value.Code, WeekFrom, WeekTo, value.Locale, "1week", "1day")
GetTimeNewPriceAll(value.Code, MonFrom, MonTo, value.Locale, "1mon", "1week")
}(FiveMinTo, FiveMinFrom, FifteenMinTo, FifteenMinFrom, ThirtyMinTo, ThirtyMinToForm, HourFrom, HourTo, DayFrom, DayTo, WeekFrom, WeekTo, MonFrom, MonTo, value)
}
wg.Wait()
for i := int64(2); i <= pageTotal; i++ {
stocks, _, _ := GetStockAll(stockName, i, PageSize)
wg := sync.WaitGroup{}
for _, value := range stocks {
wg.Add(1)
go func(FiveMinTo, FiveMinFrom, FifteenMinTo, FifteenMinFrom, ThirtyMinTo, ThirtyMinToForm, HourFrom, HourTo, DayFrom, DayTo, WeekFrom, WeekTo, MonFrom, MonTo int64, value stock.StockPolygon) {
defer wg.Done()
applogger.Info(common.TimeToNows().Format("2006-01-02 15:04:05"), "start ", value.Code, value.Locale)
if err := GetTimeNewPrice(value.Code, FiveMinFrom, FiveMinTo, value.Locale, "5min"); err != nil {
applogger.Error(err.Error(), "run end")
return
}
GetTimeNewPriceAll(value.Code, FifteenMinFrom, FifteenMinTo, value.Locale, "15min", "5min")
GetTimeNewPriceAll(value.Code, ThirtyMinToForm, ThirtyMinTo, value.Locale, "30min", "15min")
GetTimeNewPriceAll(value.Code, HourFrom, HourTo, value.Locale, "1hour", "30min")
GetTimeNewPriceAll(value.Code, DayFrom, DayTo, value.Locale, "1day", "1hour")
GetTimeNewPriceAll(value.Code, WeekFrom, WeekTo, value.Locale, "1week", "1day")
GetTimeNewPriceAll(value.Code, MonFrom, MonTo, value.Locale, "1mon", "1week")
}(FiveMinTo, FiveMinFrom, FifteenMinTo, FifteenMinFrom, ThirtyMinTo, ThirtyMinToForm, HourFrom, HourTo, DayFrom, DayTo, WeekFrom, WeekTo, MonFrom, MonTo, value)
}
wg.Wait()
}
fmt.Println("Run time: ", time.Since(start))
// TODO: 删除已画好的数据,提高查询速度
filter := bson.M{"timestamp": bson.M{"$lte": FiveMinTo}}
tableName := data.GetStockTableName(stockName)
data.MgoDeleteMany(tableName, filter)
applogger.Info(tableName, FiveMinTo, "DELETE INFO")
}
// 指数数据聚合
func TickSpotIndexKline() {
start := time.Now() // 获取当前时间
applogger.Info(common.TimeToNows().Format("2006-01-02 15:04:05"), "start TickSpotIndexKline")
// 五分钟聚合
FiveMinTo := common.GenerateSingaporeFiveMinTimestampOrigin() * 1000
FiveMinFrom := FiveMinTo - int64(5*60*1000)
// 十五分钟聚合
FifteenMinTo := common.GenerateSingaporeFifteenMinTimestampOrigin() * 1000
FifteenMinFrom := FifteenMinTo - int64(15*60*1000)
// 三十分钟聚合
ThirtyMinTo := common.GenerateSingaporeThirtyMinTimestampOrigin() * 1000
ThirtyMinToForm := ThirtyMinTo - int64(30*60*1000)
// 小时聚合
HourFrom := common.GenerateSingaporeHourTimestampOrigin() * 1000
HourTo := HourFrom + int64(60*60*1000)
// 天聚合
DayFrom := common.GenerateSingaporeDayTimestamp("") * 1000
DayTo := common.TimeToNow() * 1000
// 周聚合
WeekFrom := common.GetWeekTimestamp() * 1000
WeekTo := common.TimeToNow() * 1000
// 月聚合
MonFrom := common.GenerateSingaporeMonTimestampStock("") * 1000
MonTo := common.TimeToNow() * 1000
//并发过高 拆分
filter := bson.M{"State": common.StockIndexOn}
res := make([]stock.StockIndexPolygon, 0)
data.MgoFindStockRes(data.StockIndexList, filter, &res)
wg := sync.WaitGroup{}
for _, value := range res {
wg.Add(1)
go func(FiveMinTo, FiveMinFrom, FifteenMinTo, FifteenMinFrom, ThirtyMinTo, ThirtyMinToForm, HourFrom, HourTo, DayFrom, DayTo, WeekFrom, WeekTo, MonFrom, MonTo int64, value stock.StockIndexPolygon) {
defer wg.Done()
applogger.Info(common.TimeToNows().Format("2006-01-02 15:04:05"), "start ", value.Code, value.Locale)
if err := GetTimeNewIndexPrice(value.Code, FiveMinFrom, FiveMinTo, value.Locale, "5min"); err != nil {
applogger.Error(err.Error(), "run end")
return
}
GetTimeNewIndexPriceAll(value.Code, FifteenMinFrom, FifteenMinTo, value.Locale, "15min", "5min")
GetTimeNewIndexPriceAll(value.Code, ThirtyMinToForm, ThirtyMinTo, value.Locale, "30min", "15min")
GetTimeNewIndexPriceAll(value.Code, HourFrom, HourTo, value.Locale, "1hour", "30min")
GetTimeNewIndexPriceAll(value.Code, DayFrom, DayTo, value.Locale, "1day", "1hour")
GetTimeNewIndexPriceAll(value.Code, WeekFrom, WeekTo, value.Locale, "1week", "1day")
GetTimeNewIndexPriceAll(value.Code, MonFrom, MonTo, value.Locale, "1mon", "1week")
}(FiveMinTo, FiveMinFrom, FifteenMinTo, FifteenMinFrom, ThirtyMinTo, ThirtyMinToForm, HourFrom, HourTo, DayFrom, DayTo, WeekFrom, WeekTo, MonFrom, MonTo, value)
}
wg.Wait()
fmt.Println("Run time: ", time.Since(start))
}
func GetTimeNewIndexPrice(symbol string, from, to int64, country, period string) error {
//fmt.Println("country", country, "period", period, "from", common.ConvertToTimeStr(from/1000), from, "to", common.ConvertToTimeStr(to/1000), to)
filter := bson.M{"stock_code": symbol, "timestamp": bson.M{"$gte": from, "$lte": to}}
tableName := data.GetStockIndexTableName()
highRes := make([]model.StockIndexParam, 0)
projection := bson.M{"price": 1}
sort := bson.M{"price": -1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &highRes, 1)
if len(highRes) <= 0 {
applogger.Error(symbol+" no data", period)
return errors.New(symbol + " no data")
}
high := highRes[0].Price
lowRes := make([]model.StockIndexParam, 0)
sort = bson.M{"price": 1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &lowRes, 1)
low := lowRes[0].Price
openRes := make([]model.StockIndexParam, 0)
sort = bson.M{"timestamp": 1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &openRes, 1)
open := openRes[0].Price
closeRes := make([]model.StockIndexParam, 0)
projection = bson.M{}
sort = bson.M{"timestamp": -1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &closeRes, 1)
var dataList []mongo.WriteModel
filter = bson.M{"timestamp": bson.M{"$eq": from}, "stock_code": bson.M{"$eq": symbol}}
update := bson.D{{"$set", bson.D{
{"stock_code", closeRes[0].StockCode},
{"stock_name", closeRes[0].StockName},
{"open_price", fmt.Sprintf("%f", open)},
{"high_price", fmt.Sprintf("%f", high)},
{"low_price", fmt.Sprintf("%f", low)},
{"close_price", fmt.Sprintf("%f", closeRes[0].Price)},
{"up_down_rate", closeRes[0].UpDownRate},
{"up_down", closeRes[0].UpDown},
{"vol", closeRes[0].Vol},
{"country", closeRes[0].Country},
{"timestamp", from},
}}}
applogger.Info("GetTimeNewPrice info: %v", update)
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
if err := data.MgoBulkWrite(data.GetStockIndixKlineTableName(period), dataList); err != nil {
applogger.Error("stock MgoInsertMany err:%v", err)
return err
}
return nil
}
func GetTimeNewIndexPriceAll(symbol string, from, to int64, country, period, periodPre string) {
filter := bson.M{"stock_code": symbol, "timestamp": bson.M{"$gte": from, "$lte": to}}
tableName := data.GetStockIndixKlineTableName(periodPre)
res := make([]model.StockMogoParam, 0)
projection := bson.M{"symbol": 1, "stock_code": 1, "stock_name": 1, "open_price": 1, "high_price": 1, "low_price": 1, "close_price": 1, "vol": 1, "country": 1, "timestamp": 1}
sort := bson.M{"timestamp": 1}
data.MgoFindProjectionRes(tableName, filter, projection, sort, &res, 0)
if len(res) <= 0 {
applogger.Error(symbol+" no data", period)
return
}
var low, high, vol decimal.Decimal
for key, v := range res {
lows, _ := decimal.NewFromString(v.LowPrice)
highs, _ := decimal.NewFromString(v.HighPrice)
var vols decimal.Decimal
if key == 0 {
low = lows
high = highs
vol = vols
continue
}
vol = vol.Add(vols)
if low.GreaterThan(lows) {
low = lows
}
if high.LessThan(highs) {
high = highs
}
}
l := len(res) - 1
var dataList []mongo.WriteModel
ts := from
open := res[0].OpenPrice
opens, _ := decimal.NewFromString(open)
if low.GreaterThan(opens) && !opens.Equal(decimal.NewFromInt(0)) {
low = opens
}
if high.LessThan(opens) {
high = opens
}
filter = bson.M{"timestamp": bson.M{"$eq": ts}, "stock_code": bson.M{"$eq": symbol}}
update := bson.D{{"$set", bson.D{
{"symbol", res[l].Symbol},
{"stock_code", res[l].StockCode},
{"stock_name", res[l].StockName},
{"open_price", open},
{"high_price", high.String()},
{"low_price", low.String()},
{"close_price", res[l].ClosePrice},
{"vol", res[l].Vol},
{"country", res[l].Country},
{"timestamp", ts},
}}}
applogger.Info("GetTimeNewPriceAll info: %v", update)
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
if err := data.MgoBulkWrite(data.GetStockIndixKlineTableName(period), dataList); err != nil {
applogger.Error("stock MgoInsertMany err:%v", err)
}
}
// 控制插针数据不推送
func NewPinStock(noPin map[string]bool) {
for {
fmt.Println(noPin)
pinStockMutex.Lock()
for k, v := range StockClosedDataList {
hashListName := fmt.Sprintf("STOCK_PRICES:%d", v)
dbs := red.ScanMap(hashListName)
dbData := make(map[string]map[string]bool)
for db, keys := range dbs {
//不用插针服务
if noPin[db] {
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), db, "no pin 不用插针")
continue
}
stockCode := make(map[string]bool)
for _, key := range keys {
res, _ := red.HGetAllMap(db, key)
applogger.Debug(common.TimeToNows().Format("2006-01-02 15:04:05"), db, res)
status, _ := strconv.Atoi(res["status"])
code := res["stock_code"]
if status == StockStatusOn {
stockCode[code] = true
}
}
dbData[db] = stockCode
}
pinStock[k] = dbData
}
pinStockMutex.Unlock()
applogger.Info("pin stock :%v", pinStock)
time.Sleep(1 * time.Minute)
}
}
func getPinStock(db, country string) map[string]bool {
pinStockMutex.RLock()
defer pinStockMutex.RUnlock()
return pinStock[country][db]
}
func JudgePublishMap(country, code, channel string, message interface{}) {
for k, db := range red.RedisClientMap {
if getPinStock(k, country)[code] {
applogger.Debug(k, code, "pin stock", message)
continue
}
applogger.Info("channel", channel, "DB", k)
err := db.Publish(channel, message).Err()
if err != nil {
fmt.Println("db", k, "存储失败:", err)
}
}
}
func JudgeHsetMap(country, key, field string, value interface{}) {
for k, db := range red.RedisClientMap {
if getPinStock(k, country)[field] {
applogger.Debug(k, field, "pin stock", value)
continue
}
applogger.Info("key", key, "field", field, "value", value, "DB", k)
err := db.HSet(key, field, value).Err()
if err != nil {
fmt.Println("db", k, "存储失败:", err)
}
}
}
func StockWs(param model.StockParam, country string) {
param.Token = ""
msgStr, err := json.Marshal(param)
if err != nil {
applogger.Error("json.Marshal err: %v", err)
return
}
//applogger.Info("last date info: %v", string(msgStr))
// Write to Redis for broadcasting
JudgePublishMap(country, param.Symbol, fmt.Sprintf("%s.%s", param.Symbol, country), string(msgStr))
}

1175
internal/data/business/mgolistdataus.go

File diff suppressed because it is too large

638
internal/data/business/mgolistdatemys.go

@ -0,0 +1,638 @@
package business
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go/types"
"strings"
"time"
"wss-pool/config"
"wss-pool/dictionary"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/market"
"wss-pool/pkg/model/stock"
)
const (
MaxLimit int = 100
)
type SpotKlineResInfo struct {
ID int64 `json:"id"`
Open decimal.Decimal `json:"open"`
Close decimal.Decimal `json:"close"`
Low decimal.Decimal `json:"low"`
High decimal.Decimal `json:"high"`
Amount decimal.Decimal `json:"amount"`
Vol decimal.Decimal `json:"vol"`
Count decimal.Decimal `json:"count"`
TradeTurnover string `json:"trade_turnover"`
}
type LinearlineResInfo struct {
ID int64 `json:"id"`
Open decimal.Decimal `json:"open"`
Close decimal.Decimal `json:"close"`
Low decimal.Decimal `json:"low"`
High decimal.Decimal `json:"high"`
Amount decimal.Decimal `json:"amount"`
Vol decimal.Decimal `json:"vol"`
Count decimal.Decimal `json:"count"`
TradeTurnover decimal.Decimal `json:"trade_turnover"`
}
type LinearKlineRes struct {
Ch string `json:"ch"`
Status string `json:"status"`
Ts int64 `json:"ts"`
Data []LinearlineResInfo `json:"data"`
}
type SpotKlineWsRes struct {
Ch string `json:"ch"`
Status string `json:"status"`
Ts int64 `json:"ts"`
Tick SpotKlineResInfo `json:"tick"`
}
type SpotKlineRes struct {
Ch string `json:"ch"`
Status string `json:"status"`
Ts int64 `json:"ts"`
Data []SpotKlineResInfo `json:"data"`
}
type ContractKlineRes struct {
Ch string `json:"ch"`
Status string `json:"status"`
Ts int64 `json:"ts"`
Data []LinearlineResInfo `json:"data"`
}
type MarketTradeIDInfo struct {
ID int64 `json:"id"`
Ts int64 `json:"ts"`
TradeId int64 `json:"trade-id"`
Amount float64 `json:"amount"`
Price float64 `json:"price"`
Direction string `json:"direction"`
TradeTurnover decimal.Decimal `json:"trade_turnover"`
}
type MarketTradeID struct {
ID int64 `json:"id"`
Ts int64 `json:"ts"`
Data []MarketTradeIDInfo `json:"data"`
}
type MarketTrade struct {
Ch string `json:"ch"`
Status string `json:"status"`
Ts int64 `json:"ts"`
Data []MarketTradeID `json:"data"`
}
// UpdateStockKLSE Daily update of stock market list data【ws.eodhistoricaldata.com】
func UpdateStockKLSE() {
var dataList []mongo.WriteModel
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
before := time.Now().AddDate(0, 0, -2).Format("2006-01-02")
filter := bson.M{"Country": "Malaysia", "YesterdayClose": bson.M{"$ne": ""}, "BeforeClose": bson.M{"$ne": ""}}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
for _, value := range dateList.([]primitive.M) {
code := TypeCheck(value["Code"])
yesterdayClose := TypeCheck(value["YesterdayClose"])
beforeClose := TypeCheck(value["BeforeClose"])
eodModel, _ := ShareData(code, "KLSE", yesterday, before)
for _, eo := range eodModel {
switch eo.Date {
case yesterday:
yesterdayClose = eo.Close.String()
case before:
beforeClose = eo.Close.String()
default:
}
}
applogger.Debug("data info:%v-----%v-----%v", code, yesterdayClose, beforeClose)
filterD := bson.D{{"Code", bson.M{
"$eq": code,
}}}
updateData := bson.M{
"$set": bson.M{
"Code": code,
"YesterdayClose": yesterdayClose,
"BeforeClose": beforeClose}}
models := mongo.NewUpdateOneModel().SetFilter(filterD).SetUpdate(updateData).SetUpsert(true)
dataList = append(dataList, models)
}
applogger.Debug("update data info:%v", dataList)
if len(dataList) > 0 {
if err := data.MgoBulkWrite(data.StockList, dataList); err != nil {
applogger.Error("MgoBulkWrite update err:%v", err)
return
}
}
}
// U本位数据
func UpdateContractKline(period string) {
for _, val := range dictionary.ContractCodeList {
applogger.Info("UpdateContractKline")
result := LinearKlineRes{}
bodyStr, err := internal.HttpGet(fmt.Sprintf("https://api.hbdm.com/linear-swap-ex/market/history/kline?contract_code=%s&period=%s&size=2000", val, period))
applogger.Info(bodyStr)
if err != nil {
applogger.Error("Failed to query data:%v", err)
continue
}
if err = json.Unmarshal([]byte(bodyStr), &result); err != nil {
applogger.Error("Unmarshal err: %v---%v", err)
continue
}
var dataList []mongo.WriteModel
for _, eodValue := range result.Data {
open := eodValue.Open.String()
high := eodValue.High.String()
low := eodValue.Low.String()
close := eodValue.Close.String()
vol := eodValue.Vol.String()
amount := eodValue.Amount.String()
count := eodValue.Count.String()
tradeTurnover := eodValue.TradeTurnover.String()
//applogger.Info("data: ", eodValue)
filter := bson.M{"code": bson.M{"$eq": eodValue.ID}, "channel": bson.M{"$eq": result.Ch}}
update := bson.D{{"$set", bson.D{
{"channel", result.Ch},
{"timestamp", eodValue.ID},
{"code", eodValue.ID},
{"open", open},
{"high", high},
{"low", low},
{"close", close},
{"vol", vol},
{"amount", amount},
{"count", count},
{"trade_turnover", tradeTurnover},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
if len(dataList) > 0 {
if err := data.MgoBulkWrite(data.GetContractKLineTableName(period), dataList); err != nil {
applogger.Error("ContractKline MgoInsertMany err:%v", err)
}
}
}
}
// 现货
func UpdateSpotKline(period string) {
for _, val := range dictionary.Symbol {
result := SpotKlineRes{}
bodyStr, err := internal.HttpGet(fmt.Sprintf("https://api.huobi.pro/market/history/kline?period=%s&size=2000&symbol=%susdt", period, val))
if err != nil {
applogger.Error("Failed to query data:%v", err)
time.Sleep(2 * time.Second)
continue
}
if err = json.Unmarshal([]byte(bodyStr), &result); err != nil {
applogger.Error("Unmarshal err: %v---%v", err)
continue
}
var dataList []mongo.WriteModel
for _, eodValue := range result.Data {
open := eodValue.Open.String()
high := eodValue.High.String()
low := eodValue.Low.String()
close := eodValue.Close.String()
vol := eodValue.Vol.String()
amount := eodValue.Amount.String()
count := eodValue.Count.String()
applogger.Info("data: ", eodValue)
filter := bson.M{"code": bson.M{"$eq": eodValue.ID}, "channel": bson.M{"$eq": result.Ch}}
update := bson.D{{"$set", bson.D{
{"channel", result.Ch},
{"timestamp", eodValue.ID},
{"code", eodValue.ID},
{"open", open},
{"high", high},
{"low", low},
{"close", close},
{"vol", vol},
{"amount", amount},
{"count", count},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
if len(dataList) > 0 {
if err := data.MgoBulkWrite(data.GetStockKLineTableName(period), dataList); err != nil {
applogger.Error(" SpotKline MgoInsertMany err:%v", err)
}
}
}
}
func UpdatePriceKline(period string) {
for _, val := range dictionary.ContractCodeList {
result := SpotKlineRes{}
bodyStr, err := internal.HttpGet(fmt.Sprintf("https://api.hbdm.com/index/market/history/linear_swap_mark_price_kline?contract_code=%s&period=%s&size=2000", val, period))
if err != nil {
applogger.Error("Failed to query data:%v", err)
time.Sleep(2 * time.Second)
continue
}
if err = json.Unmarshal([]byte(bodyStr), &result); err != nil {
applogger.Error("Unmarshal err: %v---%v", err)
continue
}
var dataList []mongo.WriteModel
for _, eodValue := range result.Data {
open := eodValue.Open.String()
high := eodValue.High.String()
low := eodValue.Low.String()
close := eodValue.Close.String()
vol := eodValue.Vol.String()
amount := eodValue.Amount.String()
count := eodValue.Count.String()
applogger.Info("data: ", eodValue)
filter := bson.M{"code": bson.M{"$eq": eodValue.ID}, "channel": bson.M{"$eq": result.Ch}}
update := bson.D{{"$set", bson.D{
{"channel", result.Ch},
{"timestamp", eodValue.ID},
{"code", eodValue.ID},
{"open", open},
{"high", high},
{"low", low},
{"close", close},
{"vol", vol},
{"amount", amount},
{"count", count},
{"trade_turnover", eodValue.TradeTurnover},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
if len(dataList) > 0 {
if err := data.MgoBulkWrite(data.GetContractPriceKLineTableName(period), dataList); err != nil {
applogger.Error("PriceKline info err:%v", err)
}
}
}
}
// 现货实时入库
func UpdateWsMgo(result market.SubscribeCandlestickResponse) {
if result.Tick.Id <= 0 {
applogger.Error("ws data is null %v", result)
return
}
var dataList []mongo.WriteModel
open := result.Tick.Open.String()
high := result.Tick.High.String()
low := result.Tick.Low.String()
close := result.Tick.Close.String()
vol := result.Tick.Vol.String()
amount := result.Tick.Amount.String()
//applogger.Info("data: ", result.Tick)
filter := bson.M{"code": bson.M{"$eq": result.Tick.Id}, "channel": bson.M{"$eq": result.Channel}}
update := bson.D{{"$set", bson.D{
{"channel", result.Channel},
{"timestamp", result.Tick.Id},
{"code", result.Tick.Id},
{"open", open},
{"high", high},
{"low", low},
{"close", close},
{"vol", vol},
{"amount", amount},
{"is_ba", result.Tick.IsBa},
{"count", result.Tick.Count},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
ch := strings.Split(result.Channel, ".")
if len(ch) == 0 {
applogger.Error("ch is null")
return
}
period := ch[len(ch)-1]
tableName := data.GetStockKLineTableName(period)
if tableName == "" {
applogger.Error("table info is null")
return
}
if len(dataList) > 0 {
if err := data.MgoBulkWrite(tableName, dataList); err != nil {
applogger.Error("ContractKline MgoInsertMany err:%v", err)
}
}
}
// 现货实时入库 测试
func UpdateWsMgoTest(result market.SubscribeCandlestickResponse) {
if result.Tick.Id <= 0 {
applogger.Error("ws data is null %v", result)
return
}
var dataList []mongo.WriteModel
open := result.Tick.Open.String()
high := result.Tick.High.String()
low := result.Tick.Low.String()
close := result.Tick.Close.String()
vol := result.Tick.Vol.String()
amount := result.Tick.Amount.String()
applogger.Info("data: ", result.Tick)
filter := bson.M{"code": bson.M{"$eq": result.Tick.Id}, "channel": bson.M{"$eq": result.Channel}}
update := bson.D{{"$set", bson.D{
{"channel", result.Channel},
{"timestamp", result.Tick.Id},
{"code", result.Tick.Id},
{"open", open},
{"high", high},
{"low", low},
{"close", close},
{"vol", vol},
{"amount", amount},
{"is_ba", result.Tick.IsBa},
{"count", result.Tick.Count},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
ch := strings.Split(result.Channel, ".")
if len(ch) == 0 {
applogger.Error("ch is null")
return
}
period := ch[len(ch)-1]
tableName := data.GetStockKLineTestTableName(period)
if tableName == "" {
applogger.Error("table info is null")
return
}
if len(dataList) > 0 {
if err := data.MgoBulkWrite(tableName, dataList); err != nil {
applogger.Error("ContractKline MgoInsertMany err:%v", err)
}
}
}
// 合约测试
func UpdateSubscribeCtKlineTest(result market.SubscribeCtKlineResponse) {
if result.Tick.Id <= 0 {
applogger.Error("ws data is null %v", result)
return
}
var dataList []mongo.WriteModel
open := result.Tick.Open.String()
high := result.Tick.High.String()
low := result.Tick.Low.String()
close := result.Tick.Close.String()
vol := result.Tick.Vol.String()
amount := result.Tick.Amount.String()
applogger.Info("data: ", result.Tick)
filter := bson.M{"code": bson.M{"$eq": result.Tick.Id}, "channel": bson.M{"$eq": result.Channel}}
update := bson.D{{"$set", bson.D{
{"channel", result.Channel},
{"timestamp", result.Tick.Id},
{"code", result.Tick.Id},
{"open", open},
{"high", high},
{"low", low},
{"close", close},
{"vol", vol},
{"amount", amount},
{"count", result.Tick.Count.String()},
{"trade_turnover", result.Tick.Rrade_Turnover.String()},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
ch := strings.Split(result.Channel, ".")
if len(ch) == 0 {
applogger.Error("ch is null")
return
}
period := ch[len(ch)-1]
tableName := data.GetContractKLineTestTableName(period)
if tableName == "" {
applogger.Error("table info is null")
return
}
if len(dataList) > 0 {
if err := data.MgoBulkWrite(tableName, dataList); err != nil {
applogger.Error("ContractKline MgoInsertMany err:%v", err)
}
}
}
// 合约实时入库
func UpdateSubscribeCtKline(result market.SubscribeCtKlineResponse) {
if result.Tick.Id <= 0 {
applogger.Error("ws data is null %v", result)
return
}
//fmt.Println("kline ",result)
var dataList []mongo.WriteModel
open := result.Tick.Open.String()
high := result.Tick.High.String()
low := result.Tick.Low.String()
close := result.Tick.Close.String()
vol := result.Tick.Vol.String()
amount := result.Tick.Amount.String()
//applogger.Info("data: ", result.Tick)
filter := bson.M{"code": bson.M{"$eq": result.Tick.Id}, "channel": bson.M{"$eq": result.Channel}}
update := bson.D{{"$set", bson.D{
{"channel", result.Channel},
{"timestamp", result.Tick.Id},
{"code", result.Tick.Id},
{"open", open},
{"high", high},
{"low", low},
{"close", close},
{"vol", vol},
{"amount", amount},
{"count", result.Tick.Count.String()},
{"trade_turnover", result.Tick.Rrade_Turnover.String()},
}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
ch := strings.Split(result.Channel, ".")
if len(ch) == 0 {
applogger.Error("ch is null")
return
}
period := ch[len(ch)-1]
tableName := data.GetContractKLineTableName(period)
if tableName == "" {
applogger.Error("table info is null")
return
}
if len(dataList) > 0 {
if err := data.MgoBulkWrite(tableName, dataList); err != nil {
applogger.Error("ContractKline MgoInsertMany err:%v", err)
}
}
}
// ShareData Obtaining the closing price of Malaysian stocks through time【ws.eodhistoricaldata.com】
func ShareData(code, exchange, yesterday, before string) ([]stock.EodTimeMessage, error) {
model := fmt.Sprintf("%v.%v", code, exchange)
from := before
to := yesterday
urlHttp := fmt.Sprintf("https://%v/api/eod/%v?api_token=%v&fmt=json&period=D&order=d&from=%v&to=%v",
config.Config.ShareGather.FinancialHost, model, config.Config.ShareGather.FinancialKey, from, to)
//applogger.Debug("UrlHttp info: %v", urlHttp)
bodyStr, err := internal.HttpGet(urlHttp)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return nil, err
}
var eodModel []stock.EodTimeMessage
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("Unmarshal err: %v---%v", code, err)
return nil, err
}
return eodModel, nil
}
// 获取给定时间段内该代码的收盘价
func PreviousClose(code string) (stock.PreviousCloseResponse, error) {
var eodModel stock.PreviousCloseResponse
url := fmt.Sprintf("https://%s/v2/aggs/ticker/%s/prev?adjusted=true&apiKey=%s", config.Config.ShareGather.PolygonHost, code, config.Config.ShareGather.PolygonKey)
applogger.Debug("UrlHttp info: %v", url)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return eodModel, err
}
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("Unmarshal err: %v---%v", code, err)
return eodModel, err
}
return eodModel, nil
}
// 通过交易查询股票对应的交易所
func TradesTape(code string) (stock.TypeTradesResponse, error) {
var eodModel stock.TypeTradesResponse
url := fmt.Sprintf("https://%s/v3/trades/%s?apiKey=%s&limit=1", config.Config.ShareGather.PolygonHost, code, config.Config.ShareGather.PolygonKey)
applogger.Debug("UrlHttp info: %v", url)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return eodModel, err
}
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("Unmarshal err: %v---%v", code, err)
return eodModel, err
}
return eodModel, nil
}
// TypeCheck Character determination
func TypeCheck(m interface{}) string {
switch m.(type) {
case types.Nil:
return ""
case string:
return m.(string)
default:
return ""
}
}
// UpdateStockKLSEBak 马股列表闭盘数据更新--已废弃
func UpdateStockKLSEBak() {
var dataList []mongo.WriteModel
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
before := time.Now().AddDate(0, 0, -2).Format("2006-01-02")
// TODO: 更改为从mongoDB中查数据源
var shareModel []stock.StockShare
url := fmt.Sprintf("https://%v/api/exchange-symbol-list/KLSE?api_token=%v&fmt=json", config.Config.ShareGather.FinancialHost, config.Config.ShareGather.FinancialKey)
applogger.Debug("url info:%v", url)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
if err := json.Unmarshal([]byte(bodyStr), &shareModel); err != nil {
applogger.Error("Failed to parse stock list information:%v", err)
return
}
// 赋值更新的数据
for _, value := range shareModel {
applogger.Debug("share types info: %v", value)
model := fmt.Sprintf("%v.KLSE", value.Code)
from := before
to := yesterday
urlHttp := fmt.Sprintf("https://%v/api/eod/%v?api_token=%v&fmt=json&period=D&order=d&from=%v&to=%v",
config.Config.ShareGather.FinancialHost, model, config.Config.ShareGather.FinancialKey, from, to)
applogger.Debug("UrlHttp info: %v", urlHttp)
bodyStr, err = internal.HttpGet(urlHttp)
if err != nil {
applogger.Error("Failed to query data:%v", err)
}
var eodModel []stock.EodTimeMessage
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("Unmarshal err: %v---%v", value.Code, err)
}
for _, eo := range eodModel {
switch eo.Date {
case yesterday:
value.YesterdayClose = eo.Close.String()
case before:
value.BeforeClose = eo.Close.String()
default:
}
}
filter := bson.D{{"Code", bson.M{
"$eq": value.Code,
}}}
update := bson.D{{"$set", bson.D{
{"Code", value.Code},
{"Name", value.Name},
{"Country", value.Country},
{"Exchange", value.Exchange},
{"Currency", value.Currency},
{"Type", value.Type},
{"Isin", value.Isin},
{"YesterdayClose", value.YesterdayClose},
{"BeforeClose", value.BeforeClose}}}}
models := mongo.NewUpdateOneModel().SetFilter(filter).SetUpdate(update).SetUpsert(true)
dataList = append(dataList, models)
}
applogger.Debug("update data info:%v", dataList)
if len(dataList) > 0 {
if err := data.MgoBulkWrite(data.StockList, dataList); err != nil {
applogger.Error("MgoBulkWrite update err:%v", err)
return
}
}
}

67
internal/data/business/mgomanager.go

@ -0,0 +1,67 @@
package business
import (
"wss-pool/logging/applogger"
)
func RunHBData(checkData string) {
HbMarketSpots(checkData)
HbContract(checkData)
ShareMarket(checkData)
}
// 现货行情数据
func HbMarketSpots(checkData string) {
switch checkData {
case "subscribeDepth": // 市场深度行情数据 8863
MgoSubscribeDepth()
case "subscribeLevelMbp": // 市场深度MBP行情数据(增量推送)(150挡) 8864
MgoSubscribeLevelMbp()
case "subscribeFullMbp": // 市场深度MBP行情数据(全量推送) 8865
MgoSubscribeFullMbp()
case "subscribeSubMbp": // 市场深度MBP行情数据(增量推送) 8866
MgoSubscribeSubMbp()
case "subscribeBbo": // 买一卖一逐笔行情 8867
MgoSubscribeBbo()
case "subscribeKLine": // K线数据 8868
MgoSubscribeKLine()
case "subscribeTrade": // 成交明细 8869
MgoSubscribeTrade()
case "subscribeLast24h": // 市场概要 8847
MgoSubscribeLast24h()
case "subscribeTicker": // 聚合行情(Ticker) 8848
MgoSubscribeTicker()
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 合约行情数据
func HbContract(checkData string) {
switch checkData {
case "subscribeCtKline": // k线数据 8841
MgoSubscribeCtKline()
case "subscribeCtDepth": // 深度信息 8842
MgoSubscribeCtDepth()
case "subscribeCtAddDepth": // 新增深度信息 8843
MgoSubscribeCtAddDepth()
case "subscribeCtBbo": // 买一卖一行情数据 8844
MgoSubscribeCtBbo()
case "subscribeCtDetail": // 合约详情数据 8845
MgoSubscribeCtDetail()
case "subscribeCtTradeDetail": // 合约贸易详情数据 8846
MgoSubscribeCtTradeDetail()
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}
// 股票行情数据
func ShareMarket(checkData string) {
switch checkData {
case "usShare": // US 8849
ShareUsData()
default:
applogger.Info("Please select the data source that needs to be connected......")
}
}

86
internal/data/business/mgoshareus.go

@ -0,0 +1,86 @@
package business
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
"go.mongodb.org/mongo-driver/bson"
"strings"
"wss-pool/config"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/stock"
)
// ShareUsData TODO: 美股数据采集 修改为分布式多节点
func ShareUsData() {
url := fmt.Sprintf("https://%v/api/exchange-symbol-list/us?api_token=%v&fmt=json", config.Config.ShareGather.FinancialHost, config.Config.ShareGather.FinancialKey)
applogger.Debug("select info:%v", url)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("HttpGet err:%v", err)
return
}
var shareModel []stock.StockShare
if err := json.Unmarshal([]byte(bodyStr), &shareModel); err != nil {
applogger.Error("Unmarshal err:%v", err)
return
}
for _, value := range shareModel {
applogger.Info("select data info:%v", value)
go func() {
url := fmt.Sprintf("wss://%v/ws/%v?api_token=%v", config.Config.ShareGather.FinancialWsUs, "us", config.Config.ShareGather.FinancialKey)
conn, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
applogger.Error("链接wss服务器失败:%v", err)
return
}
defer conn.Close()
subscribe := fmt.Sprintf("{\"action\": \"subscribe\", \"symbols\": \"%v\"}", value.Code)
if err := conn.WriteMessage(websocket.TextMessage, []byte(subscribe)); err != nil {
applogger.Error("send connSub WriteMessage err::%v", err)
return
}
for {
_, msg, err := conn.ReadMessage()
if err != nil {
applogger.Error("err info:%v", err)
return
}
applogger.Info("Subscribe date:%v", string(msg))
if !strings.Contains(string(msg), "{\"status_code\":200,\"message\":\"Authorized\"}") {
go func() {
// 持久化数据信息
var msgC stock.RealTimeMessage
if err := bson.Unmarshal(msg, &msgC); err != nil {
applogger.Error("Insert info err:%v", err)
return
}
applogger.Debug("date info:%v", msgC)
var c = []string{}
for _, vc := range msgC.C {
c = append(c, vc.String())
}
cxt := bson.D{
{"S", msgC.S},
{"Dp", msgC.Dp},
{"C", c},
{"T", msgC.T},
{"Ms", msgC.Ms},
{"V", msgC.V},
{"P", msgC.P.String()},
}
if err := data.MgoInsertOne(data.StockUs, cxt); err != nil {
applogger.Error("Insert info err:%v", err)
return
}
}()
}
}
}()
}
}

381
internal/data/business/mgospots.go

@ -0,0 +1,381 @@
package business
import (
"fmt"
"go.mongodb.org/mongo-driver/bson"
"wss-pool/config"
"wss-pool/dictionary"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/hbwssclient/marketwssclient"
"wss-pool/pkg/model"
"wss-pool/pkg/model/market"
)
// 市场深度行情数据
func MgoSubscribeDepth() {
symbolList := model.SymbolListString(dictionary.Depth)
client := new(marketwssclient.DepthWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, value := range symbolList {
for _, vue := range value {
client.Request(key, vue, config.Config.HbGather.HbSubUids)
client.Subscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeDepthResponse)
if ok {
if &depthResponse != nil {
applogger.Info("subscribeDepth data,ServersId:%v,Content:%v-%v", depthResponse.Channel, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, value := range symbolList {
for _, vue := range value {
client.UnSubscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// 市场深度MBP行情数据(增量推送)(150挡)
func MgoSubscribeLevelMbp() {
symbolList := model.SymbolListString([]string{})
client := new(marketwssclient.MarketByPriceWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, _ := range symbolList {
client.Request(key, config.Config.HbGather.HbSubUids)
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeMarketByPriceResponse)
if ok {
if &depthResponse != nil {
applogger.Info("subscribeLevelMbp data,ServersId:%v,Content:%v-%v", depthResponse.Channel, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// 市场深度MBP行情数据(全量推送)
func MgoSubscribeFullMbp() {
symbolList := model.SymbolListInt(dictionary.LevelsRefresh)
client := new(marketwssclient.MarketByPriceWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, value := range symbolList {
for _, vue := range value {
client.SubscribeFull(key, vue, config.Config.HbGather.HbSubUids)
}
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeMarketByPriceResponse)
if ok {
if &depthResponse != nil {
applogger.Info("subscribeFullMbp data,ServersId:%v,Content:%v-%v", depthResponse.Channel, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, value := range symbolList {
for _, vue := range value {
client.UnSubscribeFull(key, vue, config.Config.HbGather.HbSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// 市场深度MBP行情数据(增量推送)
func MgoSubscribeSubMbp() {
symbolList := model.SymbolListInt(dictionary.LevelsMbp)
client := new(marketwssclient.MarketByPriceTickWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, value := range symbolList {
for _, vue := range value {
client.Request(key, vue, config.Config.HbGather.HbSubUids)
client.Subscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeMarketByPriceResponse)
if ok {
if &depthResponse != nil {
applogger.Info("subscribeSubMbp data,ServersId:%v,Sender:%v,Content:%v-%v", depthResponse.Channel, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, value := range symbolList {
for _, vue := range value {
client.UnSubscribe(key, vue, config.Config.HbGather.HbSubUids)
}
}
client.Close()
applogger.Info("Client closed")
}
// 买一卖一逐笔行情
func MgoSubscribeBbo() {
symbolList := model.SymbolListString([]string{})
client := new(marketwssclient.BestBidOfferWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, _ := range symbolList {
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
func(resp interface{}) {
bboResponse, ok := resp.(market.SubscribeBestBidOfferResponse)
if ok {
if bboResponse.Tick != nil {
}
applogger.Info("subscribeBbo data,ServersId:%v,Sender:%v,Content:%v-%v", bboResponse.Channel, bboResponse.Tick, nil)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Connection closed")
}
// K线数据
func MgoSubscribeKLine() {
symbolList := model.SymbolListString(dictionary.TimeCycle)
client := new(marketwssclient.CandlestickWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for symbol, period := range symbolList {
for _, value := range period {
client.Subscribe(symbol, value, config.Config.HbGather.HbSubUids)
}
}
},
func(response interface{}) {
resp, ok := response.(market.SubscribeCandlestickResponse)
if ok {
if &resp != nil {
if resp.Tick != nil || resp.Data != nil {
go func() {
Channel := resp.Channel
Tick := resp.Tick
Data := resp.Data
Timestamp := resp.Timestamp
tick := bson.D{
{"Id", Tick.Id},
{"Close", Tick.Close.String()},
{"Low", Tick.Low.String()},
{"Amount", Tick.Amount.String()},
{"High", Tick.High.String()},
{"Count", Tick.Count},
{"Open", Tick.Open.String()},
{"Vol", Tick.Vol.String()},
}
cxt := bson.D{
{"Channel", Channel},
{"Tick", tick},
{"Data", Data},
{"Timestamp", Timestamp},
}
table := internal.CheckKLineTable(Channel)
applogger.Debug("数据库表名称:%v", table)
if err := data.MgoInsertOne(table, cxt); err != nil {
applogger.Error("writeKLine err:%v", err)
return
}
}()
}
applogger.Info("subscribeKLine data Key:%v,time:%v,tick:%v,data:%v", resp.Channel, resp.Timestamp, resp.Tick, resp.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, period := range symbolList {
for _, value := range period {
client.UnSubscribe(symbol, value, config.Config.HbGather.HbSubUids)
}
}
client.UnSubscribe("btcusdt", "1min", config.Config.HbGather.HbSubUids)
client.Close()
applogger.Info("Client closed")
}
// 成交明细
func MgoSubscribeTrade() {
symbolList := model.SymbolListString([]string{})
client := new(marketwssclient.TradeWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, _ := range symbolList {
client.Request(key, config.Config.HbGather.HbSubUids)
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
func(resp interface{}) {
depthResponse, ok := resp.(market.SubscribeTradeResponse)
if ok {
if &depthResponse != nil {
applogger.Info("subscribeTrade data,ServersId:%v,Sender:%v,Content:%v-%v", depthResponse.Channel, depthResponse.Tick, depthResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// 市场概要
func MgoSubscribeLast24h() {
symbolList := model.SymbolListString([]string{})
client := new(marketwssclient.Last24hCandlestickWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for key, _ := range symbolList {
client.Request(key, config.Config.HbGather.HbSubUids)
client.Subscribe(key, config.Config.HbGather.HbSubUids)
}
},
func(resp interface{}) {
candlestickResponse, ok := resp.(market.SubscribeLast24hCandlestickResponse)
if ok {
if &candlestickResponse != nil {
applogger.Info("subscribeLast24h data,ServersId:%v,Sender:%v,Content:%v-%v", candlestickResponse.Channel, candlestickResponse.Tick, candlestickResponse.Data)
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for key, _ := range symbolList {
client.UnSubscribe(key, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}
// 聚合行情(Ticker)
func MgoSubscribeTicker() {
symbolList := model.SymbolListString([]string{})
client := new(marketwssclient.TickerWebSocketClient).Init(config.Config.HbGather.HbHost)
client.SetHandler(
func() {
for symbol, _ := range symbolList {
client.Subscribe(symbol, config.Config.HbGather.HbSubUids)
}
},
func(response interface{}) {
resp, ok := response.(market.TickerWebsocketResponse)
if ok {
if &resp != nil {
if resp.Tick != nil || resp.Data != nil {
applogger.Info("subscribeTicker data,ServersId:%v,Sender:%v,Content:%v-%v", resp.Channel, resp.Tick, resp.Data)
}
}
} else {
applogger.Warn("Unknown response: %v", resp)
}
})
client.Connect(true)
fmt.Println("Press ENTER to unsubscribe and stop...")
fmt.Scanln()
for symbol, _ := range symbolList {
client.UnSubscribe(symbol, config.Config.HbGather.HbSubUids)
}
client.Close()
applogger.Info("Client closed")
}

331
internal/data/business/mgostockus.go

@ -0,0 +1,331 @@
package business
import (
"encoding/json"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"wss-pool/config"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/stock"
)
/*
采集美股历史数据
日终数据
1每日 daily
https://eodhistoricaldata.com/api/eod/0001.KLSE?api_token=647dd6744b94f4.20894198&period=d&fmt=json&from=2022-08-01&to=2023-06-15
2每周 weekly
https://eodhistoricaldata.com/api/eod/MCD.US?api_token=647dd6744b94f4.20894198&period=w&fmt=json&from=2022-08-01&to=2023-06-15
3每月 monthly
https://eodhistoricaldata.com/api/eod/MCD.US?api_token=647dd6744b94f4.20894198&period=m&fmt=json&from=2022-08-01&to=2023-06-15
日内数据
11小时 hour
https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=1h&fmt=json&from=1659283200&to=1686815102
25分钟 fiveminutes
https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=5m&fmt=json&from=1659283200&to=1686815102
31分钟 oneminute
https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=1m&fmt=json
*/
func StockUsDaily() {
filter := bson.M{"Country": "USA", "YesterdayClose": bson.M{"$ne": ""}, "BeforeClose": bson.M{"$ne": ""}}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
for _, value := range dateList.([]primitive.M) {
code := TypeCheck(value["Code"])
applogger.Debug("code info: %v", code)
codeUS := fmt.Sprintf("%v.US", code)
// https://eodhistoricaldata.com/api/eod/0001.KLSE?api_token=647dd6744b94f4.20894198&period=d&fmt=json&from=2022-08-01&to=2023-06-15
url := fmt.Sprintf("https://%v/api/eod/%v?api_token=%v&period=d&fmt=json&from=2022-08-01&to=2023-06-17",
config.Config.ShareGather.FinancialHost, codeUS, config.Config.ShareGather.FinancialKey)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
fmt.Println(url)
var eodModel []stock.EodData
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
applogger.Info("data info: %v", eodModel)
var bsonEod []interface{}
for _, eodValue := range eodModel {
bsonEod = append(bsonEod, bson.D{
{"code", code},
{"date", eodValue.Date},
{"open", eodValue.Open},
{"high", eodValue.High},
{"low", eodValue.Low},
{"close", eodValue.Close},
{"adjusted_close", eodValue.AdjustedClose},
{"volume", eodValue.Volume},
})
}
if len(bsonEod) > 0 {
if err := data.MgoInsertMany(data.StockUsDaily, bsonEod); err != nil {
applogger.Error("MgoInsertMany info err: %v", err)
return
}
}
}
}
func StockUsWeekly() {
filter := bson.M{"Country": "USA", "YesterdayClose": bson.M{"$ne": ""}, "BeforeClose": bson.M{"$ne": ""}}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
for _, value := range dateList.([]primitive.M) {
code := TypeCheck(value["Code"])
applogger.Debug("code info: %v", code)
codeUS := fmt.Sprintf("%v.US", code)
// https://eodhistoricaldata.com/api/eod/MCD.US?api_token=647dd6744b94f4.20894198&period=w&fmt=json&from=2022-08-01&to=2023-06-15
url := fmt.Sprintf("https://%v/api/eod/%v?api_token=%v&period=w&fmt=json&from=2022-08-01&to=2023-06-17",
config.Config.ShareGather.FinancialHost, codeUS, config.Config.ShareGather.FinancialKey)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var eodModel []stock.EodData
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
applogger.Info("data info: %v", eodModel)
var bsonEod []interface{}
for _, eodValue := range eodModel {
bsonEod = append(bsonEod, bson.D{
{"code", code},
{"date", eodValue.Date},
{"open", eodValue.Open},
{"high", eodValue.High},
{"low", eodValue.Low},
{"close", eodValue.Close},
{"adjusted_close", eodValue.AdjustedClose},
{"volume", eodValue.Volume},
})
}
if len(bsonEod) > 0 {
if err := data.MgoInsertMany(data.StockUsWeekly, bsonEod); err != nil {
applogger.Error("MgoInsertMany info err: %v", err)
return
}
}
}
}
func StockUsMonthly() {
filter := bson.M{"Country": "USA", "YesterdayClose": bson.M{"$ne": ""}, "BeforeClose": bson.M{"$ne": ""}}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
for _, value := range dateList.([]primitive.M) {
code := TypeCheck(value["Code"])
applogger.Debug("code info: %v", code)
codeUs := fmt.Sprintf("%v.US", code)
// https://eodhistoricaldata.com/api/eod/MCD.US?api_token=647dd6744b94f4.20894198&period=m&fmt=json&from=2022-08-01&to=2023-06-17
url := fmt.Sprintf("https://%v/api/eod/%v?api_token=%v&period=m&fmt=json&from=2022-08-01&to=2023-06-17",
config.Config.ShareGather.FinancialHost, codeUs, config.Config.ShareGather.FinancialKey)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var eodModel []stock.EodData
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
applogger.Info("data info: %v", eodModel)
var bsonEod []interface{}
for _, eodValue := range eodModel {
bsonEod = append(bsonEod, bson.D{
{"code", code},
{"date", eodValue.Date},
{"open", eodValue.Open},
{"high", eodValue.High},
{"low", eodValue.Low},
{"close", eodValue.Close},
{"adjusted_close", eodValue.AdjustedClose},
{"volume", eodValue.Volume},
})
}
if len(bsonEod) > 0 {
if err := data.MgoInsertMany(data.StockUsMonthly, bsonEod); err != nil {
applogger.Error("MgoInsertMany info err: %v", err)
return
}
}
}
}
func StockUsHour() {
filter := bson.M{"Country": "USA", "YesterdayClose": bson.M{"$ne": ""}, "BeforeClose": bson.M{"$ne": ""}}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
for _, value := range dateList.([]primitive.M) {
code := TypeCheck(value["Code"])
applogger.Debug("code info: %v", code)
codeUs := fmt.Sprintf("%v.US", code)
// https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=1h&fmt=json&from=1659283200&to=1686759266
url := fmt.Sprintf("https://%v/api/intraday/%v?api_token=%v&interval=1h&fmt=json&from=1659283200&to=1686759266",
config.Config.ShareGather.FinancialHost, codeUs, config.Config.ShareGather.FinancialKey)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var eodModel []stock.IntraDayData
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
applogger.Info("data info: %v", eodModel)
var bsonEod []interface{}
for _, eodValue := range eodModel {
bsonEod = append(bsonEod, bson.D{
{"code", code},
{"timestamp", eodValue.Timestamp},
{"gmtoffset", eodValue.Gmtoffset},
{"datetime", eodValue.Datetime},
{"open", eodValue.Open},
{"high", eodValue.High},
{"low", eodValue.Low},
{"close", eodValue.Close},
{"volume", eodValue.Volume},
})
}
if len(bsonEod) > 0 {
if err := data.MgoInsertMany(data.StockUsHour, bsonEod); err != nil {
applogger.Error("MgoInsertMany info err: %v", err)
return
}
}
}
}
func StockUsFiveMinutes() {
filter := bson.M{"Country": "USA", "YesterdayClose": bson.M{"$ne": ""}, "BeforeClose": bson.M{"$ne": ""}}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
for _, value := range dateList.([]primitive.M) {
code := TypeCheck(value["Code"])
applogger.Debug("code info: %v", code)
codeUs := fmt.Sprintf("%v.US", code)
// https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=5m&fmt=json&from=1659283200&to=1686815102
url := fmt.Sprintf("https://%v/api/intraday/%v?api_token=%v&interval=5m&fmt=json&from=1659283200&to=1686759266",
config.Config.ShareGather.FinancialHost, codeUs, config.Config.ShareGather.FinancialKey)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var eodModel []stock.IntraDayData
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
applogger.Info("data info: %v", eodModel)
var bsonEod []interface{}
for _, eodValue := range eodModel {
bsonEod = append(bsonEod, bson.D{
{"code", code},
{"timestamp", eodValue.Timestamp},
{"gmtoffset", eodValue.Gmtoffset},
{"datetime", eodValue.Datetime},
{"open", eodValue.Open},
{"high", eodValue.High},
{"low", eodValue.Low},
{"close", eodValue.Close},
{"volume", eodValue.Volume},
})
}
if len(bsonEod) > 0 {
if err := data.MgoInsertMany(data.StockUsFiveMinutes, bsonEod); err != nil {
applogger.Error("MgoInsertMany info err: %v", err)
return
}
}
}
}
func StockUsOneMinute() {
filter := bson.M{"Country": "USA", "YesterdayClose": bson.M{"$ne": ""}, "BeforeClose": bson.M{"$ne": ""}}
dateList, err := data.MgoFind(data.StockList, filter)
if err != nil {
applogger.Error("MgoFind info err: %v", err)
return
}
for _, value := range dateList.([]primitive.M) {
code := TypeCheck(value["Code"])
applogger.Debug("code info: %v", code)
codeUs := fmt.Sprintf("%v.US", code)
// https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=1m&fmt=json
url := fmt.Sprintf("https://%v/api/intraday/%v?api_token=%v&interval=1m&fmt=json",
config.Config.ShareGather.FinancialHost, codeUs, config.Config.ShareGather.FinancialKey)
bodyStr, err := internal.HttpGet(url)
if err != nil {
applogger.Error("Failed to query data:%v", err)
return
}
var eodModel []stock.IntraDayData
if err = json.Unmarshal([]byte(bodyStr), &eodModel); err != nil {
applogger.Error("eodModel json Unmarshal err: %v", err)
return
}
applogger.Info("data info: %v", eodModel)
var bsonEod []interface{}
for _, eodValue := range eodModel {
bsonEod = append(bsonEod, bson.D{
{"code", code},
{"timestamp", eodValue.Timestamp},
{"gmtoffset", eodValue.Gmtoffset},
{"datetime", eodValue.Datetime},
{"open", eodValue.Open},
{"high", eodValue.High},
{"low", eodValue.Low},
{"close", eodValue.Close},
{"volume", eodValue.Volume},
})
}
if len(bsonEod) > 0 {
if err := data.MgoInsertMany(data.StockUsOneMinute, bsonEod); err != nil {
applogger.Error("MgoInsertMany info err: %v", err)
return
}
}
}
}

24
internal/data/gorm.go

@ -0,0 +1,24 @@
package data
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"sync"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var WebGorm *gorm.DB
type DBConnect struct {
sync.Mutex
}
func InitGorm(config model.Bourse) {
var err error
if WebGorm, err = gorm.Open(mysql.Open(config.Datasource), &gorm.Config{Logger: logger.Default.LogMode(logger.Info)}); err != nil {
applogger.Error(fmt.Sprintf("连接%s数据库失败 err: %s", config.Datasource, err.Error()))
}
}

501
internal/data/index.go

@ -0,0 +1,501 @@
package data
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"wss-pool/dictionary"
"wss-pool/logging/applogger"
)
const (
StockIndexList = "stockIndexList"
StockUs = "stockListUs" // US Real time
StockUsDaily = "stockUsDaily" // Us daily data
StockUsWeekly = "stockUsWeekly" // Us Weekly data
StockUsMonthly = "stockUsMonthly" // Us Monthly data
StockUsHour = "stockUsHour" // Us Hourly data
StockUsFiveMinutes = "stockUsFiveMinutes" // Us Data every five minutes
StockUsOneMinute = "stockUsOneMinute" // Us Data every minutes
StockNews = "stockNews"
OptionList = "optionList" // list
ForexList = "forexList" // forex list
ForexListBak = "forexListBak" // forex list
ForexTradeList = "forexTradeList" // forex trade list
ForexKLine = "forexKLine" // forex kline
)
var StockList string
// Create_stockList_index New Stock List Index
func Create_stockList_index() {
c := MgoConnect(StockList)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"Code", -1},
}},
{Keys: bson.D{
{"Symbol", -1},
}},
{Keys: bson.D{
{"Exchange", -1},
}},
{Keys: bson.D{
{"Vol", -1},
}},
{Keys: bson.D{
{"Source", -1},
}},
{Keys: bson.D{
{"Country", -1},
}},
{Keys: bson.D{
{"NumericCode", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
func Create_stockIndixList_index() {
c := MgoConnect(StockIndexList)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"Code", -1},
}},
{Keys: bson.D{
{"Exchange", -1},
}},
{Keys: bson.D{
{"Sort", -1},
}},
{Keys: bson.D{
{"State", -1},
}},
{Keys: bson.D{
{"Country", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
func CreateOptionList() {
c := MgoConnect(OptionList)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"Code", -1},
}},
{Keys: bson.D{
{"Percent", -1},
}},
{Keys: bson.D{
{"DateTime", -1},
}},
{Keys: bson.D{
{"Country", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
func CreateOptionindex() {
for _, value := range dictionary.OptionCodeList {
tableName := GetOptionTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"code", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
tableName = GetOptionExpiryTableName(value)
c = MgoConnect(tableName)
indexModel = []mongo.IndexModel{
{Keys: bson.D{
{"code", -1},
}},
{Keys: bson.D{
{"expiry", -1},
}},
{Keys: bson.D{
{"expiry_date", -1},
}},
}
_, err = c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
// 外汇索引
func CreateForexList() {
c := MgoConnect(ForexList)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"ticker", -1},
}},
{Keys: bson.D{
{"updated", -1},
}},
{Keys: bson.D{
{"day.o", -1},
}},
{Keys: bson.D{
{"day.l", -1},
}},
{Keys: bson.D{
{"day.h", -1},
}},
{Keys: bson.D{
{"day.c", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
func CreateForexListNew() {
c := MgoConnect(ForexListBak)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"code", -1},
}},
{Keys: bson.D{
{"name", -1},
}},
{Keys: bson.D{
{"category", -1},
}},
{Keys: bson.D{
{"symbol", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
func CreateForexTradeLis() {
// {"ev":"T","code":"NZDCAD","seq":"75468393","tick_time":"1732274869856","price":"0.81696","volume":"92500.00","turnover":"0.00000","trade_direction":0}
c := MgoConnect(ForexTradeList)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"code", -1},
}},
{Keys: bson.D{
{"tick_time", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
func CreateForexKLine() {
c := MgoConnect(ForexKLine)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"code", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
// Create_stockUs_index 美股实时数据索引
func Create_stockUs_index() {
c := MgoConnect(StockUs)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"s", -1},
}},
{Keys: bson.D{
{"se", -1},
}},
{Keys: bson.D{
{"t", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
// Create_stockUs_index
func Create_SpotKline_index() {
for _, value := range dictionary.TimeCycle {
tableName := GetStockKLineTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"channel", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"code", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
func Create_ContractKline_index() {
for _, value := range dictionary.ContractTime {
tableName := GetContractKLineTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"channel", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"code", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
func Create_ContractPriceKline_index() {
for _, value := range dictionary.ContractPriceTime {
tableName := GetContractPriceKLineTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"channel", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"code", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
func Create_Stock_News() {
c := MgoConnect(StockNews)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"code", -1},
}},
{Keys: bson.D{
{"country", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
func Create_Stock_index() {
for _, value := range dictionary.StockCodeList {
tableName := GetStockTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"stock_code", -1},
}},
{Keys: bson.D{
{"symbol", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}}, {
Keys: bson.D{
{"price", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
//DAY K WEEK
for _, v := range dictionary.StockSouthAsiaListTime {
c := MgoConnect(fmt.Sprintf("%s%s", tableName, v))
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"stock_code", -1},
}},
{Keys: bson.D{
{"symbol", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"high_price", -1},
}},
{Keys: bson.D{
{"low_price", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
}
func Create_SpockIndexKline() {
tableName := GetStockIndexTableName()
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"stock_code", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}}, {
Keys: bson.D{
{"price", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
//DAY K WEEK
for _, v := range dictionary.StockSouthAsiaListTime {
c := MgoConnect(fmt.Sprintf("%s%s", tableName, v))
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"stock_code", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"high_price", -1},
}},
{Keys: bson.D{
{"low_price", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
func Create_StockUsList_index() {
for _, value := range dictionary.StockUsListTime {
tableName := GetStockUsTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"code", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
func Create_SpotKlineTest_index() {
for _, value := range dictionary.TimeCycle {
tableName := GetStockKLineTestTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"channel", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"code", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}
func Create_ContractKlineTest_index() {
for _, value := range dictionary.ContractTime {
tableName := GetContractKLineTestTableName(value)
c := MgoConnect(tableName)
indexModel := []mongo.IndexModel{
{Keys: bson.D{
{"channel", -1},
}},
{Keys: bson.D{
{"timestamp", -1},
}},
{Keys: bson.D{
{"code", -1},
}},
}
_, err := c.Indexes().CreateMany(context.TODO(), indexModel)
if err != nil {
applogger.Error("Failed to create index:%v", err)
return
}
}
}

767
internal/data/mongo.go

@ -0,0 +1,767 @@
package data
import (
"context"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
"os"
"sort"
"time"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var DataBase string
var mgoDb *mongo.Client
var MgoDbClientMap = map[string]*mongo.Client{}
var MgoDbToRedisMap = map[string]string{}
type MongoTick struct {
Id string `bson:"_id" json:"id"`
Channel string `json:"channel"`
Amount string `json:"amount"` // 成交量
Count interface{} `bson:"count" json:"count"` // 成交笔数
Open string `json:"open"` // 开盘价
Close string `json:"close"` // 收盘价(当K线为最晚的一根时,是最新成交价)
Low string `json:"low"` // 最低价
High string `json:"high"` // 最高价
Vol string `json:"vol"` // 成交额, 即 sum(每一笔成交价 * 该笔的成交量)
Timestamp int64 `json:"timestamp"`
TradeTurnover string `bson:"trade_turnover" json:"trade_turnover"`
Code int64 `json:"code"`
}
func GetStockKLineTableName(period string) string {
return fmt.Sprintf("marketKline%v", period)
}
func GetContractKLineTableName(period string) string {
return fmt.Sprintf("contractKline%v", period)
}
func GetContractPriceKLineTableName(period string) string {
return fmt.Sprintf("contractPriceKline%v", period)
}
func GetStockUsTableName(period string) string {
return fmt.Sprintf("stockUs%v", period)
}
func GetStockKLineTestTableName(period string) string {
return fmt.Sprintf("marketKline%v_test", period)
}
func GetContractKLineTestTableName(period string) string {
return fmt.Sprintf("contractKline%v_test", period)
}
func GetStockTableName(period string) string {
return fmt.Sprintf("stock%v", period)
}
func GetStockIndexTableName() string {
return fmt.Sprintf("stockIndex")
}
func GetStockIndixKlineTableName(period string) string {
return fmt.Sprintf("stockIndex%s", period)
}
func GetStockSouthAsiaTableName(stock, period string) string {
return fmt.Sprintf("stock%s%s", stock, period)
}
func GetOptionTableName(country string) string {
return fmt.Sprintf("option%v", country)
}
func GetOptionExpiryTableName(country string) string {
return fmt.Sprintf("optionExpiry%v", country)
}
func GetStockList(stockListTable int) {
if stockListTable == 0 {
StockList = "stockListBak"
return
}
StockList = fmt.Sprintf("stockListBak%d", stockListTable)
}
// Mgo_init
func Mgo_init(config model.Mongodb) {
mongodb := config.DbBase
GetStockList(config.Table)
fmt.Println(StockList)
clientOptions := options.Client().ApplyURI(fmt.Sprintf("mongodb://%v:%v@%v:%v/%v?ssl=false&authSource=admin", config.DbUser, config.Password, config.DbHost, config.DbPort, mongodb))
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Printf("connect err: %v", err)
return
}
// Check the connection
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Printf("test connect err: %v", err)
return
}
fmt.Println("Mongodb ok")
mgoDb = client
DataBase = mongodb
// init mongo_index
Create_stockList_index()
Create_stockUs_index()
Create_stockIndixList_index()
//Create_StockUs_Daily()
//Create_StockUs_Weekly()
//Create_StockUs_Monthly()
//Create_StockUs_Hour()
//Create_StockUs_Five_Minutes()
//Create_StockUs_One_Minute()
Create_SpotKline_index()
Create_ContractKline_index()
Create_ContractPriceKline_index()
Create_StockUsList_index()
Create_Stock_index()
Create_SpockIndexKline()
Create_Stock_News()
//测试
//Create_SpotKlineTest_index()
//Create_ContractKlineTest_index()
//期权
CreateOptionList()
CreateOptionindex()
// 外汇
CreateForexList()
CreateForexListNew()
CreateForexTradeLis()
CreateForexKLine()
}
func Mgo_inits(config model.Mongodb) *mongo.Client {
mongodb := config.DbBase
clientOptions := options.Client().ApplyURI(fmt.Sprintf("mongodb://%v:%v@%v:%v/%v?ssl=false&authSource=admin", config.DbUser, config.Password, config.DbHost, config.DbPort, mongodb))
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Printf("connect err: %v", err)
return client
}
// Check the connection
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Printf("test connect err: %v", err)
return client
}
fmt.Println("Mongodb ok")
return client
}
func Mgo_initMap(config model.Mongodb, addrList []string) {
GetStockList(config.Table)
DataBase = config.DbBase
for _, addr := range addrList {
clientOptions := options.Client().ApplyURI(fmt.Sprintf("mongodb://%v:%v@%v:%v/%v?ssl=false&authSource=admin", config.DbUser, config.Password, addr, config.DbPort, DataBase))
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Printf("connect err: %v", err)
return
}
// Check the connection
err = client.Ping(context.TODO(), nil)
if err != nil {
os.Exit(0)
} else {
applogger.Info("mongodb init success")
}
MgoDbClientMap[addr] = client
}
}
// mgoConnect
func mgoConnect(collection string) *mongo.Collection {
return mgoDb.Database(DataBase).Collection(collection)
}
// MgoConnect
func MgoConnect(collection string) *mongo.Collection {
return mgoConnect(collection)
}
// MgoInsertOne
func MgoInsertOne(collection string, docs interface{}) error {
c := mgoConnect(collection)
if _, err := c.InsertOne(context.TODO(), docs); err != nil {
return err
}
return nil
}
// MgoInsertMany
func MgoInsertMany(collection string, docs []interface{}) error {
c := mgoConnect(collection)
if _, err := c.InsertMany(context.TODO(), docs); err != nil {
return err
}
return nil
}
// MgoUpdateID
func MgoUpdateID(collection string, id interface{}, update interface{}) error {
c := mgoConnect(collection)
if _, err := c.UpdateByID(context.TODO(), id, update); err != nil {
return err
}
return nil
}
// MgoUpdateOne
func MgoUpdateOne(collection string, filter interface{}, update interface{}) error {
c := mgoConnect(collection)
if _, err := c.UpdateOne(context.TODO(), filter, update); err != nil {
return err
}
return nil
}
func MgoUpdateOneTrue(collection string, filter interface{}, update interface{}) error {
c := mgoConnect(collection)
opts := options.Update().SetUpsert(true)
result, err := c.UpdateOne(context.TODO(), filter, update, opts)
if err != nil {
return err
}
if result.MatchedCount == 0 && result.UpsertedCount > 0 {
log.Printf("A new document was inserted with the id: %v", result.UpsertedID)
} else if result.MatchedCount > 0 {
log.Println("An existing document was updated")
} else {
log.Println("No operation was performed")
}
return nil
}
// MgoUpdateMany
func MgoUpdateMany(collection string, filter interface{}, update interface{}) error {
c := mgoConnect(collection)
if _, err := c.UpdateMany(context.TODO(), filter, update); err != nil {
return err
}
return nil
}
// MgoBulkWrite
func MgoBulkWrite(collection string, models []mongo.WriteModel) error {
c := mgoConnect(collection)
_, err := c.BulkWrite(context.TODO(), models)
if err != nil {
applogger.Error("UpdateMany err:%v", err)
}
return err
}
func MgoBulkWrites(client *mongo.Client, collection string, models []mongo.WriteModel) error {
c := client.Database(DataBase).Collection(collection)
_, err := c.BulkWrite(context.TODO(), models)
if err != nil {
applogger.Error("UpdateMany err:%v", err)
}
return err
}
// MgoIsExist
func MgoIsExist(collection string, filter interface{}) bool {
c := mgoConnect(collection)
cur, err := c.Find(context.TODO(), filter)
if err != nil {
return false
}
var numDocs int
for cur.Next(context.Background()) {
numDocs++
}
return numDocs > 0
}
// MgoFind
func MgoFind(collection string, filter interface{}) (interface{}, error) {
c := mgoConnect(collection)
cursor, err := c.Find(context.TODO(), filter, options.Find().SetSort(bson.M{"Vol": -1}))
if err != nil {
applogger.Error("Find err: %v", err)
return nil, err
}
var results []bson.M
if err = cursor.All(context.TODO(), &results); err != nil {
log.Fatal(err)
}
return results, nil
}
func MgoFindProjectionRes(collection string, filter, projection, sort interface{}, res interface{}, limit int64) { //
c := mgoConnect(collection)
var optionStr *options.FindOptions
if limit > 0 {
optionStr = options.Find().SetProjection(projection).SetSort(sort).SetLimit(limit)
} else {
optionStr = options.Find().SetProjection(projection).SetSort(sort)
}
cursor, err := c.Find(context.TODO(), filter, optionStr)
if err != nil {
applogger.Error("Find MgoFindProjectionRes err: %v", err)
return
}
if err = cursor.All(context.TODO(), res); err != nil {
log.Fatal(err)
}
return
}
func MgoFindProjectionAggregate(collection, field string, filter, projection, sort interface{}, limit int64) []model.StockMogoParam { //
//fmt.Println(collection, field, filter)
params := make([]model.StockMogoParam, 0)
pipeline := mongo.Pipeline{
{{"$match", filter}},
// Convert the string field to a double (floating-point) field
{{"$addFields", bson.D{{"stringVal", bson.D{{"$toDouble", "$" + field}}}}}},
// Sort by the new double field
{{"$sort", sort}},
// Project only the numeric field
// {{"$project", projection}},
// Limit to one document to get the single sorted numeric value
{{"$limit", limit}},
}
c := mgoConnect(collection)
aggregate, err := c.Aggregate(context.TODO(), pipeline)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return params
}
if aggregate.Next(context.TODO()) {
param := model.StockMogoParam{}
if err = aggregate.Decode(&param); err != nil {
log.Fatal(err)
}
params = append(params, param)
}
//fmt.Printf("%+v", params)
return params
}
func MgoFindRes(collection string, filter interface{}, res interface{}) {
c := mgoConnect(collection)
cursor, err := c.Find(context.TODO(), filter, options.Find().SetSort(bson.M{"timestamp": 1}))
if err != nil {
applogger.Error("Find err: %v", err)
return
}
if err = cursor.All(context.TODO(), res); err != nil {
log.Fatal(err)
}
return
}
func MgoFindStockRes(collection string, filter interface{}, res interface{}) {
c := mgoConnect(collection)
cursor, err := c.Find(context.TODO(), filter, options.Find().SetSort(bson.M{"Vol": -1}))
if err != nil {
applogger.Error("Find err: %v", err)
return
}
if err = cursor.All(context.TODO(), res); err != nil {
log.Fatal(err)
}
return
}
// MgoFindOne
func MgoFindOne(collection string, filter interface{}) (bson.M, error) {
c := mgoConnect(collection)
cur := c.FindOne(context.TODO(), filter)
var res bson.M
if err := cur.Decode(&res); err != nil {
return nil, err
}
return res, nil
}
// MgoFindAll
func MgoFindAll(collection string, filter interface{}) (interface{}, error) {
c := mgoConnect(collection)
cur, err := c.Find(context.TODO(), filter)
if err != nil {
return nil, err
}
var res interface{}
if err := cur.All(context.TODO(), &res); err != nil {
return nil, err
}
return res, err
}
// MgoDeleteOne
func MgoDeleteOne(collection string, filter interface{}) error {
c := mgoConnect(collection)
_, err := c.DeleteOne(context.TODO(), filter)
if err != nil {
return err
}
return nil
}
// MgoDeleteMany
func MgoDeleteMany(collection string, filter interface{}) error {
c := mgoConnect(collection)
_, err := c.DeleteMany(context.TODO(), filter)
if err != nil {
return err
}
return nil
}
func MillisToTime(millis int64) time.Time {
return time.Unix(0, millis*int64(time.Millisecond))
}
// MgoDeleteMany100List
func MgoDeleteMany100List(collection string, filter interface{}, code string) error {
c := mgoConnect(collection)
var count int64 = 100
// 查询所有文档并排序
findOptions := options.Find().SetSort(bson.D{{"tick_time", -1}}).SetLimit(count)
cursor, err := c.Find(context.Background(), filter, findOptions)
if err != nil {
applogger.Error("Find err:%v", err)
return err
}
var docs []bson.M
if err = cursor.All(context.Background(), &docs); err != nil {
return err
}
if len(docs) < 100 {
return nil
}
//applogger.Debug("总数据:%v", len(docs))
// 获取最新N条记录的创建时间
var latestTimes []time.Time
for _, doc := range docs {
createdAt := MillisToTime(doc["tick_time"].(int64))
latestTimes = append(latestTimes, createdAt)
}
// 按时间排序,保证时间最新的在前面
sort.Slice(latestTimes, func(i, j int) bool {
return latestTimes[i].After(latestTimes[j])
})
times := latestTimes[count-1].UnixNano() / 1e6 // 转换为毫秒
//applogger.Debug("需要删除的时间节点:%v,时间:%v", latestTimes[count-1], times)
// 删除旧的数据,保留最新的N条
deleteFilter := bson.M{
"tick_time": bson.M{
"$lt": times,
},
"code": code,
}
_, err = c.DeleteMany(context.Background(), deleteFilter)
if err != nil {
return err
}
//applogger.Debug("Deleted %v documents", deleteResult.DeletedCount)
return nil
}
func MgoFindResM(collection string, filter interface{}) ([]mongo.WriteModel, error) {
var results []mongo.WriteModel
c := mgoConnect(collection)
cur, err := c.Find(context.TODO(), filter, options.Find().SetSort(bson.M{"timestamp": 1}))
if err != nil {
applogger.Error("Find err: %v", err)
return results, err
}
if err = cur.All(context.TODO(), &results); err != nil {
log.Fatal(err)
}
return results, nil
}
// MgoPagingFind
func MgoPagingFind(collection string, filter interface{}, limit, page int64, sort int) ([]bson.M, error) {
c := mgoConnect(collection)
var optionStr *options.FindOptions
if sort != 0 {
optionStr = options.Find().SetLimit(limit).SetSkip((limit * page) - limit).SetSort(bson.M{"Code": sort})
} else {
optionStr = options.Find().SetLimit(limit).SetSkip((limit * page) - limit)
}
fmt.Println(filter)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return nil, err
}
var results []bson.M
if err = cur.All(context.TODO(), &results); err != nil {
log.Fatal(err)
}
return results, err
}
func MgoPagingFindStruct(collection string, filter interface{}, limit, page int64, sortField string, sort int, res interface{}) error {
c := mgoConnect(collection)
var optionStr *options.FindOptions
optionStr = options.Find().SetLimit(limit).SetSkip((limit * page) - limit).SetSort(bson.M{sortField: sort})
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return err
}
if err = cur.All(context.TODO(), res); err != nil {
log.Fatal(err)
return err
}
return nil
}
func MgoPagingFindStructList(collection string, filter interface{}, limit, page int64, sortStr string, sort int, res interface{}) error {
c := mgoConnect(collection)
var optionStr *options.FindOptions
optionStr = options.Find().SetLimit(limit).SetSkip((limit * page) - limit).SetSort(bson.M{sortStr: sort})
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return err
}
if err = cur.All(context.TODO(), res); err != nil {
log.Fatal(err)
return err
}
return nil
}
func MgoPagingFindStructSort(collection string, filter interface{}, limit, page int64, sort interface{}, res interface{}) error {
c := mgoConnect(collection)
var optionStr *options.FindOptions
optionStr = options.Find().SetLimit(limit).SetSkip((limit * page) - limit).SetSort(sort)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return err
}
if err = cur.All(context.TODO(), res); err != nil {
log.Fatal(err)
return err
}
return nil
}
func MgoPagingFindStructProjection(collection string, filter, projection interface{}, limit, page int64, sort int, res interface{}) error {
c := mgoConnect(collection)
var optionStr *options.FindOptions
optionStr = options.Find().SetLimit(limit).SetSkip((limit * page) - limit).SetSort(bson.M{"Vol": sort}).SetProjection(projection)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return err
}
if err = cur.All(context.TODO(), res); err != nil {
log.Fatal(err)
return err
}
return nil
}
func MgoPagingFindStructProjectionNew(mongoClient *mongo.Client, collection string, filter, projection interface{}, limit, page int64, sort int, res interface{}) error {
// 链接mongodb数据库
c := mongoClient.Database(DataBase).Collection(collection)
// 查询mongodb数据
var optionStr *options.FindOptions
optionStr = options.Find().SetLimit(limit).SetSkip((limit * page) - limit).SetSort(bson.M{"Vol": sort}).SetProjection(projection)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v,dataTable Name:%v", err, collection)
return err
}
if err = cur.All(context.TODO(), res); err != nil {
log.Fatal(err)
return err
}
return nil
}
func MgoFindToStr(collection string, filter interface{}, limit int64, res interface{}) error {
c := mgoConnect(collection)
var optionStr *options.FindOptions
if limit > 0 {
optionStr = options.Find().SetLimit(limit).SetSort(bson.M{"timestamp": -1})
} else {
optionStr = options.Find().SetSort(bson.M{"timestamp": -1})
}
//fmt.Println(filter)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return err
}
if err = cur.All(context.TODO(), res); err != nil {
log.Fatal(err)
return err
}
return nil
}
func MgoFindForexToStr(collection string, filter interface{}, limit int64, res interface{}) error {
c := mgoConnect(collection)
var optionStr *options.FindOptions
if limit > 0 {
optionStr = options.Find().SetLimit(limit).SetSort(bson.M{"tick_time": -1})
} else {
optionStr = options.Find().SetSort(bson.M{"tick_time": -1})
}
//fmt.Println(filter)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return err
}
if err = cur.All(context.TODO(), res); err != nil {
log.Fatal(err)
return err
}
return nil
}
func MgoLimitFind(collection string, filter interface{}, limit int64) ([]MongoTick, error) {
res := make([]MongoTick, 0)
c := mgoConnect(collection)
var optionStr *options.FindOptions
if limit > 0 {
optionStr = options.Find().SetLimit(limit).SetSort(bson.M{"code": -1})
} else {
optionStr = options.Find().SetSort(bson.M{"code": -1})
}
//fmt.Println(filter)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return nil, err
}
//var results []bson.M
if err = cur.All(context.TODO(), &res); err != nil {
log.Fatal(err)
}
return res, err
}
func MgoFinds(collection string, filter interface{}, limit int64) ([]bson.M, error) {
c := mgoConnect(collection)
var optionStr *options.FindOptions
if limit > 0 {
optionStr = options.Find().SetLimit(limit).SetSort(bson.M{"timestamp": -1})
} else {
optionStr = options.Find().SetSort(bson.M{"timestamp": -1})
}
//fmt.Println(filter)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return nil, err
}
var results []bson.M
if err = cur.All(context.TODO(), &results); err != nil {
log.Fatal(err)
}
return results, err
}
func MgoFindsCode(collection string, filter interface{}, limit int64) ([]bson.M, error) {
c := mgoConnect(collection)
var optionStr *options.FindOptions
if limit > 0 {
optionStr = options.Find().SetLimit(limit).SetSort(bson.M{"code": -1})
} else {
optionStr = options.Find().SetSort(bson.M{"code": -1})
}
//fmt.Println(filter)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return nil, err
}
var results []bson.M
if err = cur.All(context.TODO(), &results); err != nil {
log.Fatal(err)
}
return results, err
}
func MgoFindProjection(collection string, filter, projection, sort interface{}, limit int64) ([]bson.M, error) {
c := mgoConnect(collection)
var optionStr *options.FindOptions
if limit > 0 {
optionStr = options.Find().SetLimit(limit).SetSort(sort).SetProjection(projection)
} else {
optionStr = options.Find().SetSort(sort).SetProjection(projection)
}
//fmt.Println(filter)
cur, err := c.Find(context.Background(), filter, optionStr)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return nil, err
}
var results []bson.M
if err = cur.All(context.TODO(), &results); err != nil {
log.Fatal(err)
}
return results, err
}
// MgoFindTotal
func MgoFindTotal(collection string, filter interface{}) (int64, error) {
c := mgoConnect(collection)
total, err := c.CountDocuments(context.TODO(), filter)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return 0, err
}
return total, nil
}
// MgoAggregate
func MgoAggregate(collection string, filter mongo.Pipeline) (interface{}, error) {
c := mgoConnect(collection)
aggregate, err := c.Aggregate(context.TODO(), filter)
if err != nil {
applogger.Error("MgoPagingFind info err:%v", err)
return 0, err
}
var showsWithInfo []map[string]interface{}
if err = aggregate.All(context.TODO(), &showsWithInfo); err != nil {
log.Printf("collection %s", err)
panic(err)
}
return showsWithInfo, nil
}

48
internal/data/mysql.go

@ -0,0 +1,48 @@
package data
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
"wss-pool/logging/applogger"
"wss-pool/pkg/model"
)
var Engine *xorm.EngineGroup
var DB *sql.DB
// InitMysql Init Mysql DB
func InitMysql(config model.Bourse) {
var err error
driver := config.Driver
datasource := config.Datasource
conns := []string{
datasource,
}
Engine, err = xorm.NewEngineGroup(driver, conns, xorm.RandomPolicy())
if err != nil {
applogger.Error("orm failed to initialized: %v", err)
panic(err)
}
Engine.ShowExecTime(true)
Engine.ShowSQL(true)
Engine.SetMaxOpenConns(500)
err = Engine.Ping()
if err != nil {
panic(err)
}
}
func InitMsqlDB(config model.Bourse) {
// root:IcWw%%r3pt@tcp(34.126.77.164:3307)/bourse?charset=utf8
db, err := sql.Open("mysql", config.Datasource)
if err != nil {
applogger.Error("Datasource open err: %v", err)
return
}
DB = db
return
}

65
internal/data/mysqlbusiness/addinfo.go

@ -0,0 +1,65 @@
package mysqlbusiness
import (
"strconv"
"time"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/sqlmodel"
)
// SaveBoUserSms
func SaveBoUserSms(userSms sqlmodel.BoUserSms) error {
boUserSms := &sqlmodel.BoUserSms{
From: userSms.From,
To: userSms.To,
Message: userSms.Message,
TaskId: userSms.TaskId,
MessageResult: userSms.MessageResult,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if _, err := data.Engine.Table("bo_user_sms").Insert(boUserSms); err != nil {
applogger.Error("SaveBoUserSms info err: %v", err)
return err
}
return nil
}
// SaveBoUsers
func SaveBoUsers(phoneNumber string, password string, InvitationCode string) (string, error) {
token, err := internal.GetToken()
if err != nil {
applogger.Error("select token err:%v", err)
return "生成token失败,请联系管理员", err
}
uid := internal.Captcha(10)
phone, err := strconv.Atoi(phoneNumber)
if err != nil {
applogger.Error("Atoi err: %v", err)
return "电话号码解析失败,请联系管理员", err
}
bom := sqlmodel.BoUsers{
Uid: uid,
Phonenumber: int64(phone),
Loginpassword: password,
Invitecode: InvitationCode,
Accesstoken: token,
Status: 1,
Addtime: time.Now(),
Updatetime: time.Now(),
}
checkInt, err := data.Engine.Table("bo_users").Insert(&bom)
if err != nil {
applogger.Error("SaveBoUsers Insert err: %v", err)
return "", err
}
applogger.Debug("新增数据:%v", checkInt)
if checkInt == 0 {
return "注册用户失败", nil
}
return "注册用户成功", nil
}

112
internal/data/mysqlbusiness/getinfo.go

@ -0,0 +1,112 @@
package mysqlbusiness
import (
"strconv"
"wss-pool/internal"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/sqlmodel"
)
// GetBoUserOptionalStocksNew
func GetBoUserOptionalStocksNew(bourseType, systemBoursesId int, userId int64) ([]sqlmodel.BoUserOptionalStocks, error) {
var bom []sqlmodel.BoUserOptionalStocks
if err := data.Engine.Table("bo_user_optional_stocks").
Where("bourseType = ?", bourseType).
Where("systemBoursesId = ?", systemBoursesId).
Where("userId = ?", userId).
Desc("id").
Find(&bom); err != nil {
applogger.Error("GetBoUserOptionalStocksNew find info err: %v", err)
return nil, err
}
return bom, nil
}
// GetBoUsers
func GetBoUsers(token string) (bool, int64, error) {
var bom []sqlmodel.BoUsers
err := data.Engine.Table("bo_users").
Where("accessToken=?", token).
Where("status=1").
Where("deleteTime is null").
Find(&bom)
if err != nil {
applogger.Error("GetBoUsers info err: %v", err)
return false, 0, err
}
for _, value := range bom {
return true, value.Id, nil
}
return false, 0, nil
}
// GetBoUsersByPhoneNumber
func GetBoUsersByPhoneNumber(phoneNumber string) (string, error) {
phone, err := strconv.Atoi(phoneNumber)
if err != nil {
applogger.Error("Atoi err: %v", err)
return "电话号码解析失败,请联系管理员", err
}
var bom []sqlmodel.BoUsers
err = data.Engine.Table("bo_users").
Where("phoneNumber=?", phone).
Where("status=1").
Where("deleteTime is null").
Find(&bom)
if err != nil {
applogger.Error("GetBoUsersByPhoneNumber info err: %v", err)
return "注册异常请联系管理员", err
}
applogger.Debug("查询数据信息:", bom)
if len(bom) > 0 {
return "用户已经存在", nil
}
return "", nil
}
// GetBoUsersByPhoneAndPassWord
func GetBoUsersByPhoneAndPassWord(phoneNumber string, password string) (string, error) {
token, err := internal.GetToken()
if err != nil {
applogger.Error("select token err:%v", err)
return "生成token失败,请联系管理员", err
}
phone, err := strconv.Atoi(phoneNumber)
if err != nil {
applogger.Error("Atoi err: %v", err)
return "电话号码解析失败,请联系管理员", err
}
var u sqlmodel.UsersJson
query := "select id,uid,accessToken from bo_users where phoneNumber=? and loginPassword=? and status=1 and deleteTime is null limit 1"
if err := data.DB.QueryRow(query, phone, password).Scan(&u.Id, &u.Uid, &u.AccessToken); err != nil {
applogger.Error("QueryRow err:%v", err)
if err.Error() == "sql: no rows in result set" {
return "用户不存在", err
}
return "登录异常请联系管理员", err
}
if err = UpdateBoUsersTokenById(u.Id, token); err != nil {
applogger.Error("update info err: %v", err)
return "更新token失败,请联系管理员", err
}
return token, nil
}
func GetBoUserTerminalEquipments(userId int64) error {
var bom []sqlmodel.BoUserTerminalEquipments
if err := data.Engine.Table("bo_user_terminal_equipments").Where("userId = ?", userId).Find(&bom); err != nil {
applogger.Error("SaveBoUserSms info err: %v", err)
return err
}
applogger.Debug("查询数据信息:%v", bom)
return nil
}

102
internal/data/mysqlbusiness/updateinfo.go

@ -0,0 +1,102 @@
package mysqlbusiness
import (
"go.uber.org/zap"
"strconv"
"wss-pool/internal/data"
"wss-pool/logging/applogger"
"wss-pool/pkg/model/sqlmodel"
)
// UpdateBoUserSms
func UpdateBoUserSms(userSms sqlmodel.BoUserSms) error {
_, err := data.Engine.Table("bo_user_sms").
Where("`from` = ?", userSms.From).
Where("`to` = ?", userSms.To).
Where("task_id = ?", userSms.TaskId).
Where("message = ?", userSms.Message).
Update(&userSms)
if err != nil {
applogger.Error("UpdateBoUserSms info err:%v", err)
return err
}
return nil
}
// UpdateBoUsersById
func UpdateBoUsersById(phoneNumber string, password string) (string, error) {
phone, err := strconv.Atoi(phoneNumber)
if err != nil {
applogger.Error("Atoi err: %v", err)
return "电话号码解析失败,请联系管理员", err
}
sqlStr := "UPDATE bo_users set loginPassword=? where phoneNumber=? and status=1"
_, err = data.DB.Exec(sqlStr, password, phone)
if err != nil {
applogger.Error("Login User verification failed.", zap.Error(err))
return "修改密码异常请联系管理员", err
}
return "密码修改成功", nil
}
// UpdateBoUsersTokenById
func UpdateBoUsersTokenById(id int64, accessToken string) error {
sqlStr := "UPDATE bo_users set accessToken=? where id=? and status=1"
_, err := data.DB.Exec(sqlStr, accessToken, id)
if err != nil {
applogger.Error("Login User verification failed.", zap.Error(err))
return err
}
return nil
}
// UpdateBoUsersById
func UpdateBoUsersPassWordByPhoneNumber(phoneNumber string, password string) (string, error) {
phone, err := strconv.Atoi(phoneNumber)
if err != nil {
applogger.Error("Atoi err: %v", err)
return "电话号码解析失败,请联系管理员", err
}
//sqlStr := "UPDATE bo_users set loginPassword=? where phoneNumber=? and status=1"
//_, err = data.DB.Exec(sqlStr, password, phone)
//if err != nil {
// applogger.Error("Login User verification failed.", zap.Error(err))
// return "修改密码异常请联系管理员", err
//}
bom := sqlmodel.BoUsers{
Loginpassword: password,
}
checkInt, err := data.Engine.Table("bo_users").
Where("phoneNumber=?", phone).
Where("status=1").
Update(&bom)
if err != nil {
applogger.Error("UpdateBoUsersPassWordByPhoneNumber info err: %v", err)
return "修改密码异常请联系管理员", err
}
if checkInt == 0 {
return "修改密码失败", nil
}
return "修改密码成功", nil
}
// UpdateBoUsersById
func UpdateBoUsersPhoneNumberById(phoneNumber string, id int64) (string, error) {
phone, err := strconv.Atoi(phoneNumber)
if err != nil {
applogger.Error("Atoi err: %v", err)
return "电话号码解析失败,请联系管理员", err
}
sqlStr := "UPDATE bo_users set phoneNumber=? where id=? and status=1"
_, err = data.DB.Exec(sqlStr, phone, id)
if err != nil {
applogger.Error("Login User verification failed.", zap.Error(err))
return "设置手机号异常请联系管理员", err
}
return "设置手机号成功", err
}

61
internal/gzip/gzipanalysis.go

@ -0,0 +1,61 @@
package gzip
import (
"bytes"
"compress/gzip"
"io/ioutil"
)
// 使用gzip解压缩数据
func DecompressData(compressedData []byte) ([]byte, error) {
buf := bytes.NewReader(compressedData)
decompressor, err := gzip.NewReader(buf)
if err != nil {
return nil, err
}
defer decompressor.Close()
result, err := ioutil.ReadAll(decompressor)
if err != nil {
return nil, err
}
return result, nil
}
func GZipDecompress(input []byte) (string, error) {
buf := bytes.NewBuffer(input)
reader, gzipErr := gzip.NewReader(buf)
if gzipErr != nil {
return "", gzipErr
}
defer reader.Close()
result, readErr := ioutil.ReadAll(reader)
if readErr != nil {
return "", readErr
}
return string(result), nil
}
func GZipCompress(input string) ([]byte, error) {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
_, err := gz.Write([]byte(input))
if err != nil {
return nil, err
}
err = gz.Flush()
if err != nil {
return nil, err
}
err = gz.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

195
internal/httprequest.go

@ -0,0 +1,195 @@
package internal
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"strings"
"wss-pool/config"
"wss-pool/logging/applogger"
"wss-pool/logging/perflogger"
)
var (
ResultStr = ""
QueryError = "Query failed"
QuerySuccess = "query was successful"
QueryToken = "Token failure"
ParameterError = "参数错误"
PhoneError = "电话号码不正确"
PassWordError = "密码不正确"
TokenError = "登录成功并且生成新的token"
UserIdError = "用户Id不正确"
CodeError = "验证码不正确"
)
func HttpGet(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
applogger.Error("ioutil.ReadAll err: %v", err)
return "", err
}
return string(result), err
}
// 统一请求Get方法
func HttpGetApi(url string) (map[string]interface{}, error) {
logger := perflogger.GetInstance()
logger.Start()
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
//logger.StopAndLog("GET", url)
var status map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
applogger.Error("NewDecoder err: %v", err)
return nil, err
}
return status, err
}
func GetWithHeader(url string, headers map[string]string) (map[string]interface{}, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
for key, header := range headers {
req.Header.Set(key, header)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
applogger.Error("select err: %v", err)
return nil, err
}
if strings.Contains(string(result), "html") {
return nil, nil
}
var status map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&status); err != nil {
applogger.Error("json NewDecoder err: %v", err)
return nil, err
}
return status, nil
}
// 股票静态数据接入
func HttpGetDo(url string) (string, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
applogger.Error("http NewRequest err: %v", err)
return "", err
}
req.Header.Add("X-RapidAPI-Key", config.Config.ShareGather.RapidApiKey)
req.Header.Add("X-RapidAPI-Host", config.Config.ShareGather.RapidApiHost)
res, err := http.DefaultClient.Do(req)
if err != nil {
applogger.Error("http DefaultClient Do err: %v", err)
return "", err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
applogger.Error("io ReadAll err: %v", err)
return "", err
}
return string(body), nil
}
func HttpGetDoNew(url string) (map[string]interface{}, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
applogger.Error("NewRequest err: %v", err)
return nil, err
}
req.Header.Add("X-RapidAPI-Key", config.Config.ShareGather.RapidApiKey)
req.Header.Add("X-RapidAPI-Host", config.Config.ShareGather.RapidApiHost)
res, err := http.DefaultClient.Do(req)
if err != nil {
applogger.Error("http DefaultClient Do err: %v", err)
return nil, err
}
defer res.Body.Close()
var status map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&status); err != nil {
applogger.Error("json NewDecoder err: %v", err)
return nil, err
}
return status, nil
}
func HttpPost(url string, body string) (string, error) {
resp, err := http.Post(url, "application/json", strings.NewReader(body))
if err != nil {
return "", err
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(result), err
}
func HttpPostFrom(url string, postData map[string]string) (string, error) {
fmt.Println(url)
payload := &bytes.Buffer{}
w := multipart.NewWriter(payload)
for k, v := range postData {
w.WriteField(k, v)
}
w.Close()
client := &http.Client{}
req, _ := http.NewRequest("POST", url, payload)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := client.Do(req)
if err != nil {
return "", err
}
data, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return string(data), err
}
// Unified return format
func GinResult(code int, value interface{}, msg string) interface{} {
return gin.H{
"code": code,
"data": value,
"message": msg,
}
}

194
internal/model/contractMarket.go

@ -0,0 +1,194 @@
package model
import (
"errors"
"gorm.io/gorm"
"time"
"wss-pool/internal/data"
)
const (
AlreadyReceived int = 2 //已领取
Unclaimed int = 1 //未领取
ModifyContract int = 0 //插针
Market int = 2 //现货
Contract int = 1 //合约
)
type ContractMarket struct {
ID int64 `gorm:"column:id;"json:"id"`
TradeName string `gorm:"column:trade_name;" json:"name"`
BeginTime string `gorm:"column:begin_time;" json:"begin_time"`
Step int `gorm:"column:step;" json:"step"`
EndTime string `gorm:"column:end_time;" json:"end_time"`
MaxPrice string `gorm:"column:max_price" json:"max_price"`
IsType int `gorm:"column:type;not null;type:tinyint(2);COMMENT:'0 插针 1 自发合约 2 自发现货 '"json:"type"`
IsGet int `gorm:"column:is_get;not null;type:tinyint(2);DEFAULT:1;COMMENT:'是否完成(1 未领取 2 已领取 ) '"json:"is_get"`
KeepDecimal int `gorm:"column:keep_decimal; '"json:"keep_decimal"`
Gorm *gorm.DB `gorm:"-" json:"-"`
}
func (this *ContractMarket) TableName() string {
return "bot_contract_market"
}
func NewContractMarket() *ContractMarket {
ContractMarket := &ContractMarket{}
ContractMarket.NewGorm()
return ContractMarket
}
func (this *ContractMarket) NewGorm() *ContractMarket {
this.Gorm = data.WebGorm.Table(this.TableName())
return this
}
func (this *ContractMarket) WhereBegin() *ContractMarket {
this.Gorm = this.Gorm.Table(this.TableName()).Where("begin_time >= ?", time.Now().Format("2006-01-02 15:04:05"))
return this
}
func (this *ContractMarket) WhereID() *ContractMarket {
if this.ID == 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ContractMarket{ID: this.ID})
return this
}
func (this *ContractMarket) WhereName() *ContractMarket {
if this.TradeName == "" {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ContractMarket{TradeName: this.TradeName})
return this
}
func (this *ContractMarket) WhereModifyContract() *ContractMarket {
this.Gorm = this.Gorm.Table(this.TableName()).Where("`bot_contract_market`.`type` = ?", ModifyContract)
return this
}
func (this *ContractMarket) WhereInID(ids []int64) *ContractMarket {
if len(ids) <= 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where("id in (?)", ids)
return this
}
func (this *ContractMarket) WhereIsTyep() *ContractMarket {
if this.IsType <= 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ContractMarket{IsType: this.IsType})
return this
}
func (this *ContractMarket) WhereIsGet() *ContractMarket {
if this.IsGet == 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ContractMarket{IsGet: this.IsGet})
return this
}
func (this *ContractMarket) Update(record *ContractMarket) *ContractMarket {
this.Gorm = this.Gorm.Updates(record)
return this
}
func (this *ContractMarket) Create() *ContractMarket {
this.Gorm = this.Gorm.Create(&this)
return this
}
func (this *ContractMarket) Joins(sql string) *ContractMarket {
this.Gorm = this.Gorm.Joins(sql)
return this
}
func (this *ContractMarket) Limit(limit int) *ContractMarket {
this.Gorm = this.Gorm.Limit(limit)
return this
}
func (this *ContractMarket) Offset(Offset int) *ContractMarket {
this.Gorm = this.Gorm.Offset(Offset)
return this
}
func (this *ContractMarket) First() *ContractMarket {
Accounts := ContractMarket{}
this.Gorm = this.Gorm.First(&Accounts)
return &Accounts
}
func (this *ContractMarket) Assign(ContractMarket *ContractMarket) *ContractMarket {
this.Gorm = this.Gorm.Assign(ContractMarket)
return this
}
func (this *ContractMarket) FirstOrCreate() *ContractMarket {
this.Gorm = this.Gorm.FirstOrCreate(&this)
return this
}
func (this *ContractMarket) Order(value interface{}) *ContractMarket {
this.Gorm = this.Gorm.Order(value)
return this
}
func (this *ContractMarket) Count() int64 {
var num int64
this.Gorm = this.Gorm.Count(&num)
return num
}
func (this *ContractMarket) Select(column string) *ContractMarket {
this.Gorm = this.Gorm.Select(column)
return this
}
func (this *ContractMarket) WhereTime(LoginAt string) *ContractMarket {
if LoginAt == "" {
this.Gorm.Error = errors.New("date require ")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where("login_at = ?", LoginAt)
return this
}
func (this *ContractMarket) Pluck(column string, value interface{}) *ContractMarket {
this.Gorm = this.Gorm.Pluck(column, value)
return this
}
func (this *ContractMarket) Find(list *[]ContractMarket) *ContractMarket {
this.Gorm = this.Gorm.Find(&list)
return this
}
func (this *ContractMarket) List() (result []ContractMarket) {
this.IsGet = Unclaimed
this.WhereIsTyep().WhereIsGet().WhereBegin().WhereName().Order("id asc").Find(&result)
return result
}
func (this *ContractMarket) ListModifyContract() (result []ContractMarket) {
this.IsGet = Unclaimed
this.Select("l.keep_decimal,bot_contract_market.*").WhereModifyContract().Joins("inner join bot_contract_list l on l.trade_name = bot_contract_market.trade_name").WhereIsGet().Order("id asc").Find(&result)
return result
}
func (this *ContractMarket) UpdateIsGet(ids []int64) error {
this.IsGet = Unclaimed
return this.WhereInID(ids).WhereIsGet().Update(&ContractMarket{IsGet: AlreadyReceived}).Gorm.Error
}
func (this *ContractMarket) UpdateIsGetOne() error {
this.IsGet = Unclaimed
return this.WhereID().WhereIsGet().Update(&ContractMarket{IsGet: AlreadyReceived}).Gorm.Error
}

193
internal/model/forexMarket.go

@ -0,0 +1,193 @@
package model
import (
"errors"
"gorm.io/gorm"
"time"
"wss-pool/internal/data"
)
const (
AlreadyReceivedForex int = 2 //已领取
UnclaimedForex int = 1 //未领取
ModifyForex int = 0 //插针
Forex int = 1 //外汇
)
type ForexMarket struct {
ID int64 `gorm:"column:id;"json:"id"`
TradeName string `gorm:"column:trade_name;" json:"name"`
BeginTime string `gorm:"column:begin_time;" json:"begin_time"`
Step int `gorm:"column:step;" json:"step"`
EndTime string `gorm:"column:end_time;" json:"end_time"`
MaxPrice string `gorm:"column:max_price" json:"max_price"`
IsType int `gorm:"column:type;not null;type:tinyint(2);COMMENT:'0 插针 1 自发合约 2 自发现货 '"json:"type"`
IsGet int `gorm:"column:is_get;not null;type:tinyint(2);DEFAULT:1;COMMENT:'是否完成(1 未领取 2 已领取 ) '"json:"is_get"`
KeepDecimal int `gorm:"column:keep_decimal; '"json:"keep_decimal"`
Gorm *gorm.DB `gorm:"-" json:"-"`
}
func (this *ForexMarket) TableName() string {
return "bot_forex_market"
}
func NewForexMarket() *ForexMarket {
ForexMarket := &ForexMarket{}
ForexMarket.NewGorm()
return ForexMarket
}
func (this *ForexMarket) NewGorm() *ForexMarket {
this.Gorm = data.WebGorm.Table(this.TableName())
return this
}
func (this *ForexMarket) WhereBegin() *ForexMarket {
this.Gorm = this.Gorm.Table(this.TableName()).Where("begin_time >= ?", time.Now().Format("2006-01-02 15:04:05"))
return this
}
func (this *ForexMarket) WhereID() *ForexMarket {
if this.ID == 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ForexMarket{ID: this.ID})
return this
}
func (this *ForexMarket) WhereName() *ForexMarket {
if this.TradeName == "" {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ForexMarket{TradeName: this.TradeName})
return this
}
func (this *ForexMarket) WhereModifyForex() *ForexMarket {
this.Gorm = this.Gorm.Table(this.TableName()).Where("`bot_forex_market`.`type` = ?", ModifyForex)
return this
}
func (this *ForexMarket) WhereInID(ids []int64) *ForexMarket {
if len(ids) <= 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where("id in (?)", ids)
return this
}
func (this *ForexMarket) WhereIsTyep() *ForexMarket {
if this.IsType <= 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ForexMarket{IsType: this.IsType})
return this
}
func (this *ForexMarket) WhereIsGet() *ForexMarket {
if this.IsGet == 0 {
this.Gorm.Error = errors.New("param is null")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where(&ForexMarket{IsGet: this.IsGet})
return this
}
func (this *ForexMarket) Update(record *ForexMarket) *ForexMarket {
this.Gorm = this.Gorm.Updates(record)
return this
}
func (this *ForexMarket) Create() *ForexMarket {
this.Gorm = this.Gorm.Create(&this)
return this
}
func (this *ForexMarket) Joins(sql string) *ForexMarket {
this.Gorm = this.Gorm.Joins(sql)
return this
}
func (this *ForexMarket) Limit(limit int) *ForexMarket {
this.Gorm = this.Gorm.Limit(limit)
return this
}
func (this *ForexMarket) Offset(Offset int) *ForexMarket {
this.Gorm = this.Gorm.Offset(Offset)
return this
}
func (this *ForexMarket) First() *ForexMarket {
Accounts := ForexMarket{}
this.Gorm = this.Gorm.First(&Accounts)
return &Accounts
}
func (this *ForexMarket) Assign(ForexMarket *ForexMarket) *ForexMarket {
this.Gorm = this.Gorm.Assign(ForexMarket)
return this
}
func (this *ForexMarket) FirstOrCreate() *ForexMarket {
this.Gorm = this.Gorm.FirstOrCreate(&this)
return this
}
func (this *ForexMarket) Order(value interface{}) *ForexMarket {
this.Gorm = this.Gorm.Order(value)
return this
}
func (this *ForexMarket) Count() int64 {
var num int64
this.Gorm = this.Gorm.Count(&num)
return num
}
func (this *ForexMarket) Select(column string) *ForexMarket {
this.Gorm = this.Gorm.Select(column)
return this
}
func (this *ForexMarket) WhereTime(LoginAt string) *ForexMarket {
if LoginAt == "" {
this.Gorm.Error = errors.New("date require ")
return this
}
this.Gorm = this.Gorm.Table(this.TableName()).Where("login_at = ?", LoginAt)
return this
}
func (this *ForexMarket) Pluck(column string, value interface{}) *ForexMarket {
this.Gorm = this.Gorm.Pluck(column, value)
return this
}
func (this *ForexMarket) Find(list *[]ForexMarket) *ForexMarket {
this.Gorm = this.Gorm.Find(&list)
return this
}
func (this *ForexMarket) List() (result []ForexMarket) {
this.IsGet = UnclaimedForex
this.WhereIsTyep().WhereIsGet().WhereBegin().WhereName().Order("id asc").Find(&result)
return result
}
func (this *ForexMarket) ListModifyForex() (result []ForexMarket) {
this.IsGet = UnclaimedForex
this.Select("l.keep_decimal,bot_forex_market.*").WhereModifyForex().Joins("inner join bot_forex_list l on l.trade_name = bot_forex_market.trade_name").WhereIsGet().Order("id asc").Find(&result)
return result
}
func (this *ForexMarket) UpdateIsGet(ids []int64) error {
this.IsGet = UnclaimedForex
return this.WhereInID(ids).WhereIsGet().Update(&ForexMarket{IsGet: AlreadyReceivedForex}).Gorm.Error
}
func (this *ForexMarket) UpdateIsGetOne() error {
this.IsGet = UnclaimedForex
return this.WhereID().WhereIsGet().Update(&ForexMarket{IsGet: AlreadyReceivedForex}).Gorm.Error
}

44
internal/model/pingmessage.go

@ -0,0 +1,44 @@
package model
import (
"encoding/json"
"fmt"
"wss-pool/logging/applogger"
)
type PingMessage struct {
Ping int64 `json:"ping"`
}
type SymbolMessage struct {
Type string `json:"type"`
Symbol string `json:"symbol"`
}
func ParsePingMessage(message string) *PingMessage {
result := PingMessage{}
err := json.Unmarshal([]byte(message), &result)
if err != nil {
return nil
}
return &result
}
func SubMessage(message string) *SymbolMessage {
result := SymbolMessage{}
err := json.Unmarshal([]byte(message), &result)
applogger.Info("ws param %v", message)
if err != nil {
fmt.Println("subMessage", err)
return nil
}
return &result
}
func ReturnValue(str string) string {
pongMsg := fmt.Sprintf("{\"type\": \"%v\"}", str)
return pongMsg
}

22
internal/model/pingv1message.go

@ -0,0 +1,22 @@
package model
import "encoding/json"
type PingV1Message struct {
Op string `json:"op"`
Timestamp int64 `json:"ts"`
}
func (p *PingV1Message) IsPing() bool {
return p != nil && p.Op == "ping" && p.Timestamp != 0
}
func ParsePingV1Message(message string) *PingV1Message {
result := PingV1Message{}
err := json.Unmarshal([]byte(message), &result)
if err != nil {
return nil
}
return &result
}

24
internal/model/pingv2message.go

@ -0,0 +1,24 @@
package model
import "encoding/json"
type PingV2Message struct {
Action string `json:"action"`
Data *struct {
Timestamp int64 `json:"ts"`
}
}
func (p *PingV2Message) IsPing() bool {
return p != nil && p.Action == "ping" && p.Data.Timestamp != 0
}
func ParsePingV2Message(message string) *PingV2Message {
result := PingV2Message{}
err := json.Unmarshal([]byte(message), &result)
if err != nil {
return nil
}
return &result
}

18
internal/model/websocketv1authenticationrequest.go

@ -0,0 +1,18 @@
package model
type WebSocketV1AuthenticationRequest struct {
Op string `json:"op"`
AccessKeyId string
SignatureMethod string
SignatureVersion string
Timestamp string
Signature string
}
func (p *WebSocketV1AuthenticationRequest) Init() *WebSocketV1AuthenticationRequest {
p.Op = "auth"
p.SignatureMethod = "HmacSHA256"
p.SignatureVersion = "2"
return p
}

28
internal/model/websocketv2authenticationrequest.go

@ -0,0 +1,28 @@
package model
type WebSocketV2AuthenticationRequest struct {
Action string `json:"action"`
Ch string `json:"ch"`
Params *Params `json:"params"`
}
type Params struct {
AuthType string `json:"authType"`
AccessKey string `json:"accessKey"`
SignatureMethod string `json:"signatureMethod"`
SignatureVersion string `json:"signatureVersion"`
Timestamp string `json:"timestamp"`
Signature string `json:"signature"`
}
func (p *WebSocketV2AuthenticationRequest) Init() *WebSocketV2AuthenticationRequest {
p.Action = "req"
p.Ch = "auth"
p.Params = new(Params)
p.Params.AuthType = "api"
p.Params.SignatureMethod = "HmacSHA256"
p.Params.SignatureVersion = "2.1"
return p
}

48
internal/mq/redis.go

@ -0,0 +1,48 @@
package mq
import (
"github.com/go-redis/redis"
"wss-pool/logging/applogger"
)
var Rdb *redis.Client
const (
PublishKey = "websocket"
)
func init() {
Rdb = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
}
// Publish 发布消息到redis
// channel是发布的目标信道
// payload是要发布的消息内容
func Publish(channel string, payload string) error {
var err error
applogger.Debug("[Redis] publish [%s]: %s", channel, payload)
err = Rdb.Publish(channel, payload).Err()
if err != nil {
applogger.Error("[Redis] pulish error: %s", err.Error())
return err
}
return err
}
// Subscribe 订阅redis消息
// channel是订阅的目标信道
func Subscribe(channel string) (string, error) {
applogger.Info("[Redis] subscribe [%s]", channel)
sub := Rdb.Subscribe(channel)
msg, err := sub.ReceiveMessage()
if err != nil {
applogger.Error("[Redis] subscribe [%s]", channel)
return "", err
}
applogger.Debug("[Redis] subscribe [%s]: %s", channel, msg.String())
return msg.Payload, err
}

17
internal/mq/redis_test.go

@ -0,0 +1,17 @@
package mq
import (
"testing"
"time"
"wss-pool/logging/applogger"
)
// TestPublish 测试发布消息到redis
func TestPublish(t *testing.T) {
msg := "当前时间: " + time.Now().Format("15:04:05")
applogger.Debug("[publish] msg: %s", msg)
err := Publish(PublishKey, msg)
if err != nil {
applogger.Error("publish error: %s", err.Error())
}
}

89
internal/paramstr.go

@ -0,0 +1,89 @@
package internal
import (
"fmt"
"github.com/gin-gonic/gin"
"strconv"
"strings"
"time"
"wss-pool/pkg/model"
)
// Analysis of spot parameters
func StrParamStr(c *gin.Context) (*model.SpotsModel, error) {
var paramModel model.SpotsModel
err := c.BindJSON(&paramModel)
if err != nil {
return &paramModel, err
}
return &paramModel, err
}
// Contract parameter analysis
func ContractStr(c *gin.Context) (*model.ContractModel, error) {
var paramModel model.ContractModel
err := c.BindJSON(&paramModel)
if err != nil {
return &paramModel, err
}
return &paramModel, err
}
// Empty character replacement
func ReplaceStr(value string) string {
return strings.Replace(value, " ", "", 1)
}
// Integer conversion
func IntegerInit(value string) int {
in, err := strconv.Atoi(value)
if err != nil {
return 0
}
return in
}
// TimeMaoSendToString int64 - string
func TimeMaoSendToString(i int64) string {
layout := "2006-01-02 15:04:05.000"
t := time.Unix(0, i*int64(time.Microsecond))
return t.Format(layout)
}
// TimeDateToMaoSend time - int64
func TimeDateToMaoSend(t time.Time) int64 {
now := time.Now()
fmt.Println(now)
return now.UnixMicro()
}
// TimeStringToTime string - time
func TimeStringToTime(t string) time.Time {
timeLayout := "2006-01-02 15:04:05"
loc, err := time.LoadLocation("Local")
if err != nil {
return time.Time{}
}
theTime, err := time.ParseInLocation(timeLayout, t, loc)
if err != nil {
return time.Time{}
}
return theTime
}
// TimeStringToIn64 string - int64
func TimeStringToIn64(t string) int64 {
timeLayout := "2006-01-02 15:04:05"
loc, err := time.LoadLocation("Local")
if err != nil {
return int64(0)
}
theTime, err := time.ParseInLocation(timeLayout, t, loc)
if err != nil {
return int64(0)
}
return theTime.UnixMicro()
}

85
internal/pubsub/pubsub.go

@ -0,0 +1,85 @@
package pubsub
import (
"sync"
"time"
)
type (
subscriber chan interface{} //订阅者,类型为管道
topicFunc func(v interface{}) bool //主题,是一个过滤器函数
)
// 发布者对象
type publisher struct {
m sync.RWMutex //读写锁
buffer int //订阅队列缓存大小
timeout time.Duration //发布超时时间
subscribers map[subscriber]topicFunc //订阅者信息
}
// 构建一个新的发布者对象
func NewPublisher(buffer int, publishTimeout time.Duration) *publisher {
return &publisher{
m: sync.RWMutex{},
buffer: buffer,
timeout: publishTimeout,
subscribers: make(map[subscriber]topicFunc),
}
}
// 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *publisher) SubscriberTopic(topic topicFunc) chan interface{} {
ch := make(chan interface{}, p.buffer)
p.m.Lock()
defer p.m.Unlock()
p.subscribers[ch] = topic
return ch
}
// 添加一个订阅者,订阅所有主题
func (p *publisher) SubscriberAllTopic() chan interface{} {
return p.SubscriberTopic(nil)
}
// 退出订阅
func (p *publisher) Exict(sub chan interface{}) {
p.m.Lock()
defer p.m.Unlock()
delete(p.subscribers, sub)
close(sub)
}
// 关闭发布者对象,同时关闭所有订阅者管道
func (p *publisher) Close() {
p.m.Lock()
defer p.m.Unlock()
for sub := range p.subscribers {
close(sub)
delete(p.subscribers, sub)
}
}
// 发布一个主题
func (p *publisher) Publish(v interface{}) {
p.m.RLock()
defer p.m.RUnlock()
wg := sync.WaitGroup{}
for sub, topic := range p.subscribers { //向所有的订阅者管道发送主题
wg.Add(1)
go p.SendTopic(sub, topic, v, &wg)
}
}
// 向订阅者发送主题
func (p *publisher) SendTopic(sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup) {
defer wg.Done()
if topic != nil && !topic(v) { //订阅者未订阅这个主题,不发送
return
}
select {
case sub <- v:
case <-time.After(p.timeout): //超时后就不再发送
}
}

42
internal/pubsub/pubsub_test.go

@ -0,0 +1,42 @@
package pubsub
import (
"fmt"
"strings"
"testing"
"time"
)
func Test_NewPublisher(t *testing.T) {
//初始化一个发布者对象
publisher := NewPublisher(3, 5*time.Second)
//创建一个订阅所有主题的订阅者
all := publisher.SubscriberAllTopic()
//创建一个订阅golang主题的订阅者
golang := publisher.SubscriberTopic(func(v interface{}) bool {
if s, ok := v.(string); ok {
return strings.Contains(s, "golang")
}
return false
})
//发布2条主题
publisher.Publish("hello world")
publisher.Publish("hello golang")
go func() {
for i := range all {
fmt.Println("all:", i)
}
}()
go func() {
for i := range golang {
fmt.Println("golang:", i)
}
}()
time.Sleep(5 * time.Second)
publisher.Close()
fmt.Println(<-all, <-golang) //发布者对象关闭后读取的都是nil
}

233
internal/redis/redis.go

@ -0,0 +1,233 @@
package redis
import (
"fmt"
"github.com/go-redis/redis"
"os"
"strconv"
"time"
"wss-pool/config"
"wss-pool/logging/applogger"
)
var RedisClient *redis.Client
var RedisClientMap = map[string]*redis.Client{}
// RedisInit init Redis
func RedisInit(db int) *redis.Client {
addr := fmt.Sprintf("%v:%v", config.Config.Redis.Server, config.Config.Redis.Port)
client := redis.NewClient(&redis.Options{
Addr: addr,
DB: db, // use default DB
Password: config.Config.Redis.PassWord, // no password set
})
_, err := client.Ping().Result()
if err != nil {
applogger.Error("Failed to connect to Redis, terminating startup err: %v", err)
return nil
} else {
applogger.Info("redis init success")
}
return client
}
func RedisInitMap(dbs []string) {
RedisClientMap = make(map[string]*redis.Client)
for key, db := range dbs {
dbInt, _ := strconv.Atoi(db) // 表库
server := config.Config.Redis.Server // Ip
password := config.Config.Redis.PassWord // 密码
addr := fmt.Sprintf("%v:%v", server, config.Config.Redis.Port) // 0.0.0.0:6379
client := redis.NewClient(&redis.Options{
Addr: addr,
DB: dbInt,
Password: password,
})
_, err := client.Ping().Result()
if err != nil {
os.Exit(dbInt)
} else {
applogger.Info("redis init success")
}
if key == 0 {
RedisClient = client
}
RedisClientMap[server] = client
}
}
// TODO: 初始化多个Redis客户端
func RedisInitMapList(addrList map[string]string) {
RedisClientMap = make(map[string]*redis.Client)
for addr, password := range addrList {
client := redis.NewClient(&redis.Options{
Addr: addr,
DB: 0,
Password: password,
})
_, err := client.Ping().Result()
if err != nil {
os.Exit(0)
} else {
applogger.Info("redis init success")
}
RedisClientMap[addr] = client
}
}
// Get_Cache_Data Query data through key
func Get_Cache_Data(key string) (string, error) {
rge, err := RedisClient.Get(key).Result()
if err != nil {
applogger.Error("Redis Get err: %v", err)
return "", err
}
return rge, nil
}
// Get_Cache_Data Query data through key
func Get_Cache_Byte(key string) ([]byte, error) {
rge, err := RedisClient.Get(key).Bytes()
if err != nil {
applogger.Error("Redis Get err: %v", err)
return rge, err
}
return rge, nil
}
// Set_Cache_Data Set the key lifecycle
func Set_Cache_Data(key string, value interface{}, td int) error {
if err := RedisClient.Set(key, value, time.Duration(td)*time.Minute).Err(); err != nil {
applogger.Error("Redis Set err: %v", err)
return err
}
return nil
}
// Get_Cache_Keys Query all keys
func Get_Cache_Keys() ([]string, error) {
rge, err := RedisClient.Keys("*").Result()
if err != nil {
applogger.Error("Redis Get err: %v", err)
return []string{}, err
}
return rge, nil
}
// Get_Cache_Count Query the total number of Redis
func Get_Cache_Count(key string) (int64, error) {
rge, err := RedisClient.DbSize().Result()
if err != nil {
applogger.Error("RedisClient DbSize err:%v", err)
return rge, err
}
return rge, nil
}
// Set_Cache_Value persistent data
func Set_Cache_Value(key, value string) error {
if err := RedisClient.Set(key, value, 0).Err(); err != nil {
applogger.Error("Redis Set err: %v", err)
return err
}
return nil
}
func HsetMap(key, field string, value interface{}) {
for k, db := range RedisClientMap {
applogger.Info("key", key, "field", field, "value", value, "DB", k)
err := db.HSet(key, field, value).Err()
if err != nil {
fmt.Println("db", k, "存储失败:", err)
}
}
}
func PublishMap(channel string, message interface{}) {
for k, db := range RedisClientMap {
applogger.Info("channel", channel, "DB", k)
err := db.Publish(channel, message).Err()
if err != nil {
fmt.Println("db", k, "存储失败:", err)
}
}
}
func ScanMap(prefix string) map[string][]string {
scanMap := make(map[string][]string)
for k, db := range RedisClientMap {
var keys []string
var err error
keys, err = db.Keys(prefix + "*").Result()
if err != nil {
applogger.Error(err.Error())
}
scanMap[k] = keys
}
return scanMap
}
func HGetAllMap(db string, key string) (map[string]string, error) {
return RedisClientMap[db].HGetAll(key).Result()
}
func Hset(key, field string, value interface{}) error {
applogger.Info("key", key, "field", field, "value", value)
err := RedisClient.HSet(key, field, value).Err()
if err != nil {
fmt.Println("存储失败:", err)
return err
}
return nil
}
func Expire(key string, expiration time.Duration) error {
err := RedisClient.Expire(key, expiration).Err()
if err != nil {
fmt.Println("设置过期时间:", err)
return err
}
return nil
}
func Hget(key, field string) (string, error) {
value, err := RedisClient.HGet(key, field).Result()
if err != nil {
return value, err
}
return value, nil
}
func HGetNew(key, field string, red *redis.Client) (string, error) {
value, err := red.HGet(key, field).Result()
if err != nil {
return value, err
}
return value, nil
}
func HDel(key string, fields string) error {
if err := RedisClient.HDel(key, fields).Err(); err != nil {
fmt.Println("del error:", err)
return err
}
return nil
}
func HGetAll(key string) (map[string]string, error) {
return RedisClient.HGetAll(key).Result()
}
func Scan(prefix string) []string {
var keys []string
var err error
keys, err = RedisClient.Keys(prefix + "*").Result()
if err != nil {
applogger.Error(err.Error())
}
//fmt.Println(keys)
return keys
}

60
internal/requestbuilder/privateurlbuilder.go

@ -0,0 +1,60 @@
package requestbuilder
import (
"fmt"
"net/url"
"time"
"wss-pool/pkg/model"
)
type PrivateUrlBuilder struct {
host string
akKey string
akValue string
smKey string
smValue string
svKey string
svValue string
tKey string
signer *Signer
}
func (p *PrivateUrlBuilder) Init(accessKey string, secretKey string, host string) *PrivateUrlBuilder {
p.akKey = "AccessKeyId"
p.akValue = accessKey
p.smKey = "SignatureMethod"
p.smValue = "HmacSHA256"
p.svKey = "SignatureVersion"
p.svValue = "2"
p.tKey = "Timestamp"
p.host = host
p.signer = new(Signer).Init(secretKey)
return p
}
func (p *PrivateUrlBuilder) Build(method string, path string, request *model.GetRequest) string {
time := time.Now().UTC()
return p.BuildWithTime(method, path, time, request)
}
func (p *PrivateUrlBuilder) BuildWithTime(method string, path string, utcDate time.Time, request *model.GetRequest) string {
time := utcDate.Format("2006-01-02T15:04:05")
req := new(model.GetRequest).InitFrom(request)
req.AddParam(p.akKey, p.akValue)
req.AddParam(p.smKey, p.smValue)
req.AddParam(p.svKey, p.svValue)
req.AddParam(p.tKey, time)
parameters := req.BuildParams()
signature := p.signer.Sign(method, p.host, path, parameters)
url := fmt.Sprintf("https://%s%s?%s&Signature=%s", p.host, path, parameters, url.QueryEscape(signature))
return url
}

25
internal/requestbuilder/publicurlbuilder.go

@ -0,0 +1,25 @@
package requestbuilder
import (
"fmt"
"wss-pool/pkg/model"
)
type PublicUrlBuilder struct {
host string
}
func (p *PublicUrlBuilder) Init(host string) *PublicUrlBuilder {
p.host = host
return p
}
func (p *PublicUrlBuilder) Build(path string, request *model.GetRequest) string {
if request != nil {
result := fmt.Sprintf("https://%s%s?%s", p.host, path, request.BuildParams())
return result
} else {
result := fmt.Sprintf("https://%s%s", p.host, path)
return result
}
}

41
internal/requestbuilder/signer.go

@ -0,0 +1,41 @@
package requestbuilder
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"strings"
)
type Signer struct {
key []byte
}
func (p *Signer) Init(key string) *Signer {
p.key = []byte(key)
return p
}
func (p *Signer) Sign(method string, host string, path string, parameters string) string {
if method == "" || host == "" || path == "" || parameters == "" {
return ""
}
var sb strings.Builder
sb.WriteString(method)
sb.WriteString("\n")
sb.WriteString(host)
sb.WriteString("\n")
sb.WriteString(path)
sb.WriteString("\n")
sb.WriteString(parameters)
return p.sign(sb.String())
}
func (p *Signer) sign(payload string) string {
hash := hmac.New(sha256.New, p.key)
hash.Write([]byte(payload))
result := base64.StdEncoding.EncodeToString(hash.Sum(nil))
return result
}

65
internal/requestbuilder/websocketv1requestbuilder.go

@ -0,0 +1,65 @@
package requestbuilder
import (
"time"
"wss-pool/internal/model"
model2 "wss-pool/pkg/model"
)
type WebSocketV1RequestBuilder struct {
akKey string
akValue string
smKey string
smValue string
svKey string
svValue string
tKey string
tValue string
host string
path string
signer *Signer
}
func (p *WebSocketV1RequestBuilder) Init(accessKey string, secretKey string, host string, path string) *WebSocketV1RequestBuilder {
p.akKey = "AccessKeyId"
p.akValue = accessKey
p.smKey = "SignatureMethod"
p.smValue = "HmacSHA256"
p.svKey = "SignatureVersion"
p.svValue = "2"
p.tKey = "Timestamp"
p.host = host
p.path = path
p.signer = new(Signer).Init(secretKey)
return p
}
func (p *WebSocketV1RequestBuilder) Build() (string, error) {
time := time.Now().UTC()
return p.build(time)
}
func (p *WebSocketV1RequestBuilder) build(utcDate time.Time) (string, error) {
time := utcDate.Format("2006-01-02T15:04:05")
req := new(model2.GetRequest).Init()
req.AddParam(p.akKey, p.akValue)
req.AddParam(p.smKey, p.smValue)
req.AddParam(p.svKey, p.svValue)
req.AddParam(p.tKey, time)
signature := p.signer.Sign("GET", p.host, p.path, req.BuildParams())
auth := new(model.WebSocketV1AuthenticationRequest).Init()
auth.AccessKeyId = p.akValue
auth.Timestamp = time
auth.Signature = signature
result, err := model2.ToJson(auth)
return result, err
}

65
internal/requestbuilder/websocketv2requestbuilder.go

@ -0,0 +1,65 @@
package requestbuilder
import (
"time"
"wss-pool/internal/model"
model2 "wss-pool/pkg/model"
)
type WebSocketV2RequestBuilder struct {
akKey string
akValue string
smKey string
smValue string
svKey string
svValue string
tKey string
tValue string
host string
path string
signer *Signer
}
func (p *WebSocketV2RequestBuilder) Init(accessKey string, secretKey string, host string, path string) *WebSocketV2RequestBuilder {
p.akKey = "accessKey"
p.akValue = accessKey
p.smKey = "signatureMethod"
p.smValue = "HmacSHA256"
p.svKey = "signatureVersion"
p.svValue = "2.1"
p.tKey = "timestamp"
p.host = host
p.path = path
p.signer = new(Signer).Init(secretKey)
return p
}
func (p *WebSocketV2RequestBuilder) Build() (string, error) {
time := time.Now().UTC()
return p.build(time)
}
func (p *WebSocketV2RequestBuilder) build(utcDate time.Time) (string, error) {
time := utcDate.Format("2006-01-02T15:04:05")
req := new(model2.GetRequest).Init()
req.AddParam(p.akKey, p.akValue)
req.AddParam(p.smKey, p.smValue)
req.AddParam(p.svKey, p.svValue)
req.AddParam(p.tKey, time)
signature := p.signer.Sign("GET", p.host, p.path, req.BuildParams())
auth := new(model.WebSocketV2AuthenticationRequest).Init()
auth.Params.AccessKey = p.akValue
auth.Params.Timestamp = time
auth.Params.Signature = signature
result, err := model2.ToJson(auth)
return result, err
}

55
logging/applogger/applogger.go

@ -0,0 +1,55 @@
package applogger
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
var sugaredLogger *zap.SugaredLogger
var atomicLevel zap.AtomicLevel
func init() {
encoderCfg := zapcore.EncoderConfig{
TimeKey: "time",
MessageKey: "msg",
LevelKey: "level",
EncodeLevel: zapcore.CapitalColorLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
}
// define default level as debug level
atomicLevel = zap.NewAtomicLevel()
atomicLevel.SetLevel(zapcore.DebugLevel)
core := zapcore.NewCore(zapcore.NewConsoleEncoder(encoderCfg), os.Stdout, atomicLevel)
sugaredLogger = zap.New(core).Sugar()
}
func SetLevel(level zapcore.Level) {
atomicLevel.SetLevel(level)
}
func Fatal(template string, args ...interface{}) {
sugaredLogger.Fatalf(template, args...)
}
func Error(template string, args ...interface{}) {
sugaredLogger.Errorf(template, args...)
}
func Panic(template string, args ...interface{}) {
sugaredLogger.Panicf(template, args...)
}
func Warn(template string, args ...interface{}) {
sugaredLogger.Warnf(template, args...)
}
func Info(template string, args ...interface{}) {
sugaredLogger.Infof(template, args...)
}
func Debug(template string, args ...interface{}) {
sugaredLogger.Debugf(template, args...)
}

84
logging/perflogger/performancelogger.go

@ -0,0 +1,84 @@
package perflogger
import (
"fmt"
"log"
"os"
"strings"
"time"
)
// The global PerformanceLogger instance
var performanceLogger *PerformanceLogger
// The global switch for Performance logging
var logEnabled = false
type PerformanceLogger struct {
logger *log.Logger
enable bool
file *os.File
index int
start time.Time
}
// Enable performance logger and initialize the global instance
// This method should be called once
func Enable(enable bool) {
logEnabled = enable
if logEnabled && performanceLogger == nil {
performanceLogger = new(PerformanceLogger).init()
}
}
// Get the global PerformanceLogger instance
func GetInstance() *PerformanceLogger {
return performanceLogger
}
// Initialize the instance
func (p *PerformanceLogger) init() *PerformanceLogger {
if logEnabled {
var err error
fileName := time.Now().Format("20060102_150405.txt")
p.file, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatalln("Failed to open file: ", fileName)
}
p.logger = log.New(p.file, "", 0)
p.index = 1
}
return p
}
// Start timer
func (p *PerformanceLogger) Start() {
if logEnabled {
p.start = time.Now()
}
}
// Stop timer and output log
func (p *PerformanceLogger) StopAndLog(method string, url string) {
if logEnabled {
duration := time.Since(p.start).Milliseconds()
// Strip parameters
i := strings.IndexByte(url, '?')
var path string
if i > 0 {
path = url[0:i]
} else {
path = url
}
// Log the header before first record
if p.index == 1 {
p.logger.Println("Index, Duration(ms), URL")
}
p.logger.Println(fmt.Sprintf("%d, %d, %s %s", p.index, duration, method, path))
p.index++
}
}

137
pkg/bawssclient/candlestickwebsocketclient.go

@ -0,0 +1,137 @@
package bawssclient
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"strings"
"wss-pool/dictionary"
"wss-pool/logging/applogger"
"wss-pool/pkg/client/bawebsocketclientbase"
"wss-pool/pkg/model/market"
)
type BaKLineParam struct {
Method string `json:"method"`
Params []string `json:"params"`
ID int64 `json:"id"`
}
type BaKLineResponseK struct {
T int64 `json:"t"`
Ts int64 `json:"T"`
S string `json:"s"`
I string `json:"i"`
F int64 `json:"f"`
L interface{} `json:"L"`
O string `json:"o"`
C string `json:"c"`
H string `json:"h"`
l string `json:"l"`
V string `json:"v"`
N int `json:"n"`
X bool `json:"x"`
Vs string `json:"V"`
Q string `json:"Q"`
B string `json:"B"`
}
type BaKLineResponse struct {
E string `json:"e"` //事件类型
Es int64 `json:"E"` //事件时间
S string `json:"s"` //交易对
K BaKLineResponseK `json:"k"`
}
// Responsible to handle candlestick data from WebSocket
type CandlestickWebSocketClient struct {
bawebsocketclientbase.WebSocketClientBase
}
// Initializer
func (p *CandlestickWebSocketClient) Init(host string) *CandlestickWebSocketClient {
p.WebSocketClientBase.Init(host)
return p
}
// Request the full candlestick data according to specified criteria
//func (p *CandlestickWebSocketClient) Request(symbol string) {
// topic := fmt.Sprintf("market.%s.kline.%s", symbol, period)
// req := fmt.Sprintf("{\"req\": \"%s\", \"from\":%d, \"to\":%d, \"id\": \"%s\" }", topic, from, to, clientId)
//
// p.Send(req)
//
// applogger.Info("WebSocket requested, topic=%s, clientId=%s", topic, clientId)
//}
// Set callback handler
func (p *CandlestickWebSocketClient) SetHandler(
connectedHandler bawebsocketclientbase.ConnectedHandler,
responseHandler bawebsocketclientbase.ResponseHandler) {
p.WebSocketClientBase.SetHandler(connectedHandler, p.handleMessage, responseHandler)
}
// Request the full candlestick data according to specified criteria
// Subscribe candlestick data
func (p *CandlestickWebSocketClient) Subscribe(symbol string) {
topis := make([]string, 0)
for _, v := range dictionary.BaTimeCycle {
topis = append(topis, fmt.Sprintf("%susdt@kline_%s", symbol, v))
}
sub := BaKLineParam{
Method: "SUBSCRIBE",
Params: topis,
ID: bawebsocketclientbase.Randomnumber(),
}
data, _ := json.Marshal(sub)
p.Send(data)
applogger.Info("WebSocket subscribed, clientId=%s", sub.ID)
}
// Unsubscribe candlestick data
func (p *CandlestickWebSocketClient) UnSubscribe(symbol string) {
topis := make([]string, 0)
for _, v := range dictionary.BaTimeCycle {
topis = append(topis, fmt.Sprintf("%susdt@%s", symbol, v))
}
sub := BaKLineParam{
Method: "UNSUBSCRIBE",
Params: topis,
ID: bawebsocketclientbase.Randomnumber(),
}
data, _ := json.Marshal(sub)
p.Send(data)
applogger.Info("WebSocket unsubscribed, clientId=%s", sub.ID)
}
func (p *CandlestickWebSocketClient) handleMessage(msg string) (interface{}, error) {
baRes := BaKLineResponse{}
err := json.Unmarshal([]byte(msg), &baRes)
//转火币数据结构
amount, _ := decimal.NewFromString(baRes.K.V)
open, _ := decimal.NewFromString(baRes.K.O)
close, _ := decimal.NewFromString(baRes.K.C)
low, _ := decimal.NewFromString(baRes.K.l)
high, _ := decimal.NewFromString(baRes.K.H)
vol, _ := decimal.NewFromString(baRes.K.Q)
result := market.SubscribeCandlestickResponse{
Channel: fmt.Sprintf("market.%s.kline.%s", strings.ToLower(baRes.S), dictionary.BaToBaMap[baRes.K.I]),
Timestamp: baRes.Es,
Tick: &market.Tick{
Id: baRes.Es,
Amount: amount,
Count: baRes.K.N,
Open: open,
Close: close,
Low: low,
High: high,
Vol: vol,
IsBa: bawebsocketclientbase.BinAnce,
},
}
return result, err
}

114
pkg/bawssclient/depthwebsocketclient.go

@ -0,0 +1,114 @@
package bawssclient
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"wss-pool/dictionary"
"wss-pool/logging/applogger"
"wss-pool/pkg/client/bawebsocketclientbase"
"wss-pool/pkg/model/market"
)
type SubscribeDepthResponse struct {
LastUpdateId string `json:"lastUpdateId"`
Bids [][]string `json:"bids"`
Asks [][]string `json:"asks"`
}
// Responsible to handle Depth data from WebSocket
type DepthWebSocketClient struct {
bawebsocketclientbase.WebSocketClientBase
}
// Initializer
func (p *DepthWebSocketClient) Init(host string) *DepthWebSocketClient {
p.WebSocketClientBase.Init(host)
return p
}
// Request the full Depth data according to specified criteria
//func (p *DepthWebSocketClient) Request(symbol string) {
// topic := fmt.Sprintf("market.%s.kline.%s", symbol, period)
// req := fmt.Sprintf("{\"req\": \"%s\", \"from\":%d, \"to\":%d, \"id\": \"%s\" }", topic, from, to, clientId)
//
// p.Send(req)
//
// applogger.Info("WebSocket requested, topic=%s, clientId=%s", topic, clientId)
//}
// Set callback handler
func (p *DepthWebSocketClient) SetHandler(
connectedHandler bawebsocketclientbase.ConnectedHandler,
responseHandler bawebsocketclientbase.ResponseHandler) {
p.WebSocketClientBase.SetHandler(connectedHandler, p.handleMessage, responseHandler)
}
// Request the full Depth data according to specified criteria
// Subscribe Depth data
func (p *DepthWebSocketClient) Subscribe(symbol string) {
topis := make([]string, 0)
sub := BaKLineParam{
Method: "SUBSCRIBE",
Params: append(topis, fmt.Sprintf("%susdt@depth5@100ms", symbol)),
ID: bawebsocketclientbase.Randomnumber(),
}
data, _ := json.Marshal(sub)
p.Send(data)
applogger.Info("WebSocket subscribed, clientId=%s", sub.ID)
}
// Unsubscribe Depth data
func (p *DepthWebSocketClient) UnSubscribe(symbol string) {
topis := make([]string, 0)
for _, v := range dictionary.BaTimeCycle {
topis = append(topis, fmt.Sprintf("%susdt@%s", symbol, v))
}
sub := BaKLineParam{
Method: "UNSUBSCRIBE",
Params: append(topis, fmt.Sprintf("%susdt@depth5@100ms", symbol)),
ID: bawebsocketclientbase.Randomnumber(),
}
data, _ := json.Marshal(sub)
p.Send(data)
applogger.Info("WebSocket unsubscribed, clientId=%s", sub.ID)
}
func (p *DepthWebSocketClient) handleMessage(msg string) (interface{}, error) {
baRes := SubscribeDepthResponse{}
err := json.Unmarshal([]byte(msg), &baRes)
//转火币数据结构
bids := make([][]decimal.Decimal, 0)
for _, v := range baRes.Bids {
if len(v) >= 2 {
info := make([]decimal.Decimal, 0)
price, _ := decimal.NewFromString(v[0])
info = append(info, price)
size, _ := decimal.NewFromString(v[1])
info = append(info, size)
bids = append(bids, info)
}
}
asks := make([][]decimal.Decimal, 0)
for _, v := range baRes.Asks {
if len(v) >= 2 {
info := make([]decimal.Decimal, 0)
price, _ := decimal.NewFromString(v[0])
info = append(info, price)
size, _ := decimal.NewFromString(v[1])
info = append(info, size)
asks = append(asks, info)
}
}
result := market.SubscribeMarketByPriceResponse{
Tick: &market.MarketByPrice{
Bids: bids,
Asks: asks,
},
}
return result, err
}

122
pkg/bawssclient/tickerwebsocketclient.go

@ -0,0 +1,122 @@
package bawssclient
import (
"encoding/json"
"fmt"
"github.com/shopspring/decimal"
"strings"
"wss-pool/logging/applogger"
"wss-pool/pkg/client/bawebsocketclientbase"
"wss-pool/pkg/model/market"
)
type BaTickerResponse struct {
E string `json:"e"`
Es int64 `json:"e"`
S string `json:"s"`
P string `json:"p"`
Ps string `json:"P"`
W string `json:"w"`
X string `json:"x"`
C string `json:"c"`
Qs string `json:"Q"`
B string `json:"b"`
Bs string `json:"B"`
A string `json:"a"`
As string `json:"A"`
O string `json:"o"`
H string `json:"h"`
L string `json:"l"`
V string `json:"v"`
Q string `json:"q"`
Os int64 `json:"O"`
Cs int64 `json:"c"`
F int64 `json:"f"`
Ls int64 `json:"L"`
N int `json:"n"`
}
// Responsible to handle Ticker data from WebSocket
type TickerWebSocketClient struct {
bawebsocketclientbase.WebSocketClientBase
}
// Initializer
func (p *TickerWebSocketClient) Init(host string) *TickerWebSocketClient {
p.WebSocketClientBase.Init(host)
return p
}
// Set callback handler
func (p *TickerWebSocketClient) SetHandler(
connectedHandler bawebsocketclientbase.ConnectedHandler,
responseHandler bawebsocketclientbase.ResponseHandler) {
p.WebSocketClientBase.SetHandler(connectedHandler, p.handleMessage, responseHandler)
}
// Request the full Ticker data according to specified criteria
// Subscribe Ticker data
func (p *TickerWebSocketClient) Subscribe(symbol string) {
topis := make([]string, 0)
sub := BaKLineParam{
Method: "SUBSCRIBE",
Params: append(topis, fmt.Sprintf("%susdt@ticker", symbol)),
ID: bawebsocketclientbase.Randomnumber(),
}
data, _ := json.Marshal(sub)
p.Send(data)
applogger.Info("WebSocket subscribed, clientId=%s", sub.ID)
}
// Unsubscribe Ticker data
func (p *TickerWebSocketClient) UnSubscribe(symbol string) {
topis := make([]string, 0)
sub := BaKLineParam{
Method: "UNSUBSCRIBE",
Params: append(topis, fmt.Sprintf("%susdt@ticker", symbol)),
ID: bawebsocketclientbase.Randomnumber(),
}
data, _ := json.Marshal(sub)
p.Send(data)
applogger.Info("WebSocket unsubscribed, clientId=%s", sub.ID)
}
func (p *TickerWebSocketClient) handleMessage(msg string) (interface{}, error) {
baRes := BaTickerResponse{}
err := json.Unmarshal([]byte(msg), &baRes)
//转火币数据结构
amount, _ := decimal.NewFromString(baRes.V)
open, _ := decimal.NewFromString(baRes.X) // 整整24小时之前,向前数的最后一次成交价格
close, _ := decimal.NewFromString(baRes.C)
low, _ := decimal.NewFromString(baRes.L)
high, _ := decimal.NewFromString(baRes.H)
Bid, _ := decimal.NewFromString(baRes.B)
BidSize, _ := decimal.NewFromString(baRes.Bs)
Ask, _ := decimal.NewFromString(baRes.A)
AskSize, _ := decimal.NewFromString(baRes.As)
LastPrice, _ := decimal.NewFromString(baRes.C)
LastSize, _ := decimal.NewFromString(baRes.Qs)
result := market.TickerWebsocketResponse{
Channel: fmt.Sprintf("market.%s.ticker", strings.ToLower(baRes.S)),
Timestamp: baRes.Es,
Tick: &market.TickR{
Open: open,
High: high,
Low: low,
Close: close,
Amount: amount,
Count: baRes.N,
Bid: Bid,
BidSize: BidSize,
Ask: Ask,
AskSize: AskSize,
LastPrice: LastPrice,
LastSize: LastSize,
IsBa: bawebsocketclientbase.BinAnce,
},
}
return result, err
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save