commit
e71bae6f14
171 changed files with 29316 additions and 0 deletions
@ -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.* |
|||
|
|||
|
@ -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 |
@ -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 & |
|||
``` |
|||
|
|||
|
@ -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 |
|||
----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@ -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 |
|||
------------------------------------------------------------------------------------------------------------------------------------------------ |
@ -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 |
|||
|
|||
------------------------------------------------------------------------------------------------------------------------------------------------ |
@ -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 |
|||
} |
@ -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 |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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)
|
|||
// }
|
|||
//}
|
|||
} |
File diff suppressed because it is too large
@ -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 |
|||
} |
@ -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 |
|||
} |
|||
} |
|||
} |
@ -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......") |
|||
} |
|||
} |
@ -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") |
|||
} |
@ -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 |
|||
} |
|||
} |
|||
|
|||
} |
@ -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 |
|||
} |
@ -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") |
|||
} |
@ -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 {} |
|||
} |
@ -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) |
|||
} |
@ -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))) |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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) |
|||
} |
@ -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) |
|||
} |
|||
} |
@ -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) |
|||
} |
|||
} |
@ -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() |
|||
} |
@ -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() |
|||
} |
@ -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) |
|||
} |
|||
} |
@ -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) |
|||
} |
@ -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()
|
|||
} |
|||
} |
@ -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())) |
|||
} |
@ -0,0 +1 @@ |
|||
MANIFEST-000003 |
@ -0,0 +1 @@ |
|||
MANIFEST-000000 |
@ -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 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||
MANIFEST-000037 |
@ -0,0 +1 @@ |
|||
MANIFEST-000035 |
@ -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 |
Binary file not shown.
@ -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) |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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) |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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) |
|||
} |
@ -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(交割合约代码) |
|||
3、BTC-USDT-CW(当周合约标识) |
|||
4、BTC-USDT-NW(次周合约标识) |
|||
5、BTC-USDT-CQ(当季合约标识) |
|||
6、BTC-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"} |
@ -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 |
|||
} |
@ -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() |
|||
} |
@ -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) |
|||
} |
|||
} |
@ -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") |
|||
} |
@ -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) |
|||
} |
|||
} |
|||
} |
@ -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)) |
|||
} |
File diff suppressed because it is too large
@ -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 |
|||
} |
|||
} |
|||
} |
@ -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......") |
|||
} |
|||
} |
@ -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 |
|||
} |
|||
}() |
|||
} |
|||
} |
|||
}() |
|||
} |
|||
} |
@ -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") |
|||
} |
@ -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
|
|||
|
|||
二、日内数据 |
|||
1、1小时 hour |
|||
https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=1h&fmt=json&from=1659283200&to=1686815102
|
|||
2、5分钟 fiveminutes |
|||
https://eodhistoricaldata.com/api/intraday/AAPL.US?api_token=647dd6744b94f4.20894198&interval=5m&fmt=json&from=1659283200&to=1686815102
|
|||
3、1分钟 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 |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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())) |
|||
} |
|||
} |
@ -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 |
|||
} |
|||
} |
|||
} |
@ -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(¶m); 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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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, |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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()) |
|||
} |
|||
} |
@ -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(¶mModel) |
|||
if err != nil { |
|||
return ¶mModel, err |
|||
} |
|||
return ¶mModel, err |
|||
} |
|||
|
|||
// Contract parameter analysis
|
|||
func ContractStr(c *gin.Context) (*model.ContractModel, error) { |
|||
var paramModel model.ContractModel |
|||
err := c.BindJSON(¶mModel) |
|||
if err != nil { |
|||
return ¶mModel, err |
|||
} |
|||
return ¶mModel, 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() |
|||
} |
@ -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): //超时后就不再发送
|
|||
} |
|||
} |
@ -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
|
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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...) |
|||
} |
@ -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++ |
|||
} |
|||
} |
@ -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 |
|||
} |
@ -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 |
|||
} |
@ -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…
Reference in new issue