基于CCXT接口建立的多模块数字货币量化交易模型(MMQT)在python中的实现

不羁岁月 提交于 2020-12-25 11:49:34


版权声明:如需对此文章代码进行转载请注明出处,若用于商业用途、论文写作请私信或联系作者邮箱940648114@qq.com

一、前言

问题的引出

        量化交易是指以先进的数学模型替代人为的主观判断,利用计算机技术从庞大的历史数据中海选能带来超额收益的多种“大概率”事件以制定策略,极大地减少了投资者情绪波动的影响,避免在市场极度狂热或悲观的情况下作出非理性的投资决策。
        数字货币是一种基于节点网络和数字加密算法的虚拟货币价值的数字化表示。可以认为不由央行或当局发行,也不与法币挂钩,但由于被公众所接受,所以可作为支付手段,也可以电子形式转移、存储或交易。
        随着美国的无限量化宽松政策以及疫情的持续爆发,美元和黄金市场持续低迷,导致了大量的避险资金涌入币圈,造成了今年从四月份以来主流币凶猛的涨势。同时,伴随着国际上对于比特币等主流币的认同声逐渐增加,许多国外的金融机构都对于数字货币市场跃跃欲试,其中具有代表性是灰度信托,近几年它一直在不可逆性持续增仓,并且在二级市场发售数字货币的基金信托,这更是催生了更多的大资金买家入场,其中不乏中国的很多基金公司。
        由于数字货币24*7小时不间断的交易市场的连续性,并且量化交易可以达到高频交易的效果,从数字货币市场入手显然是做量化的很好的起步点。目前数字货币市场仍然是不成熟的。平台交易系统的宕机,k线插针依然是会偶尔出现,对于量化交易也是一种风险所在。不过对于数字货币进行量化交易总体来看依然是利大于弊。因为通过模型的回测训练和时间序列的回测分析,我们可以在最短时间能尝试到数百种模型中最合适的方式,甚至在本文介绍的Multi Module Quantitative Transaction(MMQT)框架下,我们可以把机器学习放在对时间序列的处理中,或许能达到传统计量模型所难达到的效果。以下,我将依照模型框架构建顺序介绍如何实现数字货币量化交易。


MMQT模型的优势

  • 通过python编写,可修改性强,兼容性强
  • 多模块的设计可以根据自身的需要进行模块的替换
  • 对运算要求低,在高频交易中可以节省交易时间
  • 对初学者上手快,较易学习

二、MMQT简介

        类似遗传算法一样,在MMQT模型中,我把交易者在交易过程中的每个步骤转化成模型的各个模块。我先将整体框架展示一下:
在这里插入图片描述
        通俗的来讲,整个量化交易的过程实际上就是对交易人员的代替,通过稳定的客观判断决策来避免主观判断所造成的损失。而这个功能的实现主要由数个小模块构成,利用这样的模块化量化系统可以很好的随时进行拓展,也能很容易进行Debug。

1.接口模块

        通过这个模块,我们可以获取一切可以获取的相关信息,并且把从交易所得到的所有信息转化为我们交易系统中通用的语言。我们在这个模块可以实现账户信息、交易对信息、订单信息、买卖交易、获取实时行情对等等。通过这个模块获得的信息和进行的操作,基本上能够满足我们的整个量化交易过程。

2.风控模块

        通过这个模块,我们可以实现在每一次交易前进行对整体的风险把控并终止某些交易。我们在这个板块可以实现的功能有仓位控制、余额报警、发送邮件微信等等。

3.策略模块

        通过这个板块,我们可以进行数据的清洗并转化为talib熟悉的时间序列,通过我们决定的策略进行交易,例如BBANDS布林线指标、DEMA双移动平均线、EMA指数平均数等等。

4.反馈模块

        通过这个板块,我们对已经开的交易订单进行查询并更新,更新自身的订单列表,实现监控。本文中反馈模块并没有单独定义成一个类,而是写在了策略中,大家可以根据自己的需要自行定义类的内容和相应方法。

三、MMQT的代码实现

        本文用的账号是火币交易所,如果没有账号的可以先进行注册或者通过ccxt实例化到你自己一直用的交易所。
        ccxt是一个封装了诸多数字货币交易平台的api的开源库。支持python、php、javascrit三种语言,github上可以下载源码。ccxt结构明确,易于使用,所有api被封装成统一格式的接口,返回数据被封装成统一格式的字典,基本省去了api开发时间。MMQT模型就是避开了对于火币平台进行直接对接,减少了很大的开发代价,同时也使得模型适用于其他交易所。大家可以通过pip或brew进行安装,资源还是很多的。

1.定义中间模块(类)

        这一步,我们会建立我们第一个类,在这个类,我们将实现MMQT框架中的接口模块,同样的,里面很多的数据都是通过实例化后的ccxt传入的字典进行赋值的,并不需要太多麻烦的步骤。在这里,我们至少要写上后面其他模块需要信息数据,也要定义一些最基本的开单撤单的方法,以下将进行逐步讲解。

1.初始化

        将实例化后的ccxt传入我们的中间类进行中间类的实例化,并且定义一些比较重要的后面需要的参数,包括了交易币种,交易精度。

class MidClass():
    
    #初始化
    def __init__(self,ThisExchange):
        self.Exchange=ThisExchange
        self.Symbol=ThisExchange.symbol
        self.AmountPrecision=ThisExchange.AmountPrecision
        self.PricePrecision=ThisExchange.PricePrecision

