gitee.com/quant1x/engine@v1.8.4/datasource/base/tdx_realtime.go (about) 1 package base 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "gitee.com/quant1x/engine/cache" 8 "gitee.com/quant1x/exchange" 9 "gitee.com/quant1x/gotdx" 10 "gitee.com/quant1x/gotdx/quotes" 11 "gitee.com/quant1x/gox/api" 12 "gitee.com/quant1x/gox/logger" 13 "gitee.com/quant1x/gox/runtime" 14 "os" 15 "time" 16 ) 17 18 var ( 19 ErrTdxApiQuotesTickMaxBatchSizeExceeded = errors.New(fmt.Sprintf("[tdx-api-quotes-tick]batch size exceeded maximum(%d) limit", quotes.TDX_SECURITY_QUOTES_MAX)) 20 ) 21 22 // BatchRealtimeBasicKLine 批量获取实时行情数据 23 func BatchRealtimeBasicKLine(codes []string) error { 24 if len(codes) > quotes.TDX_SECURITY_QUOTES_MAX { 25 return ErrTdxApiQuotesTickMaxBatchSizeExceeded 26 } 27 now := time.Now() 28 nowServerTime := now.Format(exchange.CN_SERVERTIME_FORMAT) 29 lastTradingDay := exchange.LastTradeDate() 30 today := exchange.Today() 31 if lastTradingDay != today { 32 // 当天非交易日, 不更新, 直接返回 33 return nil 34 } 35 if nowServerTime < exchange.CN_TradingStartTime || nowServerTime > exchange.CN_TradingStopTime { 36 // 非交易时间, 不更新, 直接返回 37 return nil 38 } 39 40 tdxApi := gotdx.GetTdxApi() 41 var err error 42 var hq []quotes.Snapshot 43 retryTimes := 0 44 for retryTimes < quotes.DefaultRetryTimes { 45 list, err := tdxApi.GetSnapshot(codes) 46 if err == nil && list != nil && len(list) > 0 { 47 hq = list 48 break 49 } 50 retryTimes++ 51 } 52 if err != nil { 53 logger.Errorf("获取即时行情数据失败", err) 54 return err 55 } 56 for _, v := range hq { 57 if v.State == quotes.TDX_SECURITY_TRADE_STATE_DELISTING || v.Code == exchange.StockDelisting || v.LastClose == float64(0) { 58 // 终止上市的数据略过 59 continue 60 } 61 securityCode := exchange.GetMarketFlag(v.Market) + v.Code 62 kl := KLine{ 63 Date: lastTradingDay, // 默认 64 Open: v.Open, 65 Close: v.Price, 66 High: v.High, 67 Low: v.Low, 68 Volume: float64(v.Vol), 69 Amount: v.Amount, 70 Up: v.BVol, 71 Down: v.SVol, 72 } 73 cacheKLines := LoadBasicKline(securityCode) 74 cacheLength := len(cacheKLines) 75 if cacheLength == 0 { 76 continue 77 } 78 // 获取缓存中最后一根K线的日期 79 klineFilename := cache.KLineFilename(securityCode) 80 cacheLastDate := cacheKLines[cacheLength-1].Date 81 if len(cacheLastDate) == 0 { 82 // 如果缓存文件异常, 则删除 83 data, _ := json.Marshal(cacheKLines[cacheLength-1]) 84 text := api.Bytes2String(data) 85 logger.Errorf("realtime kline error, code: %s, date=[%s]", securityCode, text) 86 _ = os.Remove(klineFilename) 87 // 全量更新K线 88 UpdateAllBasicKLine(securityCode) 89 continue 90 } 91 ts := exchange.TradeRange(cacheLastDate, lastTradingDay) 92 if len(ts) > 2 { 93 // 超过2天的差距, 不能用realtime更新K线数据 94 // 只能是当天更新 或者是新增, 跨越2个以上的交易日不更新 95 // 全量更新K线 96 UpdateAllBasicKLine(securityCode) 97 continue 98 } 99 // 数据差异数 100 diffDays := 0 101 // 当日的K线数据已经存在 102 if cacheLastDate == lastTradingDay { 103 // 如果最后一条数据和最后一个交易日相同, 那么去掉缓存中的最后一条, 用实时数据填补 104 // 这种情况的出现是K线被更新过了, 现在做的是用快照更新K线 105 diffDays = 1 106 } else if nowServerTime > v.ServerTime { 107 diffDays = 0 108 } 109 var klines []KLine 110 if diffDays > 0 { 111 klines = cacheKLines[:cacheLength-diffDays] 112 } else { 113 klines = cacheKLines 114 } 115 // 连接缓存和实时数据 116 klines = append(klines, kl) 117 err := api.SlicesToCsv(klineFilename, klines) 118 if err != nil { 119 logger.Errorf("更新K线数据文件失败:%s", v.Code) 120 } 121 } 122 return nil 123 } 124 125 // BasicKLineForSnapshot 通过snapshot更新基础K线 126 func BasicKLineForSnapshot(v quotes.Snapshot) { 127 now := time.Now() 128 nowServerTime := now.Format(exchange.CN_SERVERTIME_FORMAT) 129 lastTradeday := exchange.LastTradeDate() 130 today := exchange.Today() 131 if lastTradeday != today { 132 // 当天非交易日, 不更新, 直接返回 133 if !runtime.Debug() { 134 return 135 } 136 } 137 if nowServerTime < exchange.CN_TradingStartTime || nowServerTime > exchange.CN_TradingStopTime { 138 // 非交易时间, 不更新, 直接返回 139 if !runtime.Debug() { 140 return 141 } 142 } 143 if v.State == quotes.TDX_SECURITY_TRADE_STATE_DELISTING || v.Code == exchange.StockDelisting || v.LastClose == float64(0) { 144 // 终止上市的数据略过 145 return 146 } 147 //securityCode := proto.GetMarketFlag(v.Market) + v.Code 148 securityCode := v.SecurityCode 149 kl := KLine{ 150 Date: lastTradeday, 151 Open: v.Open, 152 Close: v.Price, 153 High: v.High, 154 Low: v.Low, 155 Volume: float64(v.Vol), 156 Amount: v.Amount, 157 Up: v.BVol, 158 Down: v.SVol, 159 } 160 cacheKLines := LoadBasicKline(securityCode) 161 cacheLength := len(cacheKLines) 162 if cacheLength == 0 { 163 return 164 } 165 // 获取缓存中最后一根K线的日期 166 klineFilename := cache.KLineFilename(securityCode) 167 cacheLastDate := cacheKLines[cacheLength-1].Date 168 if len(cacheLastDate) == 0 { 169 // 如果缓存文件异常, 则删除 170 data, _ := json.Marshal(cacheKLines[cacheLength-1]) 171 text := api.Bytes2String(data) 172 logger.Errorf("realtime kline error, code: %s, date=[%s]", securityCode, text) 173 _ = os.Remove(klineFilename) 174 // 全量更新K线 175 UpdateAllBasicKLine(securityCode) 176 return 177 } 178 ts := exchange.TradeRange(cacheLastDate, lastTradeday) 179 if len(ts) > 2 { 180 // 超过2天的差距, 不能用realtime更新K线数据 181 // 只能是当天更新 或者是新增, 跨越2个以上的交易日不更新 182 // 全量更新K线 183 UpdateAllBasicKLine(securityCode) 184 return 185 } 186 // 数据差异数 187 diffDays := 0 188 // 当日的K线数据已经存在 189 if cacheLastDate == lastTradeday { 190 // 如果最后一条数据和最后一个交易日相同, 那么去掉缓存中的最后一条, 用实时数据填补 191 // 这种情况的出现是K线被更新过了, 现在做的是用快照更新K线 192 diffDays = 1 193 } else if nowServerTime > v.ServerTime { 194 diffDays = 0 195 } 196 var klines []KLine 197 if diffDays > 0 { 198 klines = cacheKLines[:cacheLength-diffDays] 199 } else { 200 klines = cacheKLines 201 } 202 // 更新缓存K线 203 UpdateCacheKLines(securityCode, klines) 204 // 连接缓存和实时数据 205 klines = append(klines, kl) 206 err := api.SlicesToCsv(klineFilename, klines) 207 if err != nil { 208 logger.Errorf("更新K线数据文件失败:%s", v.Code) 209 } 210 }