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  }