2.获取账户信息、交易对信息、订单信息

        因为方法都类似,就把获取信息的方法放在一起进行统一的说明,思路就是通过ccxt导入我们所需要的信息。

    #获得交易对行情信息
    def GetTicker(self):
        self.High='___'
        self.Low='___'
        self.Buy='___'
        self.Sell='___'
        self.Last='___'
        try:
            self.Ticker=self.Exchange.fetchTicker(self.Symbol)
            self.High=self.Ticker['high']
            self.Low=self.Ticker['low']
            self.Buy=self.Ticker['bid']
            self.Sell=self.Ticker['ask']
            self.Last=self.Ticker['last']
            return True#只要有一个成功就返回True
        except:
            return False#如果全都获取不了返回False
        
    #获得账户对于该交易对信息
    def GetAccount(self):
        self.Account='___'
        self.Balance='___'
        self.FrozenBalance='___'
        self.Stocks='___'
        self.FrozenStocks='___'
        
        self.SymbolStocksName=self.Symbol.split('/')[0]
        self.SymbolBalanceName=self.Symbol.split('/')[1]
        try:
            self.Account=self.Exchange.fetchBalance()
            self.Balance=self.Account[self.SymbolBalanceName]['free']
            self.FrozenBalance=self.Account[self.SymbolBalanceName]['used']
            self.Stocks=self.Account[self.SymbolStocksName]['free']
            self.FrozenStocks=self.Account[self.SymbolStocksName]['used']
            return True
        except:
            return False

        感兴趣的同学可以去ccxt中文手册上看一下相关返回的值,这里主要通过字典索引方式将我们基本上用得到的数据提取了出来,大家也可以根据自己需要再去读取一些其他数据,比如市场深度等等。在文中的上述代码中我们实现了以下功能:获取交易对最高价最低价,买盘卖盘最优价以及最新价格,在提取账户信息中,我们获取账户余额,冻结余额,指定币对的余额以及冻结余额。通过try的简单函数可以告诉我们是否成功赋值,如果因为数据缺失或者没有连上网都会进行报错提示监控人员。

3.数据更新

        我们写好了如果调取数据的方法,那么我们就可以再写一个方法进行统一的刷新交易币对行情数据和账户数据,通过判断语句可以方便的统一告诉我们是否成功刷新。

    #确认是否获取到账户和交易对信息
    def RefreshData(self):
        if not self.GetAccount():
            return 'false get account'
        if not self.GetTicker():
            return 'false get ticker'
        return'refresh data finish!'

4.创建订单

        由于taker和maker手续费不同,我们选择了低手续费的挂单方法。在定义创建订单函数时,我们要指定交易类型以及价格数量。在开单结束后我们还需要重新刷新Account信息。

    #创建订单
    def CreateOrder(self,OrderType,Price,Amount):
        if OrderType=='buy':
            #执行买单
            OrderId=self.Exchange.createLimitBuyOrder(self.Symbol,round(Amount,self.AmountPrecision),round(Price,self.PricePrecision))['id']
        elif OrderType=='sell':
            #执行卖单
            OrderId=self.Exchange.createLimitSellOrder(self.Symbol,round(Amount,self.AmountPrecision),round(Price,self.PricePrecision))['id']
        else:
            pass
        #订单每次执行结束后,等待一点时间,让订单执行完,再刷新数据,再返回订单
        time.sleep(1)
        self.GetAccount()
        return OrderId

5.获取订单状态

        我们创建了订单后,需要对订单进行实时的检查,因为挂单不一定在短时间会成交,通过定义查询订单状态的方法,可以方便我们可以在后续的模块中进行一些未成交订单的处理,避免风险以及资金的占用浪费。

    #获取订单状态
    def GetOrder(self,Idd):
        self.OrderId='___'
        self.OrderPrice='___'
        self.OrderNum='___'
        self.OrderDealNum='___'
        self.OrderAvgPrice='___'
        self.OrderStatus='___'
        
        try:
            self.Order=self.Exchange.fetchOrder(Idd,self.Symbol)
            self.OrderId=self.Order['id']
            self.OrderPrice=self.Order['price']
            self.OrderNum=self.Order['amount']
            self.OrderDealNum=self.Order['filled']
            self.OrderAvgPrice=self.Order['average']
            self.OrderStatus=self.Order['status']
            return True
        except:
            return False

        通过上述方法,我们可以提取出以下信息:订单id、下单价格、下单数量、成交均价,订单状态等等。

6.撤销订单

        往往由于各种原因,我们的订单没有成交,其中一种方式就是撤单后改变我们的订单价格再重新下单,这样能够更快的成交不错过行情。

    #取消订单
    def CancelOrder(self,Idd):
        self.CancelResult= '___'
        try:
            self.CancelResult = self.Exchange.cancelOrder(Idd,self.Symbol)
            return True
        except:
            return False 

7.获取k线信息

        在之前我们只是获取了交易对的基本信息,对于信息处理远远不够,利用调取k线的方法获取一定的时间序列可以方便的直接对接talib包进行策略分析。在这里我选择获取1分钟的k线,大家可以根据自己策略的需求调用5m、30m、甚至1d都可以,具体有多少选择可以查看交易所官网的k线图上的选项。

    #获取k线数据
    def GetRecords(self,Timeframe='1m'):
        self.Records='___'
        try:
            self.Records=self.Exchange.fetchOHLCV(self.Symbol,Timeframe)
            return True
        except:
            return False

