基於CCXT接口建立的多模塊數字貨幣量化交易模型(MMQT)在python中的實現


版權聲明:如需對此文章代碼進行轉載請註明出處,若用於商業用途、論文寫作請私信或聯繫作者郵箱[email protected]

一、前言

問題的引出

        量化交易是指以先進的數學模型替代人爲的主觀判斷,利用計算機技術從龐大的歷史數據中海選能帶來超額收益的多種“大概率”事件以制定策略,極大地減少了投資者情緒波動的影響,避免在市場極度狂熱或悲觀的情況下作出非理性的投資決策。
        數字貨幣是一種基於節點網絡和數字加密算法的虛擬貨幣價值的數字化表示。可以認爲不由央行或當局發行,也不與法幣掛鉤,但由於被公衆所接受,所以可作爲支付手段,也可以電子形式轉移、存儲或交易。
        隨着美國的無限量化寬鬆政策以及疫情的持續爆發,美元和黃金市場持續低迷,導致了大量的避險資金湧入幣圈,造成了今年從四月份以來主流幣兇猛的漲勢。同時,伴隨着國際上對於比特幣等主流幣的認同聲逐漸增加,許多國外的金融機構都對於數字貨幣市場躍躍欲試,其中具有代表性是灰度信託,近幾年它一直在不可逆性持續增倉,並且在二級市場發售數字貨幣的基金信託,這更是催生了更多的大資金買家入場,其中不乏中國的很多基金公司。
        由於數字貨幣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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章