FMZ量化交易程序化交易中的K线数据处理浅谈
admin
2023-08-02 15:41:53
0

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意

国区邀请链接: https://www.myts3cards.com/cn/join/1837888   币种多,交易量大!

国际邀请链接:https://www.okx.com/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

FMZ量化交易平台邀请链接:https://www.fmz.com/

程序化交易中的K线数据处理浅谈

在编写程序化交易策略时,使用K线数据,经常会有需求使用一些非标准周期K线数据的情况,例如需要使用12分钟周期K线数据、4小时K线周期数据,通常这类非标准周期是无法直接获取的。那么我们如何应对此类需求呢?
答案肯定是有办法的。
非标准周期可以通过更小周期的数据,合并合成获取,可以想象一下,多个周期中的最高价,算作合成后的最高价,最低价算作合成后的最低价,开盘价不会变,就用合成这根K线原料数据的第一个开盘价,收盘价对应的是用合成这根K线的原料数据的最后一个的收盘价,时间就是取的开盘价的时间,成交量用原料数据的交易量求和计算得出。
就如图:

  • 思路

    我们以区块链资产 市场 BTC_USDT 为例,用1小时合成为4小时。

    160839e30fc7510ec013160839e30fc7510ec013

    16d9c43be40e9586015916d9c43be40e95860159

    160e0b813438cbaf6247160e0b813438cbaf6247

    15f522dff9eeb4a5c31715f522dff9eeb4a5c317

    时间
    2019.8.12 00:00 11447.07 11382.57 11367.2 11406.92
    2019.8.12 01:00 11420 11405.65 11366.6 11373.83
    2019.8.12 02:00 11419.24 11374.68 11365.51 11398.19
    2019.8.12 03:00 11407.88 11398.59 11369.7 11384.71

    这四个1小时周期的数据,合成一个根4小时周期的数据,开盘价即第一根 00:00 时间的开盘价:11382.57
    收盘价是 最后一根 即 03:00 时的收盘价:11384.71
    最高价就找这里面最高的价格:11447.07
    最低价就找这里面最低的价格:11365.51
    4小时周期 起始时间 就是 00:00 这根1小时K线的起始时间,即 2019.8.12 00:00
    成交量每根1小时的求和即可(主要观察价格如何合成,成交量数据中没有显示),这里不做赘述。

    合成出的 一根4小时K线即:
    高:11447.07
    开:11382.57
    低:11365.51
    收:11384.71
    时间:2019.8.12 00:00

    1679a745c0e11551b6161679a745c0e11551b616

    可以看到数据是一致的。

  • 编写代码实现

    验证了初步的思路,就可以动手写一写代码初步实现一下这个需求了。

    直接放出代码,代码仅供参考学习:

      function GetNewCycleRecords (sourceRecords, targetCycle) {    // K线合成函数
          var ret = []
          
          // 首先获取源K线数据的周期
          if (!sourceRecords || sourceRecords.length < 2) {
              return null
          }
          var sourceLen = sourceRecords.length
          var sourceCycle = sourceRecords[sourceLen - 1].Time - sourceRecords[sourceLen - 2].Time
    
          if (targetCycle % sourceCycle != 0) {
              Log(\"targetCycle:\", targetCycle)
              Log(\"sourceCycle:\", sourceCycle)
              throw \"targetCycle is not an integral multiple of sourceCycle.\"
          }
    
          if ((1000 * 60 * 60) % targetCycle != 0 && (1000 * 60 * 60 * 24) % targetCycle != 0) {
              Log(\"targetCycle:\", targetCycle)
              Log(\"sourceCycle:\", sourceCycle)
              Log((1000 * 60 * 60) % targetCycle, (1000 * 60 * 60 * 24) % targetCycle)
              throw \"targetCycle cannot complete the cycle.\"
          }
    
          var multiple = targetCycle / sourceCycle
    
    
          var isBegin = false 
          var count = 0
          var high = 0 
          var low = 0 
          var open = 0
          var close = 0 
          var time = 0
          var vol = 0
          for (var i = 0 ; i < sourceLen ; i++) {
              // 获取 时区偏移数值
              var d = new Date()
              var n = d.getTimezoneOffset()
    
              if (((1000 * 60 * 60 * 24) - sourceRecords[i].Time % (1000 * 60 * 60 * 24) + (n * 1000 * 60)) % targetCycle == 0) {
                  isBegin = true
              }
    
              if (isBegin) {
                  if (count == 0) {
                      high = sourceRecords[i].High
                      low = sourceRecords[i].Low
                      open = sourceRecords[i].Open
                      close = sourceRecords[i].Close
                      time = sourceRecords[i].Time
                      vol = sourceRecords[i].Volume
    
                      count++
                  } else if (count < multiple) {
                      high = Math.max(high, sourceRecords[i].High)
                      low = Math.min(low, sourceRecords[i].Low)
                      close = sourceRecords[i].Close
                      vol += sourceRecords[i].Volume
    
                      count++
                  }
    
                  if (count == multiple || i == sourceLen - 1) {
                      ret.push({
                          High : high,
                          Low : low,
                          Open : open,
                          Close : close,
                          Time : time,
                          Volume : vol,
                      })
                      count = 0
                  }
              }
          }
    
          return ret 
      }
    
      // 测试
      function main () {
          while (true) {
              var r = exchange.GetRecords()                           // 原始数据,作为合成K线的基础K线数据,例如要合成4小时K线,可以用1小时K线作为原始数据。
              var r2 = GetNewCycleRecords(r, 1000 * 60 * 60 * 4)      // 通过 GetNewCycleRecords 函数 传入 原始K线数据 r , 和目标周期, 1000 * 60 * 60 * 4 即 目标合成的周期 是4小时K线数据。
    
              $.PlotRecords(r2, \"r2\")                                 // 策略类库栏 可以勾选画线类库,调用 $.PlotRecords 画线类库 导出函数 画图。
              Sleep(1000)                                             // 每次循环间隔 1000 毫秒,防止访问K线接口获取数据过于频繁,导致交易所限制。
          }
      }
    

    其实要合成K线,就需要两个东西,第一是需要原料数据,即小周期的K线数据,例子中 var r = exchange.GetRecords() 
    获取的小周期K线数据。第二是需要明确合成为多大的周期,即 K线数据合成的目标周期。
    然后通过 GetNewCycleRecords 函数的算法,就可以最后返回一个合成出来的K线数组结构的数据了。
    需要注意的是:

    • 1、目标周期不能小于你传入GetNewCycleRecords 函数作为数据原料的K线的周期。
      因为无法用小周期去合成更小的周期的数据。
    • 2、设置的目标周期必须是周期闭合的。
      什么是周期闭合?
      简单说就是在一小时内或者在一天之内,目标周期时间范围组合在一起,组成一个闭合的循环。
      举例:
      例如 12分钟周期的K线,从每个小时的0分0秒开始(以0时举例),第一个周期是00:00:00 ~ 00:12:00,第二个周期是00:12:00 ~ 00:24:00,第三个周期是00:24:00 ~ 00:36:00,第四个周期是00:36:00 ~ 00:48:00,第五个周期是00:48:00 ~ 01:00:00 ,正好组成一个完整的1小时。如果是 13分钟周期,就是不闭合的周期,这样的周期算出的数据不唯一,因为根据合成的数据起始点不同,合成出来的数据有差异。

    实盘运行了一下:
    166aa24ceaa3bf0dfe0c166aa24ceaa3bf0dfe0c

    对比交易所图表
    172cc5e1fd86fbf2faab172cc5e1fd86fbf2faab

  • 使用K线数据构造需要的数据结构

    经常有群友提问,我想计算每根K线的最高价的均线,怎么办?

    通常,我们计算均线都是计算的收盘价的均值,组成均线,但是也有时候有需求计算最高价、最低价、开盘价等等。
    这个时候就不能直接把exchange.GetRecords() 函数返回的K线数据直接传入 指标计算函数了。

    例如:
    talib.MA 均线指标计算函数有两个参数,第一个参数是需要传入的数据,第二个参数是指标周期参数。
    例如 我们要算如下图的指标
    16acafc54e7145815c4116acafc54e7145815c41

    K线周期是4小时,
    在交易所图表上,已经设置好了一条均线,均线周期参数为9。
    并且设置计算的数据源是每根Bar的最高价。
    1791ef92c921555f10e61791ef92c921555f10e6
    即这条均线是9个4小时周期K线Bar的最高价平均计算出的均值,组成的指标均线。

    我们自己动手构造一个数据算下,看是不是和交易所的图表计算得出的一样。

    var highs = []
    for (var i = 0 ; i < r2.length ; i++) {
        highs.push(r2[i].High)
    }
    

    既然要计算每根Bar的最高价的均值得出均线指标。
    那么就需要先构造一个数组,其中每个数据元素都是对应每根Bar的最高价。
    可以看到 highs 变量初始为一个空数组,然后我们遍历 r2 这个K线数据变量(不记得r2了?看下上面合成4小时K线的main函数中的代码)。
    读取r2每根Bar的最高价(即 r2[i].High , i取值范围 从 0 到 r2.length – 1 ),然后 push 进highs 。这样就构造了一个和K线数据Bar一一对应的数据结构。

    此时 highs 就可以传入 talib.MA函数计算出均线了。

    完整的例子:

    function main () {
        while (true) {
            var r = exchange.GetRecords()
            var r2 = GetNewCycleRecords(r, 1000 * 60 * 60 * 4)
            if (!r2) {
                continue
            }
            
            $.PlotRecords(r2, \"r2\")                                               // 画出K线
            
            var highs = []
            for (var i = 0 ; i < r2.length ; i++) {
                highs.push(r2[i].High)
            }
            
            var ma = talib.MA(highs, 9)                                           // 用均线指标函数 talib.MA 计算 均线指标
            $.PlotLine(\"high_MA9\", ma[ma.length - 2], r2[r2.length - 2].Time)     // 使用画线类库把均线指标画在图表上
            
            Sleep(1000)
        }
    }
    

    回测运行:

    16cffc44655f7c57b11716cffc44655f7c57b117

    可以看到 图中鼠标停留位置的均线指标值均为 11466.9289

    以上代码可以复制到策略中运行测试,记得勾选「画线类库」后保存!

  • 数字货币市场的K线数据获取方式

    发明者量化交易平台已经有封装好的接口,即 exchange.GetRecords 函数,即可获取K线数据。
    下面着重讲解的是直接访问交易所K线数据接口获取数据,因为有时候需要指定参数获取更多的K线,封装的GetRecords 接口
    一般是返回 100根。如果遇到策略初始需要超过100根的K线时,就需要收集等待。
    为了让策略尽快进行运作,可以自己封装一个函数,直接访问交易所K线接口,指定参数获取更多的K线数据。

    以火必币币交易 BTC_USDT 交易对为例,我们实现这个需求:

    找到交易所的API文档,查看K线接口描述:
    1704616f81e6c93258e71704616f81e6c93258e7

    https://api.huobi.pro/market/history/kline?period=1day&size=200&symbol=btcusdt
    

    参数:

    参数名 类型 是否必要 描述 取值
    symbol string true 交易对 btcusdt, ethbtc…
    period string true 返回数据时间粒度,也就是每根蜡烛的时间区间 1min, 5min, 15min, 30min, 60min, 1day, 1mon, 1week, 1year
    size integer false 返回 K 线数据条数 [1, 2000]

    测试代码:

    function GetRecords_Huobi (period, size, symbol) {
        var url = \"https://api.huobi.pro/market/history/kline?\" + \"period=\" + period + \"&size=\" + size + \"&symbol=\" + symbol
        var ret = HttpQuery(url)
        
        try {
            var jsonData = JSON.parse(ret)
            var records = []
            for (var i = jsonData.data.length - 1; i >= 0 ; i--) {
                records.push({
                    Time : jsonData.data[i].id * 1000,
                    High : jsonData.data[i].high,
                    Open : jsonData.data[i].open,
                    Low : jsonData.data[i].low,
                    Close : jsonData.data[i].close,
                    Volume : jsonData.data[i].vol,
                })
            }
            return records
        } catch (e) {
            Log(e)
        }
    }  
    
    
    function main() {
        var records = GetRecords_Huobi(\"1day\", \"300\", \"btcusdt\")
        Log(records.length)
        $.PlotRecords(records, \"K\")
    }
    

    Python版本,访问火必交易所接口的范例:

#!python3import jsonimport urllib2def GetRecords_Huobi(period, size, symbol):    headers = {\'User-Agent\':\'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6\'}    url = \"https://api.huobi.pro/market/history/kline?\" + \"period=\" + period + \"&size=\" + size + \"&symbol=\" + symbol    request = urllib2.Request(url)      request.add_header(\'User-Agent\',\'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6\')      opener = urllib2.build_opener()      f= opener.open(request)      ret = f.read().decode(\'utf-8\')          try :        jsonData = json.loads(ret)                records = []        for i in range(len(jsonData[\"data\"]) - 1, -1, -1):            records.append({                \"Time\" : jsonData[\"data\"][i][\"id\"] * 1000,                 \"High\" : jsonData[\"data\"][i][\"high\"],                 \"Open\" : jsonData[\"data\"][i][\"open\"],                 \"Low\" : jsonData[\"data\"][i][\"low\"],                 \"Close\" : jsonData[\"data\"][i][\"close\"],                 \"Volume\" : jsonData[\"data\"][i][\"vol\"],             })        return records    except Exception as e:        Log(e)        def main():    r = GetRecords_Huobi(\"1day\", \"300\", \"btcusdt\")    Log(len(r))    ext.PlotRecords(r, \"K\")   # 需要引用Python画线类库

Python版本,访问币安交易所的K线接口的范例:

#!python3
import json
import urllib2

def GetRecords_Huobi(period, size, symbol):
    headers = {\'User-Agent\':\'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6\'}
    url = \"https://api.binance.com/api/v3/klines?symbol=\" + symbol + \"&interval=\" + period
    request = urllib2.Request(url)  
    request.add_header(\'User-Agent\',\'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6\')  
    opener = urllib2.build_opener()  
    f= opener.open(request)  
    ret = f.read().decode(\'utf-8\')  
    try :
        jsonData = json.loads(ret)
        
        records = []
        for i in range(len(jsonData)):
            records.append({
                \"Time\" : float(jsonData[i][0]),
                \"High\" : float(jsonData[i][2]), 
                \"Open\" : float(jsonData[i][1]), 
                \"Low\" : float(jsonData[i][3]), 
                \"Close\" : float(jsonData[i][4]), 
                \"Volume\" : float(jsonData[i][5]), 
            })
        return records
    except Exception as e:
        Log(e)
        
def main():
    r = GetRecords_Huobi(\"1m\", \"300\", \"BTCUSDT\")
    Log(len(r))
    ext.PlotRecords(r, \"K\")   # 需要引用Python画线类库


16b234820c8cb41323c216b234820c8cb41323c2

16ecba278e62dfc2e17616ecba278e62dfc2e176

可以看到日志上,打印 records.length 为 300, 即 records K线数据 bar 数量有300根。
1776e48bb8e0cbb812d61776e48bb8e0cbb812d6

FMZ量化交易平台邀请链接:https://www.fmz.com/

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意,邀请链接:https://www.myts3cards.com/cn/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!。

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

买好币上币库:https://www.kucoin.com/r/1f7w3

火必所有用户现在可用了,但是要重新注册账号火币:https://www.huobi.com

全球最大交易所币安,

国区邀请链接:https://accounts.suitechsui.mobi/zh-CN/register?ref=16003031 支持86手机号码,网页直接注册。

相关内容

热门资讯

Windows 11 和 10... Windows 11/10 文件夹属性中缺少共享选项卡 – 已修复 1.检查共享选项卡是否可用 右键...
Radmin VPN Wind... Radmin VPN 是一款免费且用户友好的软件,旨在牢固地连接计算机以创建一个有凝聚力的虚拟专用网...
如何修复 Steam 内容文件... Steam 内容文件锁定是当您的 Steam 文件无法自行更新时出现的错误。解决此问题的最有效方法之...
在 Windows 11 中打... 什么是链路状态电源管理? 您可以在系统控制面板的电源选项中看到链接状态电源管理。它是 PCI Exp...
Hive OS LOLMine... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...
事件 ID 7034:如何通过... 点击进入:ChatGPT工具插件导航大全 服务控制管理器 (SCM) 负责管理系统上运行的服务的活动...
在 iCloud 上关闭“查找... 如果您是 Apple 的长期用户,您肯定会遇到过 Find My 应用程序,它本机安装在 iPhon...
iPhone 屏幕上有亮绿色斑... iPhone 是市场上最稳定的智能手机之一,这主要归功于专为它们设计的 iOS 操作系统。然而,他们...
balenaEtcher烧录后... balenaEtcher烧录后u盘或者内存卡无法识别不能使用的解决方法想要恢复原来的方法,使用win...
统信UOS每次开机后不直接进入... 统信UOS每次开机后不直接进入系统而是进入到recovery模式 按方向上键选择UOS 20 SP1...