2.定义风控模块(类)

        风控模块最基础的就是保证我们余额足够交易,在此就做一个监测余额的方法作为演示,同学们可以自己写一些其他的方法放在这个模块里。

class RiskClass():
    
    #风控模块初始化,传入实例化后的中间类
    def __init__(self,ThisMyMid):
        self.MyMid=ThisMyMid
        
    def CheckRisk(self,Price,Amount):
        self.MyMid.RefreshData()
        if self.MyMid.Balance>=Price*Amount:
            return True
        else:
            print('余额不足,买单未执行')
            return False

3.定义策略模块(类)

       通过这个模块,我们可以将自己选择的策略模型添加到其中,我并不建议大家自己去写一些策略方法,通过talib我们可以很轻松的得到我们所需要的策略信号。talib集成了市面上几乎所有你能看到的时间序列分析的方法,由于长时间的沉淀,talib几乎已经完美。在这个类,我用双均线模型,即快慢线交易策略来做演示。我还将反馈模型集合在了策略类中,用来监控通过这个策略我们所开的所有的单,并且进行相应的风控处理。

1.策略模块初始化

       因为我们选用的是双均线策略,即MA均线,我们需要传入几个参数:实例化的中间类、实例化的风控类、每次下单的数量、talib所需要的两个时间窗口

class DoubleMa():
    
    #双均线策略初始化,传入传入实例化后的中间类以及双均线需要的窗口参数
    def __init__(self,ThisMyMid,ThisMyRisk,BuySellAmount,MyFastWindow,MySlowWindow):
        self.MyMid=ThisMyMid
        self.MyRisk=ThisMyRisk
        self.RemainStocks=self.MyMid.Stocks
        self.BuySellAmount=BuySellAmount
        self.FastWindow=MyFastWindow
        self.SlowWindow=MySlowWindow
        self.SentOrders=[]#创建一个订单列表,可以记录目前的委托订单状态

通过简单的赋值,我们可以得到了接下来所有策略所需要的参数信息,那么接下来就到了最重要的环节了。值得一提的是SentOrders这个变量,它将会记载每次策略执行的订单情况,作为了反馈模块的核心。在触发下单后,交易数量会通过初始化得到的BuySellAmount确定,而交易价格可以根据自己的情况进行调整,通常而言,限价下单时,下买单通常低于市场价格,卖单通常高于市场价格,大家可以自行替换,我在策略中设置的买卖价比例分别是0.99和1.01。

2.技术分析及交易下单

    #数据清洗并作出分析
    def BeginTrade(self):
        self.MyMid.GetRecords()
        self.CloseArrar=np.zeros(1000)#初始化收盘价数组,一共1000根k线有1000个数据
        t=0                            
        for i in self.MyMid.Records:          
            self.CloseArrar[t]=i[4]
            t+=1    
#         print(self.CloseArrar)
        self.FastMaArrar=talib.SMA(self.CloseArrar,self.FastWindow)#快速均线数组
        self.SlowMaArrar=talib.SMA(self.CloseArrar,self.SlowWindow)#慢速均线数组  
        #得到最新的Ma值,包括最近一个和上一个
        self.fast_ma0 = self.FastMaArrar[-1]
        self.fast_ma1 = self.FastMaArrar[-2]
        self.slow_ma0 = self.SlowMaArrar[-1]
        self.slow_ma1 = self.SlowMaArrar[-2]
        #金叉和死叉的判断
        CrossOver = self.fast_ma0 > self.slow_ma0 and self.fast_ma1 < self.slow_ma1#金叉
        CrossBelow = self.fast_ma0 < self.slow_ma0 and self.fast_ma1 > self.slow_ma1#死叉
        #通过判断进行交易
        if CrossOver: #如果金叉买入
              if MyRisk.CheckRisk(0.23,self.BuySellAmount):#风控
                    self.OrderId=self.MyMid.CreateOrder("buy",self.CloseArrar[-1],self.BuySellAmount)#创建买单
                    self.SentOrders.append(self.OrderId)#添加这一个订单id到订单id列表
                    print('买入价',self.CloseArrar[-1]*0.99)
                    print('产生一个限价买单!!!!!!!!!!!!!!!!!')  
        if CrossBelow:#如果死叉卖出
              if self.RemainStocks>self.BuySellAmount:
                    self.OrderId=self.MyMid.CreateOrder("sell",self.CloseArrar[-1],self.BuySellAmount)#创建卖单
                    self.SentOrders.append(self.OrderId)#添加这一个订单id到订单id列表
                    print('卖出价',self.CloseArrar[-1]*1.01)
                    print('产生一个限价卖单!!!!!!!!!!!!!!!!!')
              else:
                    print('该币种数量不足,卖单未执行')

       对于每一步我都已经做出了解释,还是在重复一遍,这个策略可以更换成其他策略,策略会因交易对,交易时间的不同而显现不同的决策效果,需要交易者自己进行回测分析,对于回测方法我也会在后面跟大家分享。

