Pythonで Dual Thrust デジタル通貨の量化取引戦略を実装する

作者: リン・ハーン優しさ, 作成日: 2019-08-13 14:52:58, 更新日: 2023-10-19 21:10:01

img

デュアルスルースト取引アルゴリズムの紹介

双推力取引アルゴリズムは,マイケル・チャレックによって開発された有名な量化取引戦略である.これは通常,フューチャー,外為,および株式市場で使用される.双推力の概念は,の双推力システムを用い,歴史的な価格に基づいて更新回帰期を構成する典型的な突破型取引システムに属し,理論上,任意の期間においてより安定している.

この記事では,この戦略の詳細な論理的詳細を提示し,発明者定量化プラットフォームでこのアルゴリズムを実装する方法を示します.まず,取引対象の歴史的な価格を選択します.この範囲は,最近N日の閉店価格,最高価格,最低価格の計算に基づいており,市場が開店価格から一定の範囲を移動すると,開場を行います.

我々はこの戦略を2つの一般的な市場状態,すなわちトレンド市場と波動市場において単一の取引対でテストした. 結果は,この動力取引システムがトレンド市場でよりうまく機能し,波動性の高い市場ではいくつかの無効なセールシグナルを誘発することを示した. 区間市場では,我々はよりよい収益を得るためにパラメータを調整することができる. 個々の参照取引指標の比較として,我々はまた,国内商品先物市場をテストした. 結果は,この戦略が平均よりも良いパフォーマンスを示した.

DT戦略の原理

その論理的原型は,一般的な日中取引戦略である.開拓区間破裂戦略は,今日の開拓価格を足して,または昨日の幅の一定の百分比をマイナスにして,上下走線を決定する.価格が上方走行線を破ると,買入し,下方走行線を破ると,空きする.

戦略の原理

  • 閉店後,最大値 - 閉店価格,閉店価格 - 低価格の2つの値を計算します. そして,この2つの値のうち,大きい値を取って,その値を0.7で掛けます. これをK値と呼びましょう.

  • 次の日の開店後,開店価格を記録し,価格が<開店価格+トリガー値>を超えると即座に購入するか,価格が<開店価格-トリガー値>を下回ると空売りします.

  • この戦略には明らかなストップ損失はない.このシステムは逆転システムである.つまり,価格が<開場価格+トリガー値>を超えると空頭ポジションの注文がある場合,それは2つの支払いを送信する.同じ理由で,もし多頭ポジションの価格が<開場価格-トリガー値>より低い場合,それは2つの売却を送信する.

img

DT戦略の数学表現

範囲=最大値 (HH-LC,HC-LL)

複数の信号を計算する方法は,

カップ = オープン + K1 × レンジキャップ = オープン + K1 × レンジ

低周波信号の計算方法として

床 = オープン K2 × レンジフロア = オープン K2 × レンジ

K1とK2がパラメータである.K1がK2より大きいとき,多頭信号を誘発し,その逆である.示範として,K1 = K2 = 0.5を選択した.実際の取引では,我々は依然として歴史的データを使用してこれらのパラメータを最適化したり,市場の傾向に応じてパラメータを調整したりすることができます.

img

このシステムは逆転システムである.したがって,価格が上走を突破するときに空頭ポジションを持っている場合,多発する前に空頭ポジションを平らにする.価格が下走を突破するときに多発している場合,新しい空頭ポジションを開く前に多発する場合は多発する前に空頭ポジションを平らにする.

DTの戦略の改善:

範囲設定では,前N日間の4つの価格ポイント (高,開,低,閉) を導入し,特定の期間の範囲を比較的安定させ,日経トレンド追跡に使用することができます.

この戦略の多重と空っぽのトリガー条件は,不均衡の大きさを考慮し,多空間の取引の参照範囲は,異なる周期数を選択すべきであり,パラメータK1とK2によっても決定することができる.K1K2の場合,空頭シグナルが比較的容易にトリガーされる.

したがって,この戦略を使用する際には,一方では,歴史的なデータ復習の最適なパラメータを参照できます.一方では,自分のバックグラウンド判断または他の主要なサイクル技術指標に基づいて,K1とK2を段階的に調整することができます.

これは,信号を待って,市場に入り,利息を取って,市場から脱出する典型的な取引方法ですが,非常に効果的です.

発明者定量化プラットフォームでDT戦略を展開

オープンしたらFMZ.COM管理者やボットを配置する.

管理者やロボットの配備については,私の前の記事を参照してください.https://www.fmz.com/bbs-topic/4140

