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:OW1EcVvbuJ2ZDW2X8G1m9K5XIN/KlDgwxNoSOHR5 |
||||
|
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