3.反馈模块

       SentOrders的最大作用是方便我们对未成交的订单做出一定的处理,通过以下的方法,我们可以针对订单的不同状态,即挂单中、已成交、已撤单进行相应操作,对SentOrders列表的如下操作将会大大减少我们后期查询订单状态的麻烦,并且可以快速对于长期未成功交易的订单进行相应处理。

    #检查策略完成后的信息
    def CheckAndReTrade(self):
        for i in self.SentOrders:#在订单id列表中遍历
            self.MyMid.GetOrder(i)#获得目前的订单id的订单状态
            if self.MyMid.OrderStatus=='closed':#如果订单完成
                self.SentOrders.remove(i)#移除在订单id列表的信息
            if self.MyMid.OrderStatus=='open':
                pass#不进行操作,可以修改价格再放上去,因为长期放着会占用保证金
            if self.MyMid.OrderStatus=='canceled':#如果撤单
                self.SentOrders.remove(i)#移除在订单id列表的信息,或者可以修改价格再放上去

4.相关类实例化及程序运行

        在本次的介绍中,用的交易对是刚上新的OXT/USDT,我使用的是火币交易所,火币交易所的api接口设置需要用电脑登陆网页,一般默认给的权限是读取和交易,不建议设置提币。由于服务器在国外,大陆的用户需要自己爬梯子或者添加代理,可以到网上查询相关教程,这里就不展开了,如果不这么做程序基本上是一直报错的。

1.ccxt实例化

        通过这一步,我们进行ccxt的实例化,指定目标账户,交易币对,交易所交易精度,一定要注意交易价格和数量的精度在不同平台或不同币对都不一样,在选择交易对的同时一定要查看精度,另一个需要注意的是平台最低的单笔订单交易额,不然容易造成交易损失或报错。

apiKey='填上你自己的'
secret='填上你自己的'

exchange=ccxt.huobipro({
   
    
#     #代理部分
#     'proxies':{
   
    
#     'http':'socks5h://127.0.0.1:7891',
#     'https':'socks5h://127.0.0.1:7891'
#     },
    #api登陆
    'apiKey':apiKey,
    'secret':secret    
    }) 
#币种
exchange.symbol='OXT/USDT'
#交易所该币种交易最小数量精度
exchange.AmountPrecision=4
#交易所该币种价格最小精度
exchange.PricePrecision=4

2.中间类、风控类、策略类实例化

       接下来就是要对接我们所有定义的类了,我们需要做的就是对于各个类进行实例化并且进行测试。在这里我选择了快慢线窗口分别为3和10,每次下单数量为30的策略,用来回测我在之前教程中选择的策略参数。

#中间模块实例化
MyMid=MidClass(exchange)

#数据更新
print(MyMid.RefreshData())

#风险模块实例化
MyRisk=RiskClass(MyMid)

#策略模块实例化
MyDoubleMa=DoubleMa(MyMid,MyRisk,BuySellAmount=30,MyFastWindow=3,MySlowWindow=10)#设置交易参数



#显示相关数据
print(MyMid.Symbol,'最新价:',MyMid.Last)
print('该币种可用额度为:',round(MyMid.Stocks,2),MyMid.SymbolStocksName) 
print('该币种冻结额度为:',round(MyMid.FrozenStocks,2),MyMid.SymbolStocksName)
print('账户可用额度为:',round(MyMid.Balance,2),'USD') 
print('账户冻结额度为:',round(MyMid.FrozenBalance,2),'USD')

       如果前面都没有问题的话,大家就可以看到下面的效果:
在这里插入图片描述
       如果没有显示以上数据或者报错,说明哪里可能出了问题。如果正常显示的也不一定代表别的没问题,主要要看最后能不能正常循环开单。

3.调控程序

       在定义了所有我们需要的方法后,我们一切就绪,可以开始进行量化交易的编码。在这里,由于我调用的是1m的k线信息,因此我这里进行每60秒操作一次。每两分钟对未成交的挂单进行撤单处理。
       主控程序的整体思想是实现以下步骤:

  1. 等待60秒
  2. 策略处理(开单或没有任何操作)
  3. 反馈
  4. 打印目前订单状态
  5. 打印账户信息和交易对最新数据
  6. 如果经过两轮的挂单依然没有成交,进行撤单处理
  7. 回到第一步
step=1
while True:
    time.sleep(60)
    MyDoubleMa.BeginTrade()
    MyDoubleMa.CheckAndReTrade()
    print('目前挂单情况',MyDoubleMa.SentOrders)
    #数据更新
    print(MyMid.RefreshData())
    print(MyMid.Symbol,'最新价:',MyMid.Last)
    print('该币种可用额度为:',round(MyMid.Stocks,2),MyMid.SymbolStocksName) 
    print('该币种冻结额度为:',round(MyMid.FrozenStocks,2),MyMid.SymbolStocksName)
    print('账户可用额度为:',round(MyMid.Balance,2),'USD') 
    print('账户冻结额度为:',round(MyMid.FrozenBalance,2),'USD')
    print('------------------------第',step,'轮尝试,等待60秒----------------------------')
    if step%2==0:
#         print('排除挂单')
        for i in MyDoubleMa.SentOrders:#在订单id列表中遍历
            MyDoubleMa.MyMid.GetOrder(i)#获得目前的订单id的订单状态
            if MyDoubleMa.MyMid.OrderStatus=='closed':#如果订单完成
                MyDoubleMa.SentOrders.remove(i)#移除在订单id列表的信息
            if MyDoubleMa.MyMid.OrderStatus=='open':#如果订单还没交易
                MyDoubleMa.MyMid.CancelOrder(i)#取消订单
                print('排除了一个未成交的挂单')
            if MyDoubleMa.MyMid.OrderStatus=='canceled':#如果撤单
                MyDoubleMa.SentOrders.remove(i)#移除在订单id列表的信息,或者可以修改价格再放上去
    step=step+1

       正常的迭代下去的效果应该是以下这样,由于在双均线策略下没有出现买点或者卖点,所以一直没有进行交易。
在这里插入图片描述

       如果双均线策略下出现了交易那么账户上的信息也将会发生改变,以下截图是在手机端看到的下单效果。
在这里插入图片描述
       由于每一次的卖单都没有成功触发,都被撤掉了,可以看出整个程序是正常运行的。

四、回测的代码实现

       通常而言,想要检测一个策略的可行性,如果直接用之前的MMQT实盘测试,经济成本和时间成本都太高,如何一次性设置一个可靠的策略参数成为了最大的问题,其中包括了策略模块的选择(talib的双均线,海龟,MACD等等),策略模块的参数(每次下单的数量,每次下单的价格,时间窗口的选择等等)。要知道这些参数的最佳状态都因不同的交易对,不同的交易时间而异,
       我们想要用最小的成本确定最佳的参数,就可以利用过去的数据进行回测。以下就简单对双均线策略进行回测。选择的时间周期是1m,币种依然是OXT,时间窗口分别为3和10,正好对于前面教程里的参数确定的交易策略进行回测。

1.获取数据

       我们获取数据的方式与MMQT的方法一样,通过ccxt实例化获取。

apiKey='填上你自己的'
secret='填上你自己的'

exchange=ccxt.huobipro({
   
    
#     #代理部分
#     'proxies':{
   
    
#     'http':'socks5h://127.0.0.1:7891',
#     'https':'socks5h://127.0.0.1:7891'
#     },
    #api登陆
    'apiKey':apiKey,
    'secret':secret    
    }) 
#币种
exchange.symbol='OXT/USDT'
Records=exchange.fetchOHLCV('OXT/USDT','1m')#获得1分钟k线数据

2.数据清洗

       我们需要得到最重要的收盘价,我们可以通过简单的索引得到这些数据。

CloseArrar=np.zeros(len(Records))#初始化收盘价数组,一共1000根k线有1000个数据
t=0                            
for i in Records:          
    CloseArrar[t]=i[4]
    t+=1   

3.模拟账户初始化

       既然是模拟账户,我们就要自己确定一些参数,其中包括了资产,初始代币数量,交易数量,交易手续费。这里的交易数量可以根据简单的操作通过仓位来确定。

#账户初始化
StartBalance=226#初始资金
Balance=StartBalance#目前资产
RemainStocks=0#账户持仓
BuySellAmount=round(0.2*Balance/Records[-1][4],0)#每次交易的数量,仓位两成
fee=0.01*0.2#交易所买卖的手续费

4.回测程序

       接下来就是确定我们的策略方式了,大家可以根据自己的选择调整相关参数以及策略模型来进行回测,以达到收益最大化。我选择的参数还是和实盘里的一样,用来检验在过去1000分钟我利用这个方法可以最终获得的收益。回测的方法和实盘是类似的,唯一不同的是通过对原始的账户余额不断的赋值来模拟真实账户资金的变化。由于是模拟盘,因此把限价挂单模拟成了市价下单,并且忽略了滑点或无人交易的情况。最终结果肯定会有一点微小的误差。

#双均线策略回测
FastMaArrar=talib.SMA(CloseArrar,3)#快速均线数组
SlowMaArrar=talib.SMA(CloseArrar,10)#慢速均线数组  

m=10
sell=buy=0
for m in range(len(Records)):
        LastPrice=Records[m][4]
        CrossOver = FastMaArrar[m]> SlowMaArrar[m] and FastMaArrar[m-1] < SlowMaArrar[m-1]#金叉
        CrossBelow = FastMaArrar[m] < SlowMaArrar[m]and FastMaArrar[m-1] > SlowMaArrar[m-1]#死叉
        if CrossOver: #如果金叉买入
              if Balance>=LastPrice*BuySellAmount:
                    Balance=Balance-LastPrice*BuySellAmount-LastPrice*BuySellAmount*fee
                    RemainStocks=RemainStocks+BuySellAmount
                    print('产生一个限价买单','资产当前资产',round(Balance+RemainStocks*Records[m][4],2),
                          '现金',round(Balance,2),'币',round(RemainStocks,2))  
                    buy+=1
              else:
                    print('余额不足!!!!!!!!!!')
        if CrossBelow:#如果死叉卖出
              if RemainStocks>=BuySellAmount:
                    Balance=Balance+LastPrice*BuySellAmount-LastPrice*BuySellAmount*fee
                    RemainStocks=RemainStocks-BuySellAmount
                    print('产生一个限价卖单','当前资产',round(Balance+RemainStocks*Records[m][4],2),
                          '现金',round(Balance,2),'币',round(RemainStocks,2))  
                    sell+=1
              else:
                    print('该币余额不足!!!!!!!!!!!!')