クラウドサーバーの展開管理者を購入したい人は,この記事を参照してください:https://www.fmz.com/bbs-topic/2848

次に,左側のメニューのポリシーパネルをクリックして,新しいポリシーをクリックします.

プログラミング言語としてPythonを選択することを覚えておいてください.

img

Pythonのコードをコード編集ページに書き込み,その下には,読者がゆっくりと理解し,感じ取れるように,非常に詳細な行ごとに解説されたコードがあります.

OKCoinのフューチャーでこの戦略をテストしました.

import time # 这里需要引入python自带的时间库,后边的程序会用到

class Error_noSupport(BaseException): # 我们定义一个名为ChartCfg的全局class,用来初始化策略图表设置。对象有很多关于图表功能的属性。图表库为:HighCharts
    def __init__(self): # log出提示信息
        Log("只支持OKCoin期货!#FF0000")

class Error_AtBeginHasPosition(BaseException):
    def __init__(self):
        Log("启动时有期货持仓! #FF0000")

ChartCfg = {
    '__isStock': True, # 该属性用于控制是否显示为单独控制数据序列(可以在图表上取消单独一个数据序列的显示),如果指定__isStock: false, 则显示为普通图表
    'title': { # title为图表的主要标题
        'text': 'Dual Thrust 上下轨图' # title的一个属性text为标题的文本,这里设置为'Dual Thrust 上下轨图'该文本就会显示在标题位置
    },
    'yAxis': { # 图表坐标Y轴的相关设置
        'plotLines': [{ # Y轴上的水平线(和Y轴垂直),该属性的值是一个数组,即多条水平线的设置
            'value': 0, # 水平线在Y轴上的坐标值
            'color': 'red', # 水平线的颜色
            'width': 2, # 水平线的线宽
            'label': {  # 水平线上的标签
                'text': '上轨', # 标签的文本
                'align': 'center' # 标签的显示位置,这里设置为居中(即 :'center')
            }, 
        }, {       # 第二条水平线([{...},{...}]数组中的第二个元素)
            'value': 0, # 水平线在Y轴上的坐标值
            'color': 'green', # 水平线的颜色
            'width': 2,  # 水平线的线宽
            'label': { # 标签
                'text': '下轨',
                'align': 'center'
            },
        }]
    },
    'series': [{ # 数据序列,即用来在图表上显示数据线、K线、标记等等内容的数据。也是一个数组第一个索引为0。
        'type': 'candlestick', # 索引为0数据序列的类型:'candlestick' 表示为K线图
        'name': '当前周期',  # 数据序列的名称
        'id': 'primary', # 数据序列的ID,用于下一个数据序列相关设置。
        'data': []  # 数据序列的数组,用于储存具体的K线数据
    }, {
        'type': 'flags',  # 数据序列,类型:'flags',在图表上显示标签,表示做多和做空。索引为1。
        'onSeries': 'primary',  # 这个属性表示标签显示在id为'primary'上。
        'data': []    # 保存标签数据的数组。
    }] 
}

STATE_IDLE = 0  # 状态常量,表示空闲
STATE_LONG = 1 # 状态常量,表示持多仓
STATE_SHORT = 2 # 状态常量,表示持空仓
State = STATE_IDLE # 表示当前程序状态 ,初始赋值为空闲

LastBarTime = 0  # K线最后一柱的时间戳(单位为毫秒,1000毫秒等于1秒,时间戳是1970年1月1日到现在时刻的毫秒数是一个很大的正整数)
UpTrack = 0   # 上轨值
BottomTrack = 0 # 下轨值
chart = None # 用于接受Chart这个API函数返回的图表控制对象。用该对象(chart)可以调用其成员函数向图表内写入数据。
InitAccount = None # 初始账户情况
LastAccount = None # 最新账户情况
Counter = { # 计数器,用于记录盈亏次数
    'w': 0, # 赢次数
    'l': 0  # 亏次数
}

def GetPosition(posType):  # 定义一个函数,用来存储账户持仓信息
    positions = exchange.GetPosition() # exchange.GetPosition()是发明者量化的官方API,关于它的用法,请参考我的官方API文档:https://www.fmz.com/api
    return [{'Price': position['Price'], 'Amount': position['Amount']} for position in positions if position['Type'] == posType] # 返回各种持仓信息

def CancelPendingOrders(): # 定义一个函数,专门用来撤单
    while True: # 循环检查
        orders = exchange.GetOrders() # 如果有持仓
        [exchange.CancelOrder(order['Id']) for order in orders if not Sleep(500)] # 撤单语句
        if len(orders) == 0: # 逻辑判断
            break 

