You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
948 lines
33 KiB
948 lines
33 KiB
package processor
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"wss-pool/config"
|
|
"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"
|
|
)
|
|
|
|
var RedisFOREX string = "FOREX:LIST:"
|
|
var UrlKline = "https://quote.tradeswitcher.com/quote-b-api/kline" // 历史K线
|
|
var UrlBatchKline = "https://quote.tradeswitcher.com/quote-b-api/batch-kline" // 批量K线查询
|
|
var UrlDepthTick = "https://quote.tradeswitcher.com/quote-b-api/depth-tick" // 最新盘口报价查询 { "trace": "", "data": { "symbol_list": [ { "code": "GBPJPY" }, { "code": "CADJPY" } ]
|
|
var UrlTradeTick = "https://quote.tradeswitcher.com/quote-b-api/trade-tick" // 最新成交报价查询 { "trace": "", "data": { "symbol_list": [ { "code": "GBPJPY" }, { "code": "CADJPY" } ]
|
|
var TokenTrade = "bf8f33c446c4494286eccaa57a2e6fac-c-app"
|
|
|
|
// ForexAggregates
|
|
//
|
|
// @Description: 外汇K线 https://api.polygon.io/v2/aggs/ticker/C:EURUSD/range/1/day/2023-01-09/2023-02-10?adjusted=true&sort=asc&apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexAggregates(c *gin.Context) {
|
|
code := internal.ReplaceStr(c.Query("code")) // 外汇code
|
|
multiplier := internal.IntegerInit(c.Query("multiplier")) // 乘数:默认1
|
|
timespan := internal.ReplaceStr(c.Query("timespan")) // 时间间隔:second\minute\hour\day\week\month\quarter\year(默认:day)
|
|
from := internal.ReplaceStr(internal.ReplaceStr(c.Query("from"))) // 开始时间
|
|
to := internal.ReplaceStr(internal.ReplaceStr(c.Query("to"))) // 结束时间
|
|
sort := internal.ReplaceStr(internal.ReplaceStr(c.Query("sort"))) // 排序(默认:asc)
|
|
|
|
if len(from) == 0 || len(to) == 0 || len(code) == 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "Parameter error!", internal.QueryError))
|
|
return
|
|
}
|
|
if multiplier == 0 {
|
|
multiplier = 1
|
|
}
|
|
if len(timespan) == 0 {
|
|
timespan = "day"
|
|
}
|
|
if len(sort) == 0 {
|
|
sort = "asc"
|
|
}
|
|
|
|
url := fmt.Sprintf("https://%v/v2/aggs/ticker/C:%v/range/%v/%v/%v/%v?adjusted=true&sort=%v&apiKey=%v",
|
|
config.Config.ShareGather.PolygonHost, code, multiplier, timespan, from, to, sort, config.Config.ShareGather.PolygonKey)
|
|
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, bodyStr, internal.QuerySuccess))
|
|
}
|
|
|
|
// 历史k线查询
|
|
func ForexAggregatesNewGet(c *gin.Context) {
|
|
codeStr := internal.ReplaceStr(c.Query("code")) // 外汇code
|
|
kline_type := internal.IntegerInit(c.Query("kline_type")) // k线类型,1分钟K,2为5分钟K,3为15分钟K,4为30分钟K,5为小时K,6为2小时K,7为4小时K,8为日K,9为周K,10为月K
|
|
query_kline_num := internal.IntegerInit(c.Query("query_kline_num")) // 查询多少根K线,最多1000根
|
|
|
|
code := model.Check_Symbol[codeStr]
|
|
if len(code) == 0 {
|
|
code = codeStr
|
|
}
|
|
|
|
if kline_type == 0 || query_kline_num == 0 || len(code) == 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
req, err := http.NewRequest("GET", UrlKline, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
q := req.URL.Query()
|
|
q.Add("token", TokenTrade)
|
|
// 参数构造
|
|
var query model.ConstructParameters
|
|
query.Trace = uuid.New().String()
|
|
query.Data.Code = code
|
|
query.Data.KlineType = kline_type
|
|
query.Data.KlineTimestampEnd = 0
|
|
query.Data.QueryKlineNum = query_kline_num
|
|
query.Data.AdjustType = 0
|
|
queryStr, err := json.Marshal(&query)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
q.Add("query", string(queryStr))
|
|
req.URL.RawQuery = q.Encode()
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bodyStr, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
var klineNew model.KlineGetReturnStruct
|
|
if err = json.Unmarshal(bodyStr, &klineNew); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
var group bson.D
|
|
addFields := bson.D{
|
|
{"$addFields", bson.D{
|
|
{"dateObj", bson.D{{"$toDate", "$timestamp"}}},
|
|
{"num", bson.D{{"$toInt", "$volume"}}}},
|
|
}}
|
|
match := bson.D{
|
|
{"$match", bson.D{
|
|
{"code", codeStr},
|
|
{"dateObj", bson.D{{"$gte", time.Now().Add(-1 * time.Hour)}}},
|
|
}}}
|
|
//minDate := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
// TODO: 插针数据
|
|
var timeStamp int64
|
|
switch kline_type {
|
|
case 1: // 分时线
|
|
timeStamp = int64(1 * 60 * 1000) // 1 分钟 = 1 * 60 秒 * 1000 毫秒
|
|
case 2: // 5分钟线
|
|
timeStamp = int64(5 * 60 * 1000) // 5分钟 = 5 * 60 秒 * 1000 毫秒
|
|
//case 3: // 15分钟
|
|
// timeStamp = int64(15 * 60 * 1000) // 15分钟 =15 * 60 秒 * 1000 毫秒
|
|
//case 4: // 30分钟
|
|
// timeStamp = int64(30 * 60 * 1000) // 30分钟 =30 * 60 秒 * 1000 毫秒
|
|
//case 5: // 1小时
|
|
// timeStamp = int64(60 * 60 * 1000) // 1小时 =60 *60 秒 * 1000 毫秒
|
|
default:
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, klineNew.Data, internal.QuerySuccess))
|
|
return
|
|
}
|
|
applogger.Debug("数据展示:%v", timeStamp)
|
|
|
|
group = bson.D{
|
|
{"$group", bson.D{
|
|
{"_id", "$dateObj"},
|
|
//{"_id", bson.M{"$subtract": []interface{}{
|
|
// bson.M{"$subtract": []interface{}{"$dateObj", bson.M{"$dateFromString": bson.M{"dateString": "1970-01-01T00:00:00Z"}}}},
|
|
// bson.M{"$mod": []interface{}{bson.M{"$subtract": []interface{}{"$dateObj", bson.M{"$dateFromString": bson.M{"dateString": "1970-01-01T00:00:00Z"}}}}, timeStamp}},
|
|
//}}},
|
|
//{"_id", bson.M{
|
|
// "date": "$dateObj", // 用实际时间字段替换dateField
|
|
// "minutes": bson.M{"$toInt": bson.M{"$divide": []interface{}{"$dateObj", timeStamp}}},
|
|
//}},
|
|
{"highestPrice", bson.D{{"$max", "$high_price"}}},
|
|
{"lowestPrice", bson.D{{"$min", "$low_price"}}},
|
|
{"openPrice", bson.D{{"$first", "$open_price"}}},
|
|
{"closePrice", bson.D{{"$last", "$close_price"}}},
|
|
{"volume", bson.D{{"$max", "$num"}}},
|
|
}}}
|
|
|
|
project := bson.D{
|
|
{"$project", bson.D{
|
|
{"_id", 0},
|
|
{"time", bson.D{
|
|
{"$dateToString", bson.D{
|
|
{"format", "%Y-%m-%d %H:%M:%S"},
|
|
{"date", "$_id"},
|
|
}}}},
|
|
//{"time", bson.D{{"$toDate", "$_id"}}},
|
|
//{"time", "$_id"},
|
|
{"highestPrice", 1},
|
|
{"lowestPrice", 1},
|
|
{"openPrice", 1},
|
|
{"closePrice", 1},
|
|
{"volume", bson.D{{"$toString", "$volume"}}},
|
|
}}}
|
|
sort := bson.D{{"$sort", bson.D{{"time", 1}}}}
|
|
|
|
operations := mongo.Pipeline{addFields, match, group, project, sort}
|
|
mapList, err := data.MgoAggregate(data.ForexKLine, operations)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "MgoAggregate err", internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("查询数据:%v", mapList)
|
|
|
|
switch v := mapList.(type) {
|
|
case []map[string]interface{}:
|
|
v = mapList.([]map[string]interface{})
|
|
for _, value := range v {
|
|
var md model.KlineList
|
|
if highest, ok := value["highestPrice"].(string); ok {
|
|
md.HighPrice = highest
|
|
}
|
|
if lowest, ok := value["lowestPrice"].(string); ok {
|
|
md.LowPrice = lowest
|
|
}
|
|
if open, ok := value["openPrice"].(string); ok {
|
|
md.OpenPrice = open
|
|
}
|
|
if closeP, ok := value["closePrice"].(string); ok {
|
|
md.ClosePrice = closeP
|
|
}
|
|
if volume, ok := value["volume"].(string); ok {
|
|
md.Volume = volume
|
|
}
|
|
if times, ok := value["time"].(string); ok {
|
|
loc, err := time.LoadLocation("Local") // 加载本地时区
|
|
if err != nil {
|
|
applogger.Error("加载时区失败:", err)
|
|
continue
|
|
}
|
|
parsedTime, err := time.ParseInLocation("2006-01-02 15:04:05", times, loc)
|
|
if err != nil {
|
|
applogger.Error("解析时间字符串失败:", err)
|
|
continue
|
|
}
|
|
md.Timestamp = strconv.Itoa(int(parsedTime.Unix()))
|
|
}
|
|
md.Turnover = strconv.Itoa(0)
|
|
applogger.Debug("数据解析为:%v", md)
|
|
klineNew.Data.KlineList = append(klineNew.Data.KlineList, md)
|
|
}
|
|
default:
|
|
}
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, klineNew.Data, internal.QuerySuccess))
|
|
}
|
|
|
|
// 最新k线查询
|
|
func ForexAggregatesNewPost(c *gin.Context) {
|
|
param := model.ConstructParametersPost{}
|
|
err := c.BindJSON(¶m)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "param error", internal.QueryError))
|
|
return
|
|
} else if len(param.Trace) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "param null", internal.QueryError))
|
|
return
|
|
} else if len(param.Data.DataList) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "token error", internal.QueryError))
|
|
return
|
|
}
|
|
queryStr, err := json.Marshal(¶m)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
bodyStr, err := internal.HttpPost(fmt.Sprintf("%v?token=%v", UrlBatchKline, TokenTrade), string(queryStr))
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
var klineNew model.KlinePostReturnStruct
|
|
if err = json.Unmarshal([]byte(bodyStr), &klineNew); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, klineNew.Data.KlineList, internal.QuerySuccess))
|
|
}
|
|
|
|
// 最新盘口报价查询
|
|
func ForexAggregatesDepthTick(c *gin.Context) {
|
|
param := model.OrderBookOrTradeTick{}
|
|
err := c.BindJSON(¶m)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "param error", internal.QueryError))
|
|
return
|
|
} else if len(param.Trace) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "param null", internal.QueryError))
|
|
return
|
|
} else if len(param.Data.SymbolList) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "token error", internal.QueryError))
|
|
return
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", UrlDepthTick, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
// 参数构造
|
|
q := req.URL.Query()
|
|
q.Add("token", TokenTrade)
|
|
queryStr, err := json.Marshal(¶m)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
q.Add("query", string(queryStr))
|
|
req.URL.RawQuery = q.Encode()
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bodyStr, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
var depthNew model.DepthReturnStruct
|
|
if err = json.Unmarshal(bodyStr, &depthNew); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, depthNew.Data, internal.QuerySuccess))
|
|
}
|
|
|
|
// 最新成交价批量查询
|
|
func ForexAggregatesTradeTick(c *gin.Context) {
|
|
param := model.OrderBookOrTradeTick{}
|
|
err := c.BindJSON(¶m)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "param error", internal.QueryError))
|
|
return
|
|
} else if len(param.Trace) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "param null", internal.QueryError))
|
|
return
|
|
} else if len(param.Data.SymbolList) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "token error", internal.QueryError))
|
|
return
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", UrlTradeTick, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
// 参数构造
|
|
q := req.URL.Query()
|
|
q.Add("token", TokenTrade)
|
|
queryStr, err := json.Marshal(¶m)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
q.Add("query", string(queryStr))
|
|
req.URL.RawQuery = q.Encode()
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, nil, internal.QueryError))
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
bodyStr, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
var tradeNew model.TradeReturnStruct
|
|
if err = json.Unmarshal(bodyStr, &tradeNew); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, tradeNew.Data, internal.QuerySuccess))
|
|
}
|
|
|
|
// 查询大宗列表
|
|
func ForexSymbolListNew(c *gin.Context) {
|
|
pageSize := internal.IntegerInit(internal.ReplaceStr(c.Query("pageSize"))) // 每页显示多少条数据
|
|
pageNumber := internal.IntegerInit(internal.ReplaceStr(c.Query("pageNumber"))) // 第几页
|
|
sort := internal.IntegerInit(internal.ReplaceStr(c.Query("sort"))) // Code排序
|
|
search := internal.ReplaceStr(c.Query("search")) // 搜索数据(模糊)
|
|
category := internal.ReplaceStr(c.Query("category")) // 类型:外汇(FX)、贵金属(Metals)、能源(Energy)
|
|
if len(category) == 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "Parameter cannot be empty", internal.QueryError))
|
|
return
|
|
}
|
|
|
|
var filter bson.M
|
|
if len(search) > 0 {
|
|
// 模糊搜索
|
|
switch category {
|
|
case "FX": // 外汇
|
|
filter = bson.M{"$and": []bson.M{{"symbol": bson.M{"$regex": search}}, {"symbol": bson.M{"$regex": "USD$"}}, {"category": category}}}
|
|
default: // 贵金属和能源
|
|
filter = bson.M{"symbol": bson.M{"$regex": search}, "category": category}
|
|
}
|
|
} else {
|
|
// 正常加载列表
|
|
filter = bson.M{"$and": []bson.M{{"category": category}, {"symbol": bson.M{"$regex": "USD$"}}}}
|
|
}
|
|
|
|
total, err := data.MgoFindTotal(data.ForexListBak, filter)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, err, internal.QueryError))
|
|
return
|
|
}
|
|
pageData := make([]stock.ForexDataNew, 0)
|
|
if sort == 0 {
|
|
sort = -1
|
|
}
|
|
|
|
data.MgoPagingFindStruct(data.ForexListBak, filter, int64(pageSize), int64(pageNumber), "symbol", sort, &pageData)
|
|
|
|
pageDataList := make([]stock.ForexDataNew, 0)
|
|
redisShield := HashValue(RedisFOREX)
|
|
// 过滤屏蔽的交易对
|
|
for _, value := range pageData {
|
|
strMap, ok := redisShield[value.Code]
|
|
if !ok {
|
|
pageDataList = append(pageDataList, value)
|
|
} else {
|
|
if strMap.Status == 1 {
|
|
pageDataList = append(pageDataList, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
var md stock.MgoPageSize
|
|
md.PageSize = pageSize
|
|
md.PageNumber = pageNumber
|
|
md.Total = total
|
|
md.Data = pageDataList
|
|
applogger.Debug("查询数据: %v", len(pageDataList))
|
|
|
|
if len(pageDataList) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
}
|
|
|
|
// 查询大宗收藏列表
|
|
func ForexFreeSymbolListNew(c *gin.Context) {
|
|
id := internal.ReplaceStr(c.Query("id")) // 用户ID
|
|
market_type := internal.IntegerInit(internal.ReplaceStr(c.Query("market_type"))) // 外汇市场
|
|
pageSize := internal.IntegerInit(internal.ReplaceStr(c.Query("pageSize"))) // 每页显示多少条数据
|
|
pageNumber := internal.IntegerInit(internal.ReplaceStr(c.Query("pageNumber"))) // 第几页
|
|
|
|
if len(id) <= 0 || market_type <= 0 || pageSize <= 0 || pageNumber <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "Parameter cannot be empty", internal.QueryError))
|
|
return
|
|
}
|
|
var md stock.MgoPageSize
|
|
pageDataList := make([]stock.ForexDataNew, 0)
|
|
md.PageSize = pageSize
|
|
md.PageNumber = pageNumber
|
|
|
|
userIdKey := fmt.Sprintf("%v%v", FreeSymbolKey, id)
|
|
result, err := red.Get_Cache_Byte(userIdKey)
|
|
if err != nil {
|
|
md.Data = pageDataList
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
var freeList []StockSymbol
|
|
if err = json.Unmarshal(result, &freeList); err != nil {
|
|
md.Data = pageDataList
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
// 组合需要查询自选缓存股票code
|
|
var symbolList []string
|
|
for _, value := range freeList {
|
|
if market_type == value.MarketType {
|
|
symbolList = append(symbolList, value.Code)
|
|
}
|
|
}
|
|
|
|
filter := bson.M{"$and": []bson.M{{"symbol": bson.M{"$in": symbolList}}, {"symbol": bson.M{"$regex": "USD$"}}}}
|
|
total, err := data.MgoFindTotal(data.ForexListBak, filter)
|
|
if err != nil {
|
|
md.Total = int64(len(symbolList))
|
|
md.Data = pageDataList
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
pageData := make([]stock.ForexDataNew, 0)
|
|
if err = data.MgoPagingFindStruct(data.ForexListBak, filter, int64(pageSize), int64(pageNumber), "symbol", -1, &pageData); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
redisShield := HashValue(RedisFOREX)
|
|
// 过滤屏蔽的交易对
|
|
for _, value := range pageData {
|
|
strMap, ok := redisShield[value.Code]
|
|
if !ok {
|
|
pageDataList = append(pageDataList, value)
|
|
} else {
|
|
if strMap.Status == 1 {
|
|
pageDataList = append(pageDataList, value)
|
|
}
|
|
}
|
|
}
|
|
md.Total = total
|
|
md.Data = pageDataList
|
|
applogger.Debug("查询数据: %v", len(pageDataList))
|
|
|
|
if len(pageDataList) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
}
|
|
|
|
// 查询大宗成交报价
|
|
func ForexTradeList(c *gin.Context) {
|
|
code := internal.ReplaceStr(c.Query("code")) // 外汇code
|
|
limit := internal.IntegerInit(internal.ReplaceStr(c.Query("limit"))) // 查询多少条数据
|
|
if len(code) == 0 || limit == 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "Parameter cannot be empty", internal.QueryError))
|
|
return
|
|
}
|
|
|
|
res := make([]model.ForexTradeList, 0)
|
|
filter := bson.M{"code": code}
|
|
|
|
if err := data.MgoFindForexToStr(data.ForexTradeList, filter, int64(limit), &res); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, []model.ForexTradeList{}, internal.QuerySuccess))
|
|
return
|
|
}
|
|
|
|
if len(res) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, []model.ForexTradeList{}, internal.QuerySuccess))
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, res, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexSymbolList
|
|
//
|
|
// @Description: 外汇代码列表查询 http://127.0.0.1:88/stock/share/exchange-symbol-list?pageNumber=1&pageSize=35&&search=
|
|
// @param c
|
|
func ForexSymbolList(c *gin.Context) {
|
|
pageSize := internal.IntegerInit(internal.ReplaceStr(c.Query("pageSize"))) // 每页显示多少条数据
|
|
pageNumber := internal.IntegerInit(internal.ReplaceStr(c.Query("pageNumber"))) // 第几页
|
|
sort := internal.IntegerInit(internal.ReplaceStr(c.Query("sort"))) // Code排序
|
|
search := internal.ReplaceStr(c.Query("search")) // 搜索数据(模糊)
|
|
|
|
var filter bson.M
|
|
if len(search) > 0 { // 模糊搜索
|
|
filter = bson.M{"$and": []bson.M{{"ticker": bson.M{"$regex": search}}, {"ticker": bson.M{"$regex": "USD$"}}}}
|
|
} else {
|
|
filter = bson.M{"ticker": bson.M{"$regex": "USD$"}}
|
|
}
|
|
filter["day.o"] = bson.M{"$gt": 0.0}
|
|
filter["day.h"] = bson.M{"$gt": 0.0}
|
|
filter["day.l"] = bson.M{"$gt": 0.0}
|
|
filter["day.c"] = bson.M{"$gt": 0.0}
|
|
|
|
total, err := data.MgoFindTotal(data.ForexList, filter)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, err, internal.QueryError))
|
|
return
|
|
}
|
|
pageData := make([]stock.ForexData, 0)
|
|
if sort == 0 {
|
|
sort = -1
|
|
}
|
|
|
|
data.MgoPagingFindStruct(data.ForexList, filter, int64(pageSize), int64(pageNumber), "updated", sort, &pageData)
|
|
|
|
pageDataList := make([]stock.ForexData, 0)
|
|
redisShield := HashValue(RedisFOREX)
|
|
// 过滤屏蔽的交易对
|
|
for _, value := range pageData {
|
|
if value.Day.C == 0 && value.Day.O == 0 && value.Day.H == 0 && value.Day.L == 0 {
|
|
continue
|
|
}
|
|
strMap, ok := redisShield[value.Ticker]
|
|
if !ok {
|
|
pageDataList = append(pageDataList, value)
|
|
} else {
|
|
if strMap.Status == 1 {
|
|
pageDataList = append(pageDataList, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
var md stock.MgoPageSize
|
|
md.PageSize = pageSize
|
|
md.PageNumber = pageNumber
|
|
md.Total = total
|
|
md.Data = pageDataList
|
|
applogger.Debug("查询数据: %v", len(pageDataList))
|
|
|
|
if len(pageDataList) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexFreeSymbolList
|
|
func ForexFreeSymbolList(c *gin.Context) {
|
|
id := internal.ReplaceStr(c.Query("id")) // 用户ID
|
|
market_type := internal.IntegerInit(internal.ReplaceStr(c.Query("market_type"))) // 外汇市场
|
|
pageSize := internal.IntegerInit(internal.ReplaceStr(c.Query("pageSize"))) // 每页显示多少条数据
|
|
pageNumber := internal.IntegerInit(internal.ReplaceStr(c.Query("pageNumber"))) // 第几页
|
|
|
|
if len(id) <= 0 || market_type <= 0 || pageSize <= 0 || pageNumber <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "参数不能为空", internal.QueryError))
|
|
return
|
|
}
|
|
var md stock.MgoPageSize
|
|
pageDataList := make([]stock.ForexData, 0)
|
|
md.PageSize = pageSize
|
|
md.PageNumber = pageNumber
|
|
|
|
userIdKey := fmt.Sprintf("%v%v", FreeSymbolKey, id)
|
|
result, err := red.Get_Cache_Byte(userIdKey)
|
|
if err != nil {
|
|
md.Data = pageDataList
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QueryError))
|
|
return
|
|
}
|
|
var freeList []StockSymbol
|
|
if err = json.Unmarshal(result, &freeList); err != nil {
|
|
md.Data = pageDataList
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
// 组合需要查询自选缓存股票code
|
|
var symbolList []string
|
|
for _, value := range freeList {
|
|
if market_type == value.MarketType {
|
|
symbolList = append(symbolList, value.Code)
|
|
}
|
|
}
|
|
filter := bson.M{"$and": []bson.M{{"ticker": bson.M{"$in": symbolList}}, {"ticker": bson.M{"$regex": "USD$"}}}}
|
|
filter["day.o"] = bson.M{"$gt": 0.0}
|
|
filter["day.h"] = bson.M{"$gt": 0.0}
|
|
filter["day.l"] = bson.M{"$gt": 0.0}
|
|
filter["day.c"] = bson.M{"$gt": 0.0}
|
|
|
|
total, err := data.MgoFindTotal(data.ForexList, filter)
|
|
if err != nil {
|
|
md.Total = int64(len(symbolList))
|
|
md.Data = pageDataList
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
pageData := make([]stock.ForexData, 0)
|
|
if err = data.MgoPagingFindStruct(data.ForexList, filter, int64(pageSize), int64(pageNumber), "updated", -1, &pageData); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
|
|
redisShield := HashValue(RedisFOREX)
|
|
// 过滤屏蔽的交易对
|
|
for _, value := range pageData {
|
|
if value.Day.C == 0 && value.Day.O == 0 && value.Day.H == 0 && value.Day.L == 0 {
|
|
continue
|
|
}
|
|
strMap, ok := redisShield[value.Ticker]
|
|
if !ok {
|
|
pageDataList = append(pageDataList, value)
|
|
} else {
|
|
if strMap.Status == 1 {
|
|
pageDataList = append(pageDataList, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
md.Total = total
|
|
md.Data = pageDataList
|
|
applogger.Debug("查询数据: %v", len(pageDataList))
|
|
|
|
if len(pageDataList) <= 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, md, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexAllTickers
|
|
//
|
|
// @Description: 查询所有外汇代码列表 https://api.polygon.io/v2/snapshot/locale/global/markets/forex/tickers?apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexAllTickers(c *gin.Context) {
|
|
url := fmt.Sprintf("https://%v/v2/snapshot/locale/global/markets/forex/tickers?apiKey=%v", config.Config.ShareGather.PolygonHost, config.Config.ShareGather.PolygonKey)
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
var listCode model.ForexDataResponse
|
|
if err = json.Unmarshal([]byte(bodyStr), &listCode); err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
|
|
var dataList []mongo.WriteModel
|
|
for _, v := range listCode.Tickers {
|
|
code := strings.Split(v.Ticker, ":")
|
|
if len(code) < 2 {
|
|
continue
|
|
}
|
|
codeStr := code[1]
|
|
filter := bson.M{
|
|
"ticker": codeStr,
|
|
}
|
|
updateData := bson.M{}
|
|
updateData["todaysChange"] = v.TodaysChange
|
|
updateData["todaysChangePerc"] = v.TodaysChangePerc
|
|
updateData["updated"] = v.Updated
|
|
updateData["day"] = v.Day
|
|
updateData["lastQuote"] = v.LastQuote
|
|
updateData["min"] = v.Min
|
|
updateData["prevDay"] = v.PrevDay
|
|
|
|
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.ForexList, dataList); err != nil {
|
|
applogger.Error("MgoInsertMany err:%v", err)
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusBadRequest, "operation failure", internal.QueryError))
|
|
return
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, dataList, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexTicker
|
|
//
|
|
// @Description: 获取单个外汇数据 https://api.polygon.io/v2/snapshot/locale/global/markets/forex/tickers/C:EURUSD?apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexTicker(c *gin.Context) {
|
|
code := internal.ReplaceStr(c.Query("code")) // 外汇代码
|
|
if len(code) == 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "Parameter error!", internal.QueryError))
|
|
return
|
|
}
|
|
|
|
url := fmt.Sprintf("https://%v/v2/snapshot/locale/global/markets/forex/tickers/C:%v?apiKey=%v", config.Config.ShareGather.PolygonHost, code, config.Config.ShareGather.PolygonKey)
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, bodyStr, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexPreviousClose
|
|
//
|
|
// @Description: 获取指定外汇代码前一天数据 https://api.polygon.io/v2/aggs/ticker/C:EURUSD/prev?adjusted=true&apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexPreviousClose(c *gin.Context) {
|
|
code := internal.ReplaceStr(c.Query("code")) // 外汇代码
|
|
if len(code) == 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "Parameter error!", internal.QueryError))
|
|
return
|
|
}
|
|
|
|
url := fmt.Sprintf("https://%v/v2/aggs/ticker/C:%v/prev?adjusted=true&apiKey=%v", config.Config.ShareGather.PolygonHost, code, config.Config.ShareGather.PolygonKey)
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, bodyStr, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexGroupedDaily
|
|
//
|
|
// @Description: 获取每日分组数据 https://api.polygon.io/v2/aggs/grouped/locale/global/market/fx/2023-01-09?adjusted=true&apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexGroupedDaily(c *gin.Context) {
|
|
date := internal.ReplaceStr(c.Query("date")) // 查询时间
|
|
if len(date) == 0 {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "Parameter error!", internal.QueryError))
|
|
return
|
|
}
|
|
url := fmt.Sprintf("https://%v/v2/aggs/grouped/locale/global/market/fx/%v?adjusted=true&apiKey=%v", config.Config.ShareGather.PolygonHost, date, config.Config.ShareGather.PolygonKey)
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, bodyStr, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexQuotesBBO
|
|
//
|
|
// @Description: 行情(BBO) 获取给定时间范围内某个股票代码的 BBO 报价。 https://api.polygon.io/v3/quotes/C:EUR-USD?order=desc&limit=1000&sort=timestamp&apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexQuotesBBO(c *gin.Context) {
|
|
code := internal.ReplaceStr(c.Query("code")) // 代码
|
|
if len(code) >= 3 {
|
|
firstThree := code[0:3]
|
|
lastThree := code[len(code)-3:] // 从字符串长度减去 3 开始到结束
|
|
code = fmt.Sprintf("%v-%v", firstThree, lastThree)
|
|
} else {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "Parameter error!", internal.QueryError))
|
|
return
|
|
}
|
|
url := fmt.Sprintf("https://%v/v3/quotes/C:%v?order=desc&limit=100&sort=timestamp&apiKey=%v", config.Config.ShareGather.PolygonHost, code, config.Config.ShareGather.PolygonKey)
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, bodyStr, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexLastQuote
|
|
//
|
|
// @Description: 货币对的最后报价 https://api.polygon.io/v1/last_quote/currencies/AUD/USD?apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexLastQuote(c *gin.Context) {
|
|
code := internal.ReplaceStr(c.Query("code")) // 代码
|
|
var firstCode, lastCode string
|
|
if len(code) >= 3 {
|
|
firstCode = code[0:3]
|
|
lastCode = code[len(code)-3:] // 从字符串长度减去 3 开始到结束
|
|
} else {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "Parameter error!", internal.QueryError))
|
|
return
|
|
}
|
|
url := fmt.Sprintf("https://%v/v1/last_quote/currencies/%v/%v?apiKey=%v", config.Config.ShareGather.PolygonHost, firstCode, lastCode, config.Config.ShareGather.PolygonKey)
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, bodyStr, internal.QuerySuccess))
|
|
}
|
|
|
|
// ForexRealTimeCurrency
|
|
//
|
|
// @Description: 实时货币兑换 https://api.polygon.io/v1/conversion/AUD/USD?amount=100&precision=2&apiKey=3uX9zgBRPFl6MS11t4CsIrIZ_s_o2nh9
|
|
// @param c
|
|
func ForexRealTimeCurrency(c *gin.Context) {
|
|
var firstCode, lastCode string
|
|
code := internal.ReplaceStr(c.Query("code")) // 代码
|
|
amount := internal.IntegerInit(internal.ReplaceStr(c.Query("amount"))) // 查询数量 默认:100
|
|
precision := internal.IntegerInit(internal.ReplaceStr(c.Query("precision"))) // 精度 0,1,2,3,4 默认:2
|
|
|
|
if len(code) >= 3 {
|
|
firstCode = code[0:3]
|
|
lastCode = code[len(code)-3:] // 从字符串长度减去 3 开始到结束
|
|
} else {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, "Parameter error!", internal.QueryError))
|
|
return
|
|
}
|
|
if amount < 1 {
|
|
amount = 100
|
|
}
|
|
if precision < 0 || precision > 4 {
|
|
precision = 2
|
|
}
|
|
|
|
url := fmt.Sprintf("https://%v/v1/conversion/%v/%v?amount=%v&precision=%v&apiKey=%v",
|
|
config.Config.ShareGather.PolygonHost, firstCode, lastCode, amount, precision, config.Config.ShareGather.PolygonKey)
|
|
applogger.Debug("查询数据信息:%v", url)
|
|
|
|
bodyStr, err := internal.HttpGet(url)
|
|
if err != nil {
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, err, internal.QueryError))
|
|
return
|
|
}
|
|
applogger.Debug("第三方数据接收:%v", bodyStr)
|
|
|
|
c.JSON(http.StatusOK, internal.GinResult(http.StatusOK, bodyStr, internal.QuerySuccess))
|
|
}
|
|
|
|
// HashValue
|
|
//
|
|
// @Description: 获取屏蔽的外汇交易对
|
|
// @param hashListName
|
|
// @return []stock.PHPData
|
|
func HashValue(hashListName string) map[string]stock.PHPData {
|
|
keys := red.Scan(hashListName)
|
|
result := make(map[string]stock.PHPData, 0)
|
|
for _, v := range keys {
|
|
res, _ := red.HGetAll(v)
|
|
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)
|
|
}
|
|
}
|
|
|
|
result[item.Code] = item
|
|
}
|
|
|
|
return result
|
|
}
|
|
|