print('XXXXXXXXXXXXXXXX交易结束','当前资产',Balance+RemainStocks*Records[-1][4],'收益率为',
      round(((Balance+RemainStocks*Records[-1][4])/StartBalance-1)*100,2),'%','该币涨跌幅为',
      round(((Records[-1][4]/Records[0][4])-1)*100,2),'%'
     )
print('买单次数',buy,'卖单次数',sell)

       在每一次的交易后会立即更新当前账户的资产。还有一点不同的是,在回测结束后会,我增加了总的收益率和交易对的涨跌情况。
在这里插入图片描述
       可以看到在1000分钟后该交易对跌了近9个点,而通过双均线策略在考虑一百次交易手续费的情况下,总体仅亏了5.8个点,如果不考虑手续费,应该收益在正的5个点左右。可以看出,该方法还是有一定收益和抗风险的,但最后还是交易所赚了╮(╯▽╰)╭

五、总结与展望

1.MMQT的不足之处

       照例先进行自我批评。纵观近十年,量化交易早已在金融行业的各个领域生根发芽,我们能接触到的,打包好的模型肯定是最经典的,同时也意味着最原始和简单的模型,例如本文利用的双均线模型,也就所谓股民挂在嘴边的均线,早已在20世纪中期被美国投资大佬提出,甚至连现在很多金融投行用的CVaR指标也是上世纪提出来的。总而言之,MMQT的核心就在于决策的选择,而这个决策也是最需要与时俱进的,真正最新的,最好的策略几乎都是不会在互联网上找到,更不可能在哪里报个班,你学会了就能赚钱的。
       量化交易的深度学习并不是靠敲敲代码能实现的,量化的框架并不是很难搭建,最难的核心在于交易者对于市场规律的把控,而这种从不规律中获取规律的直觉感也正是机器所不能代替的,因此从另一个角度来看,如果交易者能够严格执行自己的策略,在瞬息万变的市场下,机器量化交易远远比不上人的主观操作。
       MMQT模型还较为基础,四大模块还有带扩充,市场上波动无常,行情规律也飘忽不定,读者可以根据自己的交易喜好补充自己的风控模块。本文阐述的只是个人对于量化交易板块的理解,我定义了一个框架,里面的灵魂需要靠大家自己去发挥设计。

2.致谢与参考资料

  • ccxt中文开发手册
  • 火币网

六、附录:完整代码

1.MMQT

import requests
import json
import ccxt
import time
import numpy as np
import talib

apiKey='填上你自己的'
secret='填上你自己的'

exchange=ccxt.huobipro({
   
    
#     #代理部分
#     'proxies':{
   
    
#     'http':'socks5h://127.0.0.1:7891',
#     'https':'socks5h://127.0.0.1:7891'
#     },
    #api登陆
    'apiKey':apiKey,
    'secret':secret    
    }) 
#币种
exchange.symbol='OXT/USDT'
#交易所该币种交易最小数量精度
exchange.AmountPrecision=4
#交易所该币种价格最小精度
exchange.PricePrecision=4



class MidClass():
    
    #初始化
    def __init__(self,ThisExchange):
        self.Exchange=ThisExchange
        self.Symbol=ThisExchange.symbol
        self.AmountPrecision=ThisExchange.AmountPrecision
        self.PricePrecision=ThisExchange.PricePrecision
        
    #获得交易对行情信息
    def GetTicker(self):
        self.High='___'
        self.Low='___'
        self.Buy='___'
        self.Sell='___'
        self.Last='___'
        try:
            self.Ticker=self.Exchange.fetchTicker(self.Symbol)
            self.High=self.Ticker['high']
            self.Low=self.Ticker['low']
            self.Buy=self.Ticker['bid']
            self.Sell=self.Ticker['ask']
            self.Last=self.Ticker['last']
            return True#只要有一个成功就返回True
        except:
            return False#如果全都获取不了返回False
        
    #获得账户对于该交易对信息
    def GetAccount(self):
        self.Account='___'
        self.Balance='___'
        self.FrozenBalance='___'
        self.Stocks='___'
        self.FrozenStocks='___'
        
        self.SymbolStocksName=self.Symbol.split('/')[0]
        self.SymbolBalanceName=self.Symbol.split('/')[1]
        try:
            self.Account=self.Exchange.fetchBalance()
            self.Balance=self.Account[self.SymbolBalanceName]['free']
            self.FrozenBalance=self.Account[self.SymbolBalanceName]['used']
            self.Stocks=self.Account[self.SymbolStocksName]['free']
            self.FrozenStocks=self.Account[self.SymbolStocksName]['used']
            return True
        except:
            return False
        
    #确认是否获取到账户和交易对信息
    def RefreshData(self):
        if not self.GetAccount():
            return 'false get account'
        if not self.GetTicker():
            return 'false get ticker'
        return'refresh data finish!'
    
    #创建订单
    def CreateOrder(self,OrderType,Price,Amount):
        if OrderType=='buy':
            #执行买单
            OrderId=self.Exchange.createLimitBuyOrder(self.Symbol,round(Amount,self.AmountPrecision),round(Price,self.PricePrecision))['id']
        elif OrderType=='sell':
            #执行卖单
            OrderId=self.Exchange.createLimitSellOrder(self.Symbol,round(Amount,self.AmountPrecision),round(Price,self.PricePrecision))['id']
        else:
            pass
        #订单每次执行结束后,等待一点时间,让订单执行完,再刷新数据,再返回订单
        time.sleep(1)
        self.GetAccount()
        return OrderId
    
    #获取订单状态
    def GetOrder(self,Idd):
        self.OrderId='___'
        self.OrderPrice='___'
        self.OrderNum='___'
        self.OrderDealNum='___'
        self.OrderAvgPrice='___'
        self.OrderStatus='___'
        
        try:
            self.Order=self.Exchange.fetchOrder(Idd,self.Symbol)
            self.OrderId=self.Order['id']
            self.OrderPrice=self.Order['price']
            self.OrderNum=self.Order['amount']
            self.OrderDealNum=self.Order['filled']
            self.OrderAvgPrice=self.Order['average']
            self.OrderStatus=self.Order['status']
            return True
        except:
            return False
    
    #取消订单
    def CancelOrder(self,Idd):
        self.CancelResult= '___'
        try:
            self.CancelResult = self.Exchange.cancelOrder(Idd,self.Symbol)
            return True
        except:
            return False 
        
    #获取k线数据
    def GetRecords(self,Timeframe='1m'):
        self.Records='___'
        try:
            self.Records=self.Exchange.fetchOHLCV(self.Symbol,Timeframe)
            return True
        except:
            return False