def Trade(currentState,nextState): # 定义一个函数,用来判断下单逻辑
    global InitAccount,LastAccount,OpenPrice,ClosePrice # 定义全局作用域
    ticker = _C(exchange.GetTicker) # 关于_C的用法,请参考:https://www.fmz.com/api
    slidePrice = 1 # 定义滑点值
    pfn = exchange.Buy if nextState == STATE_LONG else exchange.Sell # 买卖判断逻辑
    if currentState != STATE_IDLE: # 循环开始
        Log(_C(exchange.GetPosition)) # 日志信息 
        exchange.SetDirection("closebuy" if currentState == STATE_LONG else "closesell") # 调整下单方向,特别是下过单后
        while True:
            ID = pfn( (ticker['Last'] - slidePrice) if currentState == STATE_LONG else (ticker['Last'] + slidePrice), AmountOP) # 限价单,ID = pfn(-1, AmountOP)为市价单,ID = pfn(AmountOP)为市价单
            Sleep(Interval) # 休息一阵,防止API访问频率过快,账户被封。
            Log(exchange.GetOrder(ID)) # Log信息
            ClosePrice = (exchange.GetOrder(ID))['AvgPrice'] # 设置收盘价
            CancelPendingOrders() # 调用撤单函数
            if len(GetPosition(PD_LONG if currentState == STATE_LONG else PD_SHORT)) == 0: # 撤单逻辑
                break 
        account = exchange.GetAccount() # 获取账户信息
        if account['Stocks'] > LastAccount['Stocks']: # 如果当前账户币值大于之前账户币值
            Counter['w'] += 1 # 盈亏计数器中,盈利次数加一
        else:
            Counter['l'] += 1 # 否者亏损次数加一
        Log(account) # log信息
        LogProfit((account['Stocks'] - InitAccount['Stocks']),"收益率:", ((account['Stocks'] - InitAccount['Stocks']) * 100 / InitAccount['Stocks']),'%')
        Cal(OpenPrice,ClosePrice)
        LastAccount = account 
    
    exchange.SetDirection("buy" if nextState == STATE_LONG else "sell") # 这一段的逻辑同上,不再详述
    Log(_C(exchange.GetAccount))
    while True:
        ID = pfn( (ticker['Last'] + slidePrice) if nextState == STATE_LONG else (ticker['Last'] - slidePrice), AmountOP) 
        Sleep(Interval)
        Log(exchange.GetOrder(ID)) 
        CancelPendingOrders()
        pos = GetPosition(PD_LONG if nextState == STATE_LONG else PD_SHORT)
        if len(pos) != 0:
            Log("持仓均价",pos[0]['Price'],"数量:",pos[0]['Amount'])
            OpenPrice = (exchange.GetOrder(ID))['AvgPrice']
            Log("now account:",exchange.GetAccount())
            break 

