history.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. package quote
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "mtp2_if/global/app"
  6. "mtp2_if/global/e"
  7. "mtp2_if/logger"
  8. "mtp2_if/models"
  9. "mtp2_if/mtpcache"
  10. "mtp2_if/utils"
  11. "net/http"
  12. "sort"
  13. "strings"
  14. "time"
  15. "github.com/gin-gonic/gin"
  16. )
  17. // HistoryData 历史数据
  18. type HistoryData struct {
  19. Opened float64 `json:"o"` // 开盘价
  20. Highest float64 `json:"h"` // 最高价
  21. Lowest float64 `json:"l"` // 最低价
  22. Closed float64 `json:"c"` // 收盘价
  23. TotleVolume int `json:"tv"` // 总量(成交量)
  24. TotleTurnover float64 `json:"tt"` // 总金额
  25. HoldVolume int `json:"hv"` // 持仓量
  26. Settle float64 `json:"s"` // 结算价,日线周期(包括)以上才有
  27. TimeStamp time.Time `json:"ts"` // 时间
  28. IsFill bool `json:"f"` // 是否补充数据
  29. }
  30. // QueryHistoryDatasReq 查询行情历史数据请求参数
  31. type QueryHistoryDatasReq struct {
  32. CycleType int `form:"cycleType" binding:"required"`
  33. GoodsCode string `form:"goodsCode" binding:"required"`
  34. StartTime string `form:"startTime"`
  35. EndTime string `form:"endTime"`
  36. Count int `form:"count"`
  37. IsAsc bool `form:"isAsc"`
  38. }
  39. // QueryHistoryDatas 查询行情历史数据
  40. // @Summary 查询行情历史数据
  41. // @Produce json
  42. // @Security ApiKeyAuth
  43. // @Param cycleType query int true "周期类型, 0-秒 1: 1分钟 2: 5分钟 3: 30分钟 4: 60分钟 120: 2小时 240: 4小时 11: 日线 12:周线 13:月线 14:年线"
  44. // @Param goodsCode query string true "商品代码"
  45. // @Param startTime query string false "开始时间,格式:yyyy-MM-dd HH:mm:ss"
  46. // @Param endTime query string false "结束时间,格式:yyyy-MM-dd HH:mm:ss"
  47. // @Param count query int false "条数"
  48. // @Param isAsc query bool false "是否按时间顺序排序(默认为时间倒序排序)"
  49. // @Success 200 {object} HistoryData
  50. // @Failure 500 {object} app.Response
  51. // @Router /Quote/QueryHistoryDatas [get]
  52. // @Tags 行情服务
  53. func QueryHistoryDatas(c *gin.Context) {
  54. appG := app.Gin{C: c}
  55. // 获取请求参数
  56. var req QueryHistoryDatasReq
  57. if err := appG.C.ShouldBindQuery(&req); err != nil {
  58. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  59. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  60. return
  61. }
  62. // 转换时间
  63. timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
  64. var startTime *time.Time
  65. if len(req.StartTime) > 0 {
  66. st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
  67. if err != nil {
  68. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  69. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  70. return
  71. }
  72. startTime = &st
  73. }
  74. var endTime *time.Time
  75. if len(req.EndTime) > 0 {
  76. et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
  77. if err != nil {
  78. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  79. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  80. return
  81. }
  82. endTime = &et
  83. }
  84. // 查询数据
  85. cycleDatas, err := models.GetHistoryCycleDatas(models.CycleType(req.CycleType), req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
  86. if err != nil {
  87. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  88. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  89. return
  90. }
  91. // 获取目标商品报价小数位
  92. var dcplace int = 0
  93. if len(cycleDatas) > 0 {
  94. dcplace = mtpcache.GetGoodsDecimalplace(req.GoodsCode)
  95. }
  96. // 计算最终价格
  97. rst := make([]HistoryData, 0)
  98. for _, v := range cycleDatas {
  99. historyData := HistoryData{
  100. Opened: utils.IntToFloat64(v.Open, dcplace),
  101. Highest: utils.IntToFloat64(v.High, dcplace),
  102. Lowest: utils.IntToFloat64(v.Low, dcplace),
  103. Closed: utils.IntToFloat64(v.Close, dcplace),
  104. TotleVolume: v.TV,
  105. TotleTurnover: float64(v.TT),
  106. HoldVolume: v.HV,
  107. Settle: utils.IntToFloat64(v.SP, dcplace),
  108. TimeStamp: time.Unix(int64(v.ST), 0),
  109. }
  110. rst = append(rst, historyData)
  111. }
  112. // 包括日线以上的周期,如果没有带当前周期则通过盘面数据生成当前周期数据
  113. if req.CycleType >= 11 {
  114. // 获取商品信息
  115. goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
  116. if goods == nil {
  117. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  118. appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
  119. return
  120. }
  121. // 获取市场
  122. market, err := models.GetMarketByGoodsCode(req.GoodsCode)
  123. if err != nil {
  124. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  125. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  126. return
  127. }
  128. if market == nil {
  129. logger.GetLogger().Errorf("QueryHistoryDatas failed: %s", err.Error())
  130. appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKET_FAILED, nil)
  131. return
  132. }
  133. // 获取目标品种交易日
  134. // FIXME: - 由于mtp2.0目前未同步外部交易所品种的当前交易日,
  135. // 故通道交易的品种目前只能在交易系统的外部市场中获
  136. // 取统一的交易日,后期应要求服务端同步外部数据
  137. // 2021.06.30 业务更新:15、97和99市场的行情交易日要从商品盘面中获取
  138. quoteTradeDate := ""
  139. if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
  140. if quoteDays, err := models.GetQuoteDays("'" + req.GoodsCode + "'"); err == nil {
  141. if len(quoteDays) > 0 {
  142. quoteTradeDate = time.Unix(quoteDays[0].Exchangedate, 0).Format("20060102")
  143. }
  144. }
  145. } else {
  146. if marketRun, err := models.GetMarketRun(int(market.Marketid)); err == nil {
  147. quoteTradeDate = marketRun.Tradedate2
  148. }
  149. }
  150. if quoteTradeDate != "" {
  151. if tradeDate, err := time.ParseInLocation("20060102", quoteTradeDate, time.Local); err == nil {
  152. var fisrtDate time.Time
  153. switch req.CycleType {
  154. case 12: // 周
  155. fisrtDate = getFirstDateOfWeek(tradeDate)
  156. case 13: // 月
  157. fisrtDate = getFirstDateOfMonth(tradeDate)
  158. case 14: // 年
  159. fisrtDate = getFirstDateOfYear(tradeDate)
  160. default: // 日
  161. fisrtDate = tradeDate
  162. }
  163. // 获取盘面数据
  164. if quoteDays, err := models.GetQuoteDays(req.GoodsCode); err == nil && len(quoteDays) > 0 {
  165. v := quoteDays[0]
  166. if len(rst) == 0 {
  167. // 历史数据当前没数据则直接加当前盘面数据
  168. historyData := HistoryData{
  169. Opened: utils.IntToFloat64(int(v.Opened), dcplace),
  170. Highest: utils.IntToFloat64(int(v.Highest), dcplace),
  171. Lowest: utils.IntToFloat64(int(v.Lowest), dcplace),
  172. Closed: utils.IntToFloat64(int(v.Last), dcplace),
  173. TotleVolume: int(v.Totalvolume),
  174. TotleTurnover: float64(v.Totalturnover),
  175. HoldVolume: int(v.Holdvolume),
  176. Settle: utils.IntToFloat64(int(v.Settle), dcplace),
  177. TimeStamp: fisrtDate,
  178. }
  179. if req.IsAsc {
  180. rst = append(rst, historyData)
  181. } else {
  182. // 插入第一条
  183. rear := append([]HistoryData{}, rst[0:]...)
  184. rst = append(append(rst[:0], historyData), rear...)
  185. }
  186. } else {
  187. // 判断最后周期是否已经存在盘面周期
  188. hd := rst[len(rst)-1]
  189. if hd.TimeStamp.Before(fisrtDate) {
  190. historyData := HistoryData{
  191. Opened: utils.IntToFloat64(int(v.Opened), dcplace),
  192. Highest: utils.IntToFloat64(int(v.Highest), dcplace),
  193. Lowest: utils.IntToFloat64(int(v.Lowest), dcplace),
  194. Closed: utils.IntToFloat64(int(v.Last), dcplace),
  195. TotleVolume: int(v.Totalvolume),
  196. TotleTurnover: float64(v.Totalturnover),
  197. HoldVolume: int(v.Holdvolume),
  198. Settle: utils.IntToFloat64(int(v.Settle), dcplace),
  199. TimeStamp: fisrtDate,
  200. }
  201. if req.IsAsc {
  202. rst = append(rst, historyData)
  203. } else {
  204. // 插入第一条
  205. rear := append([]HistoryData{}, rst[0:]...)
  206. rst = append(append(rst[:0], historyData), rear...)
  207. }
  208. }
  209. }
  210. }
  211. }
  212. }
  213. }
  214. // 查询成功
  215. logger.GetLogger().Debugln("QueryHistoryDatas successed, rows:%v", len(rst))
  216. appG.Response(http.StatusOK, e.SUCCESS, rst)
  217. }
  218. // HistoryTikData Tik数据
  219. type HistoryTikData struct {
  220. TimeStamp time.Time `json:"TS"` // 行情时间文本
  221. PE float64 `json:"PE"` // 现价
  222. Vol int `json:"Vol"` // 现量
  223. TT float64 `json:"TT"` // 现金额
  224. Bid float64 `json:"Bid"` // 买价
  225. BV int `json:"BV"` // 买量
  226. Ask float64 `json:"Ask"` // 卖价
  227. AV int `json:"AV"` // 卖量
  228. HV int `json:"HV"` // 持仓量
  229. HI int `json:"HI"` // 单笔持仓
  230. TDR int `json:"TDR"` // 交易方向,0:买 1:卖
  231. TK int `json:"TK"` // 交易类型
  232. }
  233. // QueryHistoryTikDatasReq 查询行情Tik数据请求参数
  234. type QueryHistoryTikDatasReq struct {
  235. GoodsCode string `form:"goodsCode" binding:"required"`
  236. StartTime string `form:"startTime"`
  237. EndTime string `form:"endTime"`
  238. Count int `form:"count"`
  239. IsAsc bool `form:"isAsc"`
  240. }
  241. // QueryHistoryTikDatas 查询行情Tik数据
  242. // @Summary 查询行情Tik数据
  243. // @Produce json
  244. // @Security ApiKeyAuth
  245. // @Param goodsCode query string true "商品代码"
  246. // @Param startTime query string false "开始时间,格式:yyyy-MM-dd HH:mm:ss"
  247. // @Param endTime query string false "结束时间,格式:yyyy-MM-dd HH:mm:ss"
  248. // @Param count query int false "条数"
  249. // @Param isAsc query bool false "是否按时间顺序排序(默认为时间倒序排序)"
  250. // @Success 200 {object} HistoryTikData
  251. // @Failure 500 {object} app.Response
  252. // @Router /Quote/QueryHistoryTikDatas [get]
  253. // @Tags 行情服务
  254. func QueryHistoryTikDatas(c *gin.Context) {
  255. appG := app.Gin{C: c}
  256. // 获取请求参数
  257. var req QueryHistoryTikDatasReq
  258. if err := appG.C.ShouldBindQuery(&req); err != nil {
  259. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  260. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  261. return
  262. }
  263. // 转换时间
  264. timeFormat := "2006-01-02 15:04:05" // go中的时间格式化必须是这个时间
  265. var startTime *time.Time
  266. if len(req.StartTime) > 0 {
  267. st, err := time.ParseInLocation(timeFormat, req.StartTime, time.Local)
  268. if err != nil {
  269. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  270. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  271. return
  272. }
  273. startTime = &st
  274. }
  275. var endTime *time.Time
  276. if len(req.EndTime) > 0 {
  277. et, err := time.ParseInLocation(timeFormat, req.EndTime, time.Local)
  278. if err != nil {
  279. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  280. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_TIME_FORMAT_FAIL, nil)
  281. return
  282. }
  283. endTime = &et
  284. }
  285. // 查询数据
  286. tikDatas, err := models.GetHistoryTikDatas(req.GoodsCode, startTime, endTime, req.Count, req.IsAsc)
  287. if err != nil {
  288. logger.GetLogger().Errorf("QueryHistoryTikDatas failed: %s", err.Error())
  289. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  290. return
  291. }
  292. // 获取目标商品报价小数位
  293. var dcplace int = 0
  294. if len(tikDatas) > 0 {
  295. dcplace = mtpcache.GetGoodsDecimalplace(req.GoodsCode)
  296. }
  297. // 计算最终价格
  298. rst := make([]HistoryTikData, 0)
  299. for _, v := range tikDatas {
  300. // 获取方向
  301. buyOrSell := 0
  302. if v.TDR == 83 { // (Ascii) B-66 S-83
  303. buyOrSell = 1
  304. }
  305. rst = append(rst, HistoryTikData{
  306. TimeStamp: time.Unix(int64(v.AT), 0),
  307. PE: utils.IntToFloat64(v.PE, dcplace),
  308. Vol: v.Vol,
  309. TT: float64(v.TT),
  310. Bid: utils.IntToFloat64(v.Bid, dcplace),
  311. BV: v.BV,
  312. Ask: utils.IntToFloat64(v.Ask, dcplace),
  313. AV: v.AV,
  314. HV: v.HV,
  315. HI: v.HI,
  316. TDR: buyOrSell,
  317. TK: v.TK,
  318. })
  319. }
  320. // 查询成功
  321. logger.GetLogger().Debugln("QueryHistoryTikDatas successed, rows: %v", len(rst))
  322. appG.Response(http.StatusOK, e.SUCCESS, rst)
  323. }
  324. // QueryTSDataReq 分时图数据查询请求参数
  325. type QueryTSDataReq struct {
  326. GoodsCode string `form:"goodsCode" binding:"required"` // 商品代码
  327. }
  328. // QueryTSDataRsp 分时图数据查询返回模型
  329. type QueryTSDataRsp struct {
  330. GoodsCode string `json:"goodsCode"` // 商品代码
  331. OutGoodsCode string `json:"outGoodsCode"` // 外部商品代码
  332. DecimalPlace int `json:"decimalPlace"` // 小数位
  333. TradeDate string `json:"tradeDate"` // 交易日
  334. StartTime time.Time `json:"startTime"` // 开始时间
  335. EndTime time.Time `json:"endTime"` // 结束时间
  336. PreSettle float64 `json:"preSettle"` // 昨结价
  337. Count int `json:"Count"` // 交易日应有数据个数
  338. HistoryDatas []HistoryData `json:"historyDatas"` // 历史数据
  339. RunSteps []TSDataRunStep `json:"runSteps"` // 交易日开休市计划
  340. }
  341. // TSDataRunStep 分时图交易日开休市计划
  342. type TSDataRunStep struct {
  343. Groupid int32 `json:"groupid"` // 分组ID
  344. Tradeweekday int32 `json:"tradeweekday"` // 交易日归属 - 0:星期天、1:星期一、2:星期二、3:星期三、4:星期四、5:星期五、6:星期六
  345. Sectionid int32 `json:"sectionid"` // 从 1 开始 往下编 [0为系统清盘、结算时间] SectionId = 0时,开始时间=清盘时间 开始周几= 清盘周几, 结束时间=结算时间 结束周几=结算周几
  346. Runstep int32 `json:"runstep"` // 运行阶段 - 2:连续交易
  347. Startweekday int32 `json:"startweekday"` // 起始周几
  348. Starttime string `json:"starttime"` // 起始时间(HH:mm)
  349. Endweekday int32 `json:"endweekday"` // 结束周几
  350. Endtime string `json:"endtime"` // 结束时间(HH:mm)
  351. Startflag int32 `json:"startflag"` // 开始日标识 - (-1:上日 0:当日 1:次日 )
  352. Endflag int32 `json:"endflag"` // 结束日标识 - (-1:上日 0:当日 1:次日 )
  353. Start time.Time `json:"start"` // 真实开始时间
  354. End time.Time `json:"end"` // 真实结束时间
  355. }
  356. // QueryTSData 分时图数据查询
  357. // @Summary 分时图数据查询
  358. // @Produce json
  359. // @Security ApiKeyAuth
  360. // @Param goodsCode query string true "商品代码"
  361. // @Success 200 {object} QueryTSDataRsp
  362. // @Failure 500 {object} app.Response
  363. // @Router /Quote/QueryTSData [get]
  364. // @Tags 行情服务
  365. func QueryTSData(c *gin.Context) {
  366. appG := app.Gin{C: c}
  367. // 获取请求参数
  368. var req QueryTSDataReq
  369. if err := appG.C.ShouldBindQuery(&req); err != nil {
  370. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  371. appG.Response(http.StatusBadRequest, e.INVALID_PARAMS, nil)
  372. return
  373. }
  374. // FIXME: - 一些不常变化的数据(如市场信息、商品信息等)应缓存到Redis中, 或缓存到服务内存
  375. // 获取商品信息
  376. goods, err := models.GetGoodsByGoodsCode(req.GoodsCode)
  377. if goods == nil {
  378. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  379. appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
  380. return
  381. }
  382. // 获取市场
  383. market, err := models.GetMarketByGoodsCode(req.GoodsCode)
  384. if err != nil {
  385. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  386. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  387. return
  388. }
  389. if market == nil {
  390. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  391. appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKET_FAILED, nil)
  392. return
  393. }
  394. // 获取目标品种交易日
  395. // FIXME: - 由于mtp2.0目前未同步外部交易所品种的当前交易日,
  396. // 故通道交易的品种目前只能在交易系统的外部市场中获
  397. // 取统一的交易日,后期应要求服务端同步外部数据
  398. // 2021.06.30 业务更新:15、97和99市场的行情交易日要从商品盘面中获取
  399. quoteTradeDate := ""
  400. if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
  401. if quoteDays, err := models.GetQuoteDays("'" + req.GoodsCode + "'"); err == nil {
  402. if len(quoteDays) > 0 {
  403. quoteTradeDate = time.Unix(quoteDays[0].Exchangedate, 0).Format("20060102")
  404. }
  405. }
  406. } else {
  407. if marketRun, err := models.GetMarketRun(int(market.Marketid)); err == nil {
  408. quoteTradeDate = marketRun.Tradedate2
  409. }
  410. }
  411. if len(quoteTradeDate) == 0 {
  412. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  413. appG.Response(http.StatusBadRequest, e.ERROR_GET_MARKETRUN_FAILED, nil)
  414. return
  415. }
  416. // 获取目标品种的开休市计划
  417. var runSteps []map[string]interface{}
  418. // 通道交易外部市场开休市计划表 - QuoteSourceGroupRunStep; 其它市场的 - MarketRunStepDetail
  419. if market.Trademode == 15 || market.Trademode == 97 || market.Trademode == 99 {
  420. // 外部市场
  421. sourceRunSteps, err := models.FindQuoteSourceGroupRunSteps(*goods)
  422. if err != nil {
  423. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  424. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  425. return
  426. }
  427. for _, v := range sourceRunSteps {
  428. // struct -> json
  429. if jsonBytes, err := json.Marshal(v); err == nil {
  430. // json -> struct
  431. var runStepMap map[string]interface{}
  432. json.Unmarshal(jsonBytes, &runStepMap)
  433. runSteps = append(runSteps, runStepMap)
  434. }
  435. }
  436. }
  437. // 非外部市场或外部市场没有配置QuoteSourceGroupRunStep表数据的情况下,都从MarketRunStepDetail中获取数据
  438. if len(runSteps) == 0 {
  439. sourceRunSteps, err := models.FindMarketRunStepDetails(int(market.Marketid))
  440. if err != nil {
  441. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  442. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  443. return
  444. }
  445. for _, v := range sourceRunSteps {
  446. // struct -> json
  447. if jsonBytes, err := json.Marshal(v); err == nil {
  448. // json -> struct
  449. var runStepMap map[string]interface{}
  450. json.Unmarshal(jsonBytes, &runStepMap)
  451. runSteps = append(runSteps, runStepMap)
  452. }
  453. }
  454. }
  455. // 获取目标商品的盘面信息
  456. quoteDays, err := models.GetQuoteDays("'" + strings.ToUpper(goods.Goodscode) + "'")
  457. if err != nil {
  458. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  459. appG.Response(http.StatusBadRequest, e.ERROR_GET_GOODS_FAILED, nil)
  460. return
  461. }
  462. var preSettle float64
  463. var preSettleInt int
  464. if len(quoteDays) > 0 {
  465. if quoteDays[0].Presettle != 0 {
  466. preSettleInt = int(quoteDays[0].Presettle)
  467. preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
  468. }
  469. if preSettle == 0 && quoteDays[0].Preclose != 0 {
  470. preSettleInt = int(quoteDays[0].Preclose)
  471. preSettle = utils.IntToFloat64(preSettleInt, int(goods.Decimalplace))
  472. }
  473. }
  474. // 构建返回数据
  475. // 注意:这里使用的是 marketRun.Tradedate2 行情交易日
  476. queryTSDataRsp := QueryTSDataRsp{
  477. GoodsCode: goods.Goodscode,
  478. OutGoodsCode: goods.Outgoodscode,
  479. TradeDate: quoteTradeDate,
  480. DecimalPlace: int(goods.Decimalplace),
  481. PreSettle: preSettle,
  482. }
  483. // 构建分时图可直接使用的开休市数据
  484. // 这里有一个知识点:TRADEWEEKDAY 与 STARTWEEKDAY,以及 TRADEWEEKDAY 与 ENDWEEKDAY 之间相差最多一天(管理端限制),
  485. // 所以目前并不支持正真的周五夜盘模式。我们在实现时不用做得太复杂。
  486. // 当前交易日(周几)对应的开休市计划
  487. tradeDate, err := time.ParseInLocation("20060102", quoteTradeDate, time.Local)
  488. if err != nil {
  489. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  490. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  491. return
  492. }
  493. // !!!开休市计划明细
  494. curWeekRunSteps := make([]map[string]interface{}, 0)
  495. for _, v := range runSteps {
  496. tradeWeekDay := int(v["tradeweekday"].(float64))
  497. if tradeWeekDay == int(tradeDate.Weekday()) {
  498. curWeekRunSteps = append(curWeekRunSteps, v)
  499. }
  500. }
  501. // 获取不到可用的开休市计划
  502. if len(curWeekRunSteps) == 0 {
  503. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  504. appG.Response(http.StatusBadRequest, e.ERROR_GET_RUNSTEP_FAILED, nil)
  505. return
  506. }
  507. // 按 SECTIONID 顺序排序
  508. sort.Slice(curWeekRunSteps, func(i int, j int) bool {
  509. return curWeekRunSteps[i]["sectionid"].(float64) < curWeekRunSteps[j]["sectionid"].(float64)
  510. })
  511. // 把各开休市时间段转化为真实的时间
  512. // 关于开休市计划的时间顺序:管理端会按时间顺序添加开休市计划,所以交易日开始时间为第一条记录的开始时间,结束时间为最后一条记录的结束时间
  513. // 关于目标商品的交易日问题:目前只能从商品所属市场获取当前交易日,这样有两个问题,一是不能按常规显示最后一个有历史数据的交易日;二是目前所有外部商品的开休市计划都是一样的。
  514. timeFormat := "20060102 15:04"
  515. // 开始时间
  516. startInterval := getTradeDay(int(curWeekRunSteps[0]["tradeweekday"].(float64)), int(curWeekRunSteps[0]["startweekday"].(float64)))
  517. queryTSDataRsp.StartTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, curWeekRunSteps[0]["starttime"].(string)), time.Local)
  518. if startInterval != 0 {
  519. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
  520. queryTSDataRsp.StartTime = queryTSDataRsp.StartTime.Add(duration)
  521. }
  522. // 结束时间
  523. index := len(curWeekRunSteps) - 1
  524. endInterval := getTradeDay(int(curWeekRunSteps[index]["tradeweekday"].(float64)), int(curWeekRunSteps[index]["endweekday"].(float64)))
  525. queryTSDataRsp.EndTime, _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, curWeekRunSteps[index]["endtime"].(string)), time.Local)
  526. if endInterval != 0 {
  527. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
  528. queryTSDataRsp.EndTime = queryTSDataRsp.EndTime.Add(duration)
  529. }
  530. // fmt.Printf("开始时间:%s 结束时间:%s\n", queryTSDataRsp.StartTime.Format(timeFormat), queryTSDataRsp.EndTime.Format(timeFormat))
  531. // 获取目标时间段的历史数据(1分钟周期)
  532. // 这里要注意:由于交易库和行情库由于GoodsCode大小写不一定对得上,所以在使用交易库的商品查询行情数据时间,都要使用OutGoodsCode字段
  533. // 20210702: 按行情服务要求,改用大写的GoodsCode
  534. cycleDatas, err := models.GetHistoryCycleDatas(models.CycleTypeMinutes1, strings.ToUpper(queryTSDataRsp.GoodsCode), &queryTSDataRsp.StartTime, &queryTSDataRsp.EndTime, 0, true)
  535. if err != nil {
  536. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  537. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  538. return
  539. }
  540. // ==================== 补数据(补休市数据和缺少的数据)====================
  541. if len(cycleDatas) > 0 {
  542. // 补数据第一步:如果第一数据不是开始时间的,需要补数据
  543. sources := time.Unix(int64(cycleDatas[0].ST), 0)
  544. diff := sources.Sub(queryTSDataRsp.StartTime)
  545. if diff.Minutes() > 0 {
  546. minute := int(diff.Minutes())
  547. for i := 1; i <= minute; i++ {
  548. st := cycleDatas[0].ST - i*60
  549. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  550. // 前面的数据使用昨结来补
  551. cycleDatas = append(cycleDatas, models.CycleData{
  552. GC: cycleDatas[0].GC,
  553. Open: preSettleInt,
  554. High: preSettleInt,
  555. Low: preSettleInt,
  556. Close: preSettleInt,
  557. TV: 0,
  558. TT: 0,
  559. HV: 0,
  560. SP: 0,
  561. ST: st,
  562. SST: stt,
  563. FI: true,
  564. })
  565. }
  566. }
  567. // 接时间戳排序
  568. sort.Slice(cycleDatas, func(i int, j int) bool {
  569. return cycleDatas[i].ST < cycleDatas[j].ST
  570. })
  571. // 补数据第二步:按尾部的时间(当前服务器时间或最后休市时间)进行全补
  572. // 获取服务器时间
  573. s, _ := models.GetServerTime()
  574. endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
  575. if err != nil {
  576. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  577. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  578. return
  579. }
  580. if endTime.After(queryTSDataRsp.EndTime) {
  581. endTime = queryTSDataRsp.EndTime
  582. }
  583. index := len(cycleDatas) - 1
  584. sources = time.Unix(int64(cycleDatas[index].ST), 0)
  585. diff = endTime.Sub(sources)
  586. if diff.Minutes() > 0 {
  587. minute := int(diff.Minutes())
  588. for i := 1; i <= minute; i++ {
  589. st := cycleDatas[index].ST + i*60
  590. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  591. cycleDatas = append(cycleDatas, models.CycleData{
  592. GC: cycleDatas[index].GC,
  593. Open: cycleDatas[index].Close,
  594. High: cycleDatas[index].Close,
  595. Low: cycleDatas[index].Close,
  596. Close: cycleDatas[index].Close,
  597. TV: 0,
  598. TT: 0,
  599. HV: 0,
  600. SP: 0,
  601. ST: st,
  602. SST: stt,
  603. FI: true,
  604. })
  605. }
  606. }
  607. // 接时间戳排序
  608. sort.Slice(cycleDatas, func(i int, j int) bool {
  609. return cycleDatas[i].ST < cycleDatas[j].ST
  610. })
  611. // 补数据第三步:补中间数据
  612. fillDatas := make([]models.CycleData, 0)
  613. for i := range cycleDatas {
  614. // 第一条记录跳过
  615. if i == 0 {
  616. continue
  617. }
  618. current := time.Unix(int64(cycleDatas[i].ST), 0)
  619. prev := time.Unix(int64(cycleDatas[i-1].ST), 0)
  620. diff := current.Sub(prev)
  621. if diff.Minutes() > 1 {
  622. minute := int(diff.Minutes())
  623. // 判断是否需要补数据,与上一条数据的间距不是一分钟
  624. if minute > 1 {
  625. // 注意补中间数据时,是不用补最后一条数据的,所以是 < minute
  626. for j := 1; j < minute; j++ {
  627. st := cycleDatas[i-1].ST + j*60
  628. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  629. fillDatas = append(fillDatas, models.CycleData{
  630. GC: cycleDatas[i-1].GC,
  631. Open: cycleDatas[i-1].Close,
  632. High: cycleDatas[i-1].Close,
  633. Low: cycleDatas[i-1].Close,
  634. Close: cycleDatas[i-1].Close,
  635. TV: 0,
  636. TT: 0,
  637. HV: 0,
  638. SP: 0,
  639. ST: st,
  640. SST: stt,
  641. FI: true,
  642. })
  643. }
  644. }
  645. }
  646. }
  647. // 加入到源数据
  648. cycleDatas = append(cycleDatas, fillDatas...)
  649. // 接时间戳排序
  650. sort.Slice(cycleDatas, func(i int, j int) bool {
  651. return cycleDatas[i].ST < cycleDatas[j].ST
  652. })
  653. } else {
  654. // TODO: - 下面这块操作需求确认
  655. // 如果查询结果是空数据,则使用昨结价补到服务器时间(或最后休市时间)
  656. // 获取服务器时间
  657. s, _ := models.GetServerTime()
  658. endTime, err := time.ParseInLocation("2006-01-02T15:04:05Z", *s, time.Local)
  659. if err != nil {
  660. logger.GetLogger().Errorf("QueryTSData failed: %s", err.Error())
  661. appG.Response(http.StatusBadRequest, e.ERROR_QUERY_FAIL, nil)
  662. return
  663. }
  664. if endTime.After(queryTSDataRsp.EndTime) {
  665. endTime = queryTSDataRsp.EndTime
  666. }
  667. diff := endTime.Sub(queryTSDataRsp.StartTime)
  668. minute := int(diff.Minutes())
  669. for i := 1; i <= minute; i++ {
  670. st := int(queryTSDataRsp.StartTime.Unix()) + i*60
  671. stt := time.Unix(int64(st), 0).Format("2006-01-02 15:04:05")
  672. cycleDatas = append(cycleDatas, models.CycleData{
  673. GC: queryTSDataRsp.GoodsCode,
  674. Open: preSettleInt,
  675. High: preSettleInt,
  676. Low: preSettleInt,
  677. Close: preSettleInt,
  678. TV: 0,
  679. TT: 0,
  680. HV: 0,
  681. SP: 0,
  682. ST: st,
  683. SST: stt,
  684. FI: true,
  685. })
  686. }
  687. // 接时间戳排序
  688. sort.Slice(cycleDatas, func(i int, j int) bool {
  689. return cycleDatas[i].ST < cycleDatas[j].ST
  690. })
  691. }
  692. // 补数据第四步:清除掉开市计划外的数据
  693. // 先计算出每条计划明细的真正开始与结束时间
  694. queryTSDataRsp.RunSteps = make([]TSDataRunStep, 0)
  695. for _, v := range curWeekRunSteps {
  696. // 开始时间
  697. startInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["startweekday"].(float64)))
  698. v["start"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, v["starttime"].(string)), time.Local)
  699. if startInterval != 0 {
  700. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", startInterval*24))
  701. v["start"] = v["start"].(time.Time).Add(duration)
  702. }
  703. // 结束时间
  704. endInterval := getTradeDay(int(v["tradeweekday"].(float64)), int(v["endweekday"].(float64)))
  705. v["end"], _ = time.ParseInLocation(timeFormat, fmt.Sprintf("%s %s", quoteTradeDate, v["endtime"].(string)), time.Local)
  706. if endInterval != 0 {
  707. duration, _ := time.ParseDuration(fmt.Sprintf("%dh", endInterval*24))
  708. v["end"] = v["end"].(time.Time).Add(duration)
  709. }
  710. // map -> struct
  711. var tsDataRunStep TSDataRunStep
  712. jsonbody, err := json.Marshal(v)
  713. if err != nil {
  714. continue
  715. }
  716. if err := json.Unmarshal(jsonbody, &tsDataRunStep); err != nil {
  717. continue
  718. }
  719. queryTSDataRsp.RunSteps = append(queryTSDataRsp.RunSteps, tsDataRunStep)
  720. }
  721. // 最终返回的历史数据
  722. historyDatas := make([]HistoryData, 0)
  723. for _, cycleData := range cycleDatas {
  724. needAdd := false
  725. for _, runStep := range curWeekRunSteps {
  726. // 判断是否在开市计划内
  727. if cycleData.ST >= int(runStep["start"].(time.Time).Unix()) && cycleData.ST <= int(runStep["end"].(time.Time).Unix()) {
  728. needAdd = true
  729. break
  730. }
  731. }
  732. if needAdd {
  733. historyDatas = append(historyDatas, HistoryData{
  734. Opened: utils.IntToFloat64(cycleData.Open, int(goods.Decimalplace)),
  735. Highest: utils.IntToFloat64(cycleData.High, int(goods.Decimalplace)),
  736. Lowest: utils.IntToFloat64(cycleData.Low, int(goods.Decimalplace)),
  737. Closed: utils.IntToFloat64(cycleData.Close, int(goods.Decimalplace)),
  738. TotleVolume: cycleData.TV,
  739. TotleTurnover: float64(cycleData.TT),
  740. HoldVolume: cycleData.HV,
  741. Settle: utils.IntToFloat64(cycleData.SP, int(goods.Decimalplace)),
  742. TimeStamp: time.Unix(int64(cycleData.ST), 0),
  743. IsFill: cycleData.FI,
  744. })
  745. }
  746. }
  747. queryTSDataRsp.HistoryDatas = historyDatas
  748. // 计算当前交易日应该有多少个历史数据,主要用于iOS
  749. for _, v := range queryTSDataRsp.RunSteps {
  750. diff := v.End.Sub(v.Start)
  751. queryTSDataRsp.Count += int(diff.Minutes())
  752. }
  753. // 查询成功
  754. logger.GetLogger().Debugln("QueryTSData successed.")
  755. appG.Response(http.StatusOK, e.SUCCESS, queryTSDataRsp)
  756. }
  757. // getTradeDay 获取结算计划中天数间隔的方法
  758. // - tradeDay: 交易日周几
  759. // - weekDay: 开始或结束周几
  760. // - Returns: 天数间隔
  761. func getTradeDay(tradeDay, weekDay int) int {
  762. if tradeDay == weekDay {
  763. return 0
  764. }
  765. if weekDay == 0 {
  766. weekDay = 7
  767. }
  768. betWeekDay := weekDay - tradeDay
  769. if betWeekDay < 0 {
  770. betWeekDay = betWeekDay + 7
  771. }
  772. if betWeekDay >= 4 {
  773. betWeekDay = betWeekDay - 7
  774. }
  775. return betWeekDay
  776. }
  777. /**
  778. 获取某日所在周周一的日期
  779. */
  780. func getFirstDateOfWeek(d time.Time) time.Time {
  781. offset := int(time.Monday - d.Weekday())
  782. if offset > 0 {
  783. offset = -6
  784. }
  785. weekStartDate := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, time.Local).AddDate(0, 0, offset)
  786. return weekStartDate
  787. }
  788. //获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。
  789. func getFirstDateOfMonth(d time.Time) time.Time {
  790. d = d.AddDate(0, 0, -d.Day()+1)
  791. return getZeroTime(d)
  792. }
  793. //获取传入的时间所在年的第一天,即某年第一天的0点。如传入time.Now(), 返回当前年的第一天0点时间。
  794. func getFirstDateOfYear(d time.Time) time.Time {
  795. d = d.AddDate(0, -d.Year()+1, 0)
  796. return getZeroTime(d)
  797. }
  798. //获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。
  799. func getLastDateOfMonth(d time.Time) time.Time {
  800. return getFirstDateOfMonth(d).AddDate(0, 1, -1)
  801. }
  802. //获取某一天的0点时间
  803. func getZeroTime(d time.Time) time.Time {
  804. return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location())
  805. }