class RiskClass():
    
    #风控模块初始化,传入实例化后的中间类
    def __init__(self,ThisMyMid):
        self.MyMid=ThisMyMid
        
    def CheckRisk(self,Price,Amount):
        self.MyMid.RefreshData()
        if self.MyMid.Balance>=Price*Amount:
            return True
        else:
            print('余额不足,买单未执行')
            return False



#策略类
class DoubleMa():
    
    #双均线策略初始化,传入传入实例化后的中间类以及双均线需要的窗口参数
    def __init__(self,ThisMyMid,ThisMyRisk,BuySellAmount,MyFastWindow,MySlowWindow):
        self.MyMid=ThisMyMid
        self.MyRisk=ThisMyRisk
        self.RemainStocks=self.MyMid.Stocks
        self.BuySellAmount=BuySellAmount
        self.FastWindow=MyFastWindow
        self.SlowWindow=MySlowWindow
        self.SentOrders=[]#创建一个订单列表,可以记录目前的委托订单状态
        
    #数据清洗并作出分析
    def BeginTrade(self):
        self.MyMid.GetRecords()
        self.CloseArrar=np.zeros(1000)#初始化收盘价数组,一共1000根k线有1000个数据
        t=0                            
        for i in self.MyMid.Records:          
            self.CloseArrar[t]=i[4]
            t+=1    
        self.FastMaArrar=talib.SMA(self.CloseArrar,self.FastWindow)#快速均线数组
        self.SlowMaArrar=talib.SMA(self.CloseArrar,self.SlowWindow)#慢速均线数组  
        #得到最新的Ma值,包括最近一个和上一个
        self.fast_ma0 = self.FastMaArrar[-1]
        self.fast_ma1 = self.FastMaArrar[-2]
        self.slow_ma0 = self.SlowMaArrar[-1]
        self.slow_ma1 = self.SlowMaArrar[-2]
        #金叉和死叉的判断
        CrossOver = self.fast_ma0 > self.slow_ma0 and self.fast_ma1 < self.slow_ma1#金叉
        CrossBelow = self.fast_ma0 < self.slow_ma0 and self.fast_ma1 > self.slow_ma1#死叉
        #通过判断进行交易
        if CrossOver: #如果金叉买入
              if MyRisk.CheckRisk(0.23,self.BuySellAmount):#风控
                    self.OrderId=self.MyMid.CreateOrder("buy",self.CloseArrar[-1],self.BuySellAmount)#创建买单
                    self.SentOrders.append(self.OrderId)#添加这一个订单id到订单id列表
                    print('买入价',self.CloseArrar[-1]*0.99)
                    print('产生一个限价买单!!!!!!!!!!!!!!!!!')  
        if CrossBelow:#如果死叉卖出
              if self.RemainStocks>self.BuySellAmount:
                    self.OrderId=self.MyMid.CreateOrder("sell",self.CloseArrar[-1],self.BuySellAmount)#创建卖单
                    self.SentOrders.append(self.OrderId)#添加这一个订单id到订单id列表
                    print('卖出价',self.CloseArrar[-1]*1.01)
                    print('产生一个限价卖单!!!!!!!!!!!!!!!!!')
              else:
                    print('该币种数量不足,卖单未执行')
    
    #检查策略完成后的信息
    def CheckAndReTrade(self):
        for i in self.SentOrders:#在订单id列表中遍历
            self.MyMid.GetOrder(i)#获得目前的订单id的订单状态
            if self.MyMid.OrderStatus=='closed':#如果订单完成
                self.SentOrders.remove(i)#移除在订单id列表的信息
            if self.MyMid.OrderStatus=='open':
                pass#不进行操作,可以修改价格再放上去,因为长期放着会占用保证金
            if self.MyMid.OrderStatus=='canceled':#如果撤单
                self.SentOrders.remove(i)#移除在订单id列表的信息,或者可以修改价格再放上去


#中间模块实例化
MyMid=MidClass(exchange)

#数据更新
print(MyMid.RefreshData())

#风险模块实例化
MyRisk=RiskClass(MyMid)

#策略模块实例化
MyDoubleMa=DoubleMa(MyMid,MyRisk,BuySellAmount=30,MyFastWindow=3,MySlowWindow=10)#设置交易参数