def onTick(exchange): # 程序主要函数,程序主要逻辑都是在该函数内处理。
    global LastBarTime,chart,State,UpTrack,DownTrack,LastAccount # 定义全局作用域
    records = exchange.GetRecords() # 关于exchange.GetRecords()的用法,请参见:https://www.fmz.com/api
    if not records or len(records) <= NPeriod: # 防止发生意外的判断语句
        return 
    Bar = records[-1] # 取records K线数据的倒数第一个元素,也就是最后一个bar
    if LastBarTime != Bar['Time']:
        HH = TA.Highest(records, NPeriod, 'High')  # 声明HH变量,调用TA.Highest函数计算当前K线数据NPeriod周期内最高价的最大值赋值给HH。
        HC = TA.Highest(records, NPeriod, 'Close') # 声明HC变量,获取NPeriod周期内的收盘价的最大值。
        LL = TA.Lowest(records, NPeriod, 'Low') # 声明LL变量,获取NPeriod周期内的最低价的最小值。
        LC = TA.Lowest(records, NPeriod, 'Close') # 声明LC变量,获取NPeriod周期内的收盘价的最小值。具体TA相关的应用,请参见官方API文档。
        
        Range = max(HH - LC, HC - LL)  # 计算出范围 
        UpTrack = _N(Bar['Open'] + (Ks * Range))  # 根据界面参数的上轨系数Ks最新K线柱的开盘价等,计算出上轨值。
        DownTrack = _N(Bar['Open'] - (Kx * Range)) # 计算下轨值
        if LastBarTime > 0: # 由于LastBarTime该变量初始化设置的值为0,所以第一次运行到此处LastBarTime > 0必定是false,不会执行if块内的代码,而是会执行else块内的代码
            PreBar = records[-2] # 声明一个变量含义是“前一个Bar”把当前K线的倒数第二Bar赋值给它。
            chart.add(0, [PreBar['Time'], PreBar['Open'], PreBar['High'], PreBar['Low'], PreBar['Close']], -1) # 调用chart图标控制类的add函数更新K线数据(用获取的K线数据的倒数第二Bar去更新图标的倒数第一个Bar,因为有新的K线Bar生成)
        else:  # chart.add函数的具体用法请参见API文档,和论坛里的文章。程序第一次运行到此必定执行else块内代码,主要作用是把第一次获取的K线一次性全部添加到图表上。
            for i in range(len(records) - min(len(records), NPeriod * 3), len(records)): # 此处执行一个for循环,循环次数使用K线长度和NPeriod的3倍二者中最小的值,可以保证初始的K线不会画的太多太长。索引是从大到小的。
                b = records[i] # 声明一个临时变量b用来取每次循环索引为records.length - i的K线柱数据。
                chart.add(0,[b['Time'], b['Open'], b['High'], b['Low'], b['Close']]) # 调用chart.add函数向图表添加K线柱,注意add函数最后一个参数如果传入-1就是更新图表上最后一个Bar(柱),如果没传参数,就是向最后添加Bar。执行完i等于2这次循环后(i-- 了已经,此时为1了),就会触发i > 1为false停止循环,可见此处代码只处理到records.length - 2这个Bar,最后一个Bar没有处理。                
        chart.add(0,[Bar['Time'], Bar['Open'], Bar['High'], Bar['Low'], Bar['Close']]) # 由于以上if的2个分支都没处理records.length - 1这个Bar,所以此处处理。添加最新出现的Bar到图表中。
        ChartCfg['yAxis']['plotLines'][0]['value'] = UpTrack  # 把计算出来的上轨值赋值给图表对象(区别于图表控制对象chart),用于稍后显示。
        ChartCfg['yAxis']['plotLines'][1]['value'] = DownTrack # 赋值下轨值
        ChartCfg['subtitle'] = { # 设置副标题
            'text': '上轨' + str(UpTrack) + '下轨' + str(DownTrack) # 副标题文本设置,在副标题上显示出上轨下轨值。
        }
        chart.update(ChartCfg) # 用图表类ChartCfg更新图表
        chart.reset(PeriodShow) # 刷新根据界面参数设置的PeriodShow变量,只保留PeriodShow的值数量的K线柱。
        
        LastBarTime = Bar['Time'] # 此次新产生的Bar的时间戳更新,给LastBarTime用于判断下次循环获取的K线数据最后一个Bar,是否是新产生的。
    else: # 如果LastBarTime等于Bar.Time即:没有新的K线Bar产生。则执行一下{..}内代码
        chart.add(0,[Bar['Time'], Bar['Open'], Bar['High'], Bar['Low'], Bar['Close']], -1) # 用当前K线数据的最后一个Bar(K线的最后一个Bar即当前周期的Bar是不断在变化的),更新图表上的最后一个K线柱。        
    LogStatus("Price:", Bar["Close"], "up:", UpTrack, "down:", DownTrack, "wins:", Counter['w'], "losses:", Counter['l'], "Date:", time.time()) # 调用LogStatus函数显示当前策略的数据在状态栏上。
    msg = "" # 定义一个变量msg。
    if State == STATE_IDLE or State == STATE_SHORT: # 判断当前状态变量State是否等于空闲或者State是否等于持空仓,在空闲状态下可以触发做多,在持空仓状态下可以触发平多仓,并反手。
        if Bar['Close'] >= UpTrack: # 如果当前K线的收盘价大于上轨值,执行if块内代码。
            msg = "做多,触发价:" + str(Bar['Close']) + "上轨" + str(UpTrack) # 给msg赋值,把需要显示的数值组合成字符串。
            Log(msg) # 信息
            Trade(State, STATE_LONG) # 调用上边的Trade函数进行交易
            State = STATE_LONG # 无论开多仓还是反手,此刻程序状态要更新为持多仓。
            chart.add(1,{'x': Bar['Time'], 'color': 'red', 'shape': 'flag', 'title': '多', 'text': msg}) # 在K线相应的位置添加一个标记显示开多。 
    
    if State == STATE_IDLE or State == STATE_LONG: # 做空方向与以上同理,不在赘述。代码完全一致。
        if Bar['Close'] <= DownTrack:
            msg = "做空,触发价:" + str(Bar['Close']) + "下轨" + str(DownTrack)
            Log(msg)
            Trade(State, STATE_SHORT)
            State = STATE_SHORT
            chart.add(1,{'x': Bar['Time'], 'color': 'green', 'shape': 'circlepin', 'title': '空', 'text': msg})

OpenPrice = 0 # 初始化OpenPrice和ClosePrice
ClosePrice = 0
def Cal(OpenPrice, ClosePrice): # 定义一个Cal函数,用来计算策略运行后的盈亏情况
    global AmountOP,State
    if State == STATE_SHORT:
        Log(AmountOP,OpenPrice,ClosePrice,"策略盈亏:", (AmountOP * 100) / ClosePrice - (AmountOP * 100) / OpenPrice, "个币,  手续费:", - (100 * AmountOP * 0.0003), "美元,折合:", _N( - 100 * AmountOP * 0.0003/OpenPrice,8), "个币")
        Log(((AmountOP * 100) / ClosePrice - (AmountOP * 100) / OpenPrice) + (- 100 * AmountOP * 0.0003/OpenPrice))
    if State == STATE_LONG:
        Log(AmountOP,OpenPrice,ClosePrice,"策略盈亏:", (AmountOP * 100) / OpenPrice - (AmountOP * 100) / ClosePrice, "个币,  手续费:", - (100 * AmountOP * 0.0003), "美元,折合:", _N( - 100 * AmountOP * 0.0003/OpenPrice,8), "个币")
        Log(((AmountOP * 100) / OpenPrice - (AmountOP * 100) / ClosePrice) + (- 100 * AmountOP * 0.0003/OpenPrice))

def main(): # 策略程序的主函数。(入口函数)
    global LoopInterval,chart,LastAccount,InitAccount # 定义全局作用域
    if exchange.GetName() != 'Futures_OKCoin':  # 判断添加的交易所对象的名称(通过exchange.GetName函数获取)如果不等于'Futures_OKCoin'即:添加的不是OKCoin期货交易所对象。
        raise Error_noSupport # 抛出异常
    exchange.SetRate(1) # 设置交易所的各种参数
    exchange.SetContractType(["this_week","next_week","quarter"][ContractTypeIdx])  # 确定要交易的哪种具体合约。
    exchange.SetMarginLevel([10,20][MarginLevelIdx]) # 设置保证金率,也就是杠杆。
    
    if len(exchange.GetPosition()) > 0: # 设置容错机制
        raise Error_AtBeginHasPosition
    CancelPendingOrders()
    InitAccount = LastAccount = exchange.GetAccount()
    LoopInterval = min(1,LoopInterval)
    Log("交易平台:",exchange.GetName(), InitAccount)
    LogStatus("Ready...")
    
    LogProfitReset()
    chart = Chart(ChartCfg)
    chart.reset()
    
    LoopInterval = max(LoopInterval, 1)
    while True: # 循环整个交易逻辑,调用onTick函数
        onTick(exchange)
        Sleep(LoopInterval * 1000) # 休息一阵,防止API访问频率过快,账户被封。

コードが書き終わると,まだ全策の書き込み部分が終わっていないことに注意してください. 次に,策のパラメータを策の書き込みページに追加する必要があります. 追加方法は非常に簡単です. 直接策の書き込みのダイアログボックスの下の追加番号をクリックしてください.

img

この記事へのトラックバック一覧です.

img

戦略の書き込みは完了し,次に戦略を復習しよう.

戦略の復習

戦略を書いた後,最初にやるのは,それを復習して,歴史的データでどのように表現するかを見る.しかし,読者の注意を深めたい,復習の結果は,将来の予測に等しくありません.復習は,我々の戦略の有効性を考慮するための参考情報としてのみ使用できます.市場が変化し,戦略が大きな損失が出始めると,我々は問題を見つけ,新しい市場環境に適応するために戦略を変更する必要があります.

ポリシー編集ページの模擬復習をクリックすると,復習ページでは,パラメータの調整が必要に応じて異なるので,便利で迅速な復習を行うことができます.特に論理的に複雑で,パラメータが多いポリシーでは,ソースコードに戻らず,個別に変更します.

返信時間 過去半年を選択し,OKCoin先物取引所を追加するボタンをクリックし,BTC取引指標を選択します.

img

この6ヶ月間,BTCの一方的なトレンドが非常に良いので,戦略は良い利益を得ていることがわかります.

img img

問題を抱えている友人は来てくださいhttps://www.fmz.com/bbs戦略やプラットフォームの技術に関しても,発明者の定量化プラットフォームには専門家がいつでも対応しています.


関連性

もっと

メイドーブこの戦略は,停止損失と停止利益をもたらすべきではないのか?