#显示相关数据
print(MyMid.Symbol,'最新价:',MyMid.Last)
print('该币种可用额度为:',round(MyMid.Stocks,2),MyMid.SymbolStocksName) 
print('该币种冻结额度为:',round(MyMid.FrozenStocks,2),MyMid.SymbolStocksName)
print('账户可用额度为:',round(MyMid.Balance,2),'USD') 
print('账户冻结额度为:',round(MyMid.FrozenBalance,2),'USD')


step=1
while True:
    time.sleep(60)
    MyDoubleMa.BeginTrade()
    MyDoubleMa.CheckAndReTrade()
    print('目前挂单情况',MyDoubleMa.SentOrders)
    #数据更新
    print(MyMid.RefreshData())
    print(MyMid.Symbol,'最新价:',MyMid.Last)
    print('该币种可用额度为:',round(MyMid.Stocks,2),MyMid.SymbolStocksName) 
    print('该币种冻结额度为:',round(MyMid.FrozenStocks,2),MyMid.SymbolStocksName)
    print('账户可用额度为:',round(MyMid.Balance,2),'USD') 
    print('账户冻结额度为:',round(MyMid.FrozenBalance,2),'USD')
    print('------------------------第',step,'轮尝试,等待60秒----------------------------')
    if step%2==0:
        for i in MyDoubleMa.SentOrders:#在订单id列表中遍历
            MyDoubleMa.MyMid.GetOrder(i)#获得目前的订单id的订单状态
            if MyDoubleMa.MyMid.OrderStatus=='closed':#如果订单完成
                MyDoubleMa.SentOrders.remove(i)#移除在订单id列表的信息
            if MyDoubleMa.MyMid.OrderStatus=='open':#如果订单还没交易
                MyDoubleMa.MyMid.CancelOrder(i)#取消订单
                print('排除了一个未成交的挂单')
            if MyDoubleMa.MyMid.OrderStatus=='canceled':#如果撤单
                MyDoubleMa.SentOrders.remove(i)#移除在订单id列表的信息,或者可以修改价格再放上去
    step=step+1
    

2.回测

import requests
import json
import ccxt
import time
import numpy as np
import talib

apiKey='填上你自己的'
secret='填上你自己的'

exchange=ccxt.huobipro({
   
    
#     #代理部分
#     'proxies':{
   
    
#     'http':'socks5h://127.0.0.1:7891',
#     'https':'socks5h://127.0.0.1:7891'
#     },
    #api登陆
    'apiKey':apiKey,
    'secret':secret    
    }) 
#币种
exchange.symbol='OXT/USDT'
Records=exchange.fetchOHLCV('OXT/USDT','1m')#获得1分钟k线数据


#数据清洗,获得收盘价
CloseArrar=np.zeros(len(Records))#初始化收盘价数组,一共1000根k线有1000个数据
t=0                            
for i in Records:          
    CloseArrar[t]=i[4]
    t+=1    


#账户初始化
StartBalance=226#初始资金
Balance=StartBalance#目前资产
RemainStocks=0#账户持仓
BuySellAmount=round(0.2*Balance/Records[-1][4],0)#每次交易的数量,仓位两成
fee=0.01*0.2#交易所买卖的手续费

#双均线策略回测
FastMaArrar=talib.SMA(CloseArrar,3)#快速均线数组
SlowMaArrar=talib.SMA(CloseArrar,10)#慢速均线数组  

m=10
sell=buy=0
for m in range(len(Records)):
        LastPrice=Records[m][4]
        CrossOver = FastMaArrar[m]> SlowMaArrar[m] and FastMaArrar[m-1] < SlowMaArrar[m-1]#金叉
        CrossBelow = FastMaArrar[m] < SlowMaArrar[m]and FastMaArrar[m-1] > SlowMaArrar[m-1]#死叉
        if CrossOver: #如果金叉买入
              if Balance>=LastPrice*BuySellAmount:
                    Balance=Balance-LastPrice*BuySellAmount-LastPrice*BuySellAmount*fee
                    RemainStocks=RemainStocks+BuySellAmount
                    print('产生一个限价买单','资产当前资产',round(Balance+RemainStocks*Records[m][4],2),
                          '现金',round(Balance,2),'币',round(RemainStocks,2))  
                    buy+=1
              else:
                    print('余额不足!!!!!!!!!!')
        if CrossBelow:#如果死叉卖出
              if RemainStocks>=BuySellAmount:
                    Balance=Balance+LastPrice*BuySellAmount-LastPrice*BuySellAmount*fee
                    RemainStocks=RemainStocks-BuySellAmount
                    print('产生一个限价卖单','当前资产',round(Balance+RemainStocks*Records[m][4],2),
                          '现金',round(Balance,2),'币',round(RemainStocks,2))  
                    sell+=1
              else:
                    print('该币余额不足!!!!!!!!!!!!')
print('XXXXXXXXXXXXXXXX交易结束','当前资产',Balance+RemainStocks*Records[-1][4],'收益率为',
      round(((Balance+RemainStocks*Records[-1][4])/StartBalance-1)*100,2),'%','该币涨跌幅为',
      round(((Records[-1][4]/Records[0][4])-1)*100,2),'%'
     )
print('买单次数',buy,'卖单次数',sell)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!