Python で デジタル 通貨 の 量 的な 取引 戦略 を 実行 する

作者: リン・ハーンリディア, 作成日:2023-01-10 17:07:49, 更新日:2023-09-20 10:53:48

img

Python で デジタル 通貨 の 量 的な 取引 戦略 を 実行 する

デュアルスルースト取引アルゴリズムへの導入

デュアル・スルースト・トレーディングアルゴリズムは,マイケル・チャレックによって開発された有名な定量的なトレーディング戦略である.通常,先物,外為,株式市場で使用される.デュアル・スルーストの概念は典型的な突破型トレーディングシステムである.歴史価格に基づいて更新されたバックトラッキング期間を構築するために,デュアル・スルースト・システムを使用し,理論的には任意の期間でより安定している.

この記事では,この戦略の詳細な論理的な詳細を提供し,このアルゴリズムをFMZ Quantプラットフォームで実装する方法を示します.まず,取引対象の歴史的な価格を選択する必要があります.この範囲は,最後のN日の閉値,最高値,最低値に基づいて計算されます.市場は開盤価格から一定の範囲を移動すると,ポジションを開きます.

この戦略は,トレンド市場とショック市場という2つの一般的な市場条件下で,単一の取引ペアでテストしました.結果は,モメント・トレーディングシステムがトレンド市場でよりうまく機能し,不安定な市場でいくつかの無効な取引信号を誘発することを示しました.間隔市場では,より良いリターンを得るためにパラメータを調整することができます.個々の参照取引目標の比較として,国内商品先物市場もテストしました.結果は,戦略が平均パフォーマンスよりも優れていることを示しました.

DT戦略の原則

論理的なプロトタイプは,一般的な日中取引戦略である. オープニングレンジブレークスルー戦略は,今日のオープニング価格プラスまたはマイナス昨日の範囲の一定パーセントをベースに,上位と下位トラックを決定する. 価格が上位トラックをクロスすると,購入するポジションを開く. 下位トラックを突破すると,ショートするポジションを開く.

戦略の原則

  • 閉じる後,2つの値が計算されます.最も高い価格 - 閉じる価格,そして閉じる価格 - 最低価格です.それから,2つの値のうちより大きい値を取って,値を0.7で掛けます.それをK値と呼びましょう.これをトリガー値と呼びます.

  • 市場が翌日開通した後,開通価格を記録し,価格が値を超えるとすぐに購入 (開通価格 + トリガー値) または価格が値を下回るとショートポジションを売却します (開通価格 - トリガー値).

  • この戦略には明らかなストップ・ロスは存在しない.このシステムは逆システムである.つまり,価格が (オープニング価格+トリガー値) を超えたときにショートポジションオーダーがある場合,それは2つの購入オーダーを送信する (1つは間違ったポジションを閉鎖し,もう1つは正しいポジションを開く).同じ理由で,ロングポジションの価格が (オープニング価格 -トリガー値) より低い場合,それは2つの販売オーダーを送信する.

DT戦略の数学表現

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

長期位置信号の計算方法は次のとおりである.

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

ショートポジション信号の計算方法は:

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

K1 と K2 がパラメータである.K1 が K2 より大きいとき,ロングポジション信号が起動し,その逆です.実証のために,K1=K2=0.5 を選択します.実際の取引では,これらのパラメータを最適化したり,市場動向に応じてパラメータを調整するために,歴史的なデータをまだ使用できます.もしあなたが上昇傾向であれば,K1 は K2 より小さいはずです.もしあなたが下落傾向であれば,K1 は K2 より大きいはずです.

img

このシステムは逆転システムである.したがって,価格が上位トラックを横切ったときに投資家がショートポジションを保持する場合は,ロングポジションを開く前にショートポジションを閉鎖すべきである.価格が底線を突破したときに投資家がロングポジションを保持する場合は,新しいショートポジションを開く前にロングポジションを閉鎖すべきである.

DT戦略の改善:

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

戦略のロングとショートポジションを開設するトリガー条件は,不対称範囲を考慮し,ロングとショート取引の基準範囲は,パラメータK1とK2によって決定される異なる期間数を選択すべきである.K1 < K2の場合,ロングポジション信号は比較的容易に行動し,K1 > K2の場合,ショートポジション信号は比較的容易に行動する.

したがって,この戦略を使用するとき,一方,あなたは歴史的なデータバックテストの最良のパラメータを参照することができます.一方,あなたは将来の判断または他の主要な期間技術指標に応じて段階的にK1とK2を調整することができます.

信号を待って 市場に入って 引換して 市場を出て行くような 典型的な取引方法ですが 効果は素晴らしいです

FMZ Quant プラットフォームで DT 戦略を展開する

オープンするFMZ.COMドーカーとロボットを展開します. コンピューターは,ドーカーとロボットを

ドッカーとロボットの展開について 私の前の記事を参照してください:https://www.fmz.com/bbs-topic/9864.

ドーカーを展開するために独自のクラウドコンピューティングサーバを購入したい読者は,この記事を参照してください:https://www.fmz.com/digest-topic/5711.

戦略を追加するボタンをクリックします.

図のように,戦略編集ページの右上隅に Python のようにプログラミング言語を選択してください.

img

Python のコードをコード編集ページに書き込みます. 次のコードには非常に詳細な行ごとにコメントがあります.

OKCoinの先物を使って戦略をテストします.

import time # Here we need to introduce the time library that comes with python, which will be used later in the program.

class Error_noSupport(BaseException): # We define a global class named ChartCfg to initialize the strategy chart settings. Object has many attributes about the chart function. Chart library: HighCharts.
    def __init__(self): # log out prompt messages
        Log("Support OKCoin Futures only! #FF0000")

class Error_AtBeginHasPosition(BaseException):
    def __init__(self):
        Log("Start with a futures position! #FF0000")

ChartCfg = {
    '__isStock': True, # This attribute is used to control whether to display a single control data series (you can cancel the display of a single data series on the chart). If you specify __isStock: false, it will be displayed as a normal chart.
    'title': { # title is the main title of the chart
        'text': 'Dual Thrust upper and bottom track chart' # An attribute of the title text is the text of the title, here set to 'Dual Thrust upper and bottom track chart' the text will be displayed in the title position.
    },
    'yAxis': { # Settings related to the Y-axis of the chart coordinate.
        'plotLines': [{ # Horizontal lines on the Y-axis (perpendicular to the Y-axis), the value of this attribute is an array, i.e. the setting of multiple horizontal lines.
            'value': 0, # Coordinate value of horizontal line on Y-axis
            'color': 'red', # Color of horizontal line
            'width': 2, # Line width of horizontal line
            'label': {  # Labels on the horizontal line
                'text': 'Upper track', # Text of the label
                'align': 'center' # The display position of the label, here set to center (i.e.: 'center')
            }, 
        }, {       # The second horizontal line ([{...} , {...}] the second element in the array)
            'value': 0, # Coordinate value of horizontal line on Y-axis
            'color': 'green', # Color of horizontal line
            'width': 2,  # Line width of horizontal line
            'label': { # Label
                'text': 'bottom track',
                'align': 'center'
            },
        }]
    },
    'series': [{ # Data series, that is, data used to display data lines, K-lines, tags, and other contents on the chart. It is also an array whose first index is 0.
        'type': 'candlestick', # Type of data series with index 0: 'candlestick' indicates a K-line chart.
        'name': 'Current period',  # Name of the data series
        'id': 'primary', # The ID of the data series, which is used for the related settings of the next data series.
        'data': []  # An array of data series to store specific K-line data
    }, {
        'type': 'flags',  # Data series, type: 'flags', display labels on the chart, indicating going long and going short. Index is 1.
        'onSeries': 'primary',  # This attribute indicates that the label is displayed on id 'primary'.
        'data': []    # The array that holds the label data.
    }] 
}

STATE_IDLE = 0  # Status constants, indicating idle
STATE_LONG = 1 # Status constants, indicating long positions
STATE_SHORT = 2 # Status constants, indicating short positions
State = STATE_IDLE # Indicates the current program status, assigned as idle initially

LastBarTime = 0  # The time stamp of the last column of the K-line (in milliseconds, 1000 milliseconds is equal to 1 second, and the timestamp is the number of milliseconds from January 1, 1970 to the present time is a large positive integer).
UpTrack = 0   # Upper track value
BottomTrack = 0 # Bottom track value
chart = None # It is used to accept the chart control object returned by the Chart API function. Use this object (chart) to call its member function to write data to the chart.
InitAccount = None # Initial account status
LastAccount = None # Latest account status
Counter = { # Counters for recording profit and loss counts
    'w': 0, # Number of wins
    'l': 0  # Number of losses
}

def GetPosition(posType):  # Define a function to store account position information
    positions = exchange.GetPosition() # exchange.GetPosition() is the FMZ Quant official API. For its usage, please refer to the official API document: https://www.fmz.com/api.
    return [{'Price': position['Price'], 'Amount': position['Amount']} for position in positions if position['Type'] == posType] # Return to various position information

def CancelPendingOrders(): # Define a function specifically for withdrawing orders
    while True: # Loop check
        orders = exchange.GetOrders() # If there is a position
        [exchange.CancelOrder(order['Id']) for order in orders if not Sleep(500)] # Withdrawal statement
        if len(orders) == 0: # Logical judgment
            break 

def Trade(currentState,nextState): # Define a function to determine the order placement logic.
    global InitAccount,LastAccount,OpenPrice,ClosePrice # Define the global scope
    ticker = _C(exchange.GetTicker) # For the usage of _C, please refer to: https://www.fmz.com/api.
    slidePrice = 1 # Define the slippage value
    pfn = exchange.Buy if nextState == STATE_LONG else exchange.Sell # Buying and selling judgment logic
    if currentState != STATE_IDLE: # Loop start
        Log(_C(exchange.GetPosition)) # Log information 
        exchange.SetDirection("closebuy" if currentState == STATE_LONG else "closesell") # Adjust the order direction, especially after placing the order.
        while True:
            ID = pfn( (ticker['Last'] - slidePrice) if currentState == STATE_LONG else (ticker['Last'] + slidePrice), AmountOP) # Price limit order, ID=pfn (- 1, AmountOP) is the market price order, ID=pfn (AmountOP) is the market price order.
            Sleep(Interval) # Take a break to prevent the API from being accessed too often and your account being blocked.
            Log(exchange.GetOrder(ID)) # Log information
            ClosePrice = (exchange.GetOrder(ID))['AvgPrice'] # Set the closing price
            CancelPendingOrders() # Call the withdrawal function
            if len(GetPosition(PD_LONG if currentState == STATE_LONG else PD_SHORT)) == 0: # Order withdrawal logic
                break 
        account = exchange.GetAccount() # Get account information
        if account['Stocks'] > LastAccount['Stocks']: # If the current account currency value is greater than the previous account currency value.
            Counter['w'] += 1 # In the profit and loss counter, add one to the number of profits.
        else:
            Counter['l'] += 1 # Otherwise, add one to the number of losses.
        Log(account) # log information
        LogProfit((account['Stocks'] - InitAccount['Stocks']),"Return rates:", ((account['Stocks'] - InitAccount['Stocks']) * 100 / InitAccount['Stocks']),'%')
        Cal(OpenPrice,ClosePrice)
        LastAccount = account 
    
    exchange.SetDirection("buy" if nextState == STATE_LONG else "sell") # The logic of this part is the same as above and will not be elaborated.
    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("Average price of positions",pos[0]['Price'],"Amount:",pos[0]['Amount'])
            OpenPrice = (exchange.GetOrder(ID))['AvgPrice']
            Log("now account:",exchange.GetAccount())
            break 

def onTick(exchange): # The main function of the program, within which the main logic of the program is processed.
    global LastBarTime,chart,State,UpTrack,DownTrack,LastAccount # Define the global scope
    records = exchange.GetRecords() # For the usage of exchange.GetRecords(), please refer to: https://www.fmz.com/api.
    if not records or len(records) <= NPeriod: # Judgment statements to prevent accidents.
        return 
    Bar = records[-1] # Take the penultimate element of records K-line data, that is, the last bar.
    if LastBarTime != Bar['Time']:
        HH = TA.Highest(records, NPeriod, 'High')  # Declare the HH variable, call the TA.Highest function to calculate the maximum value of the highest price in the current K-line data NPeriod period and assign it to HH.
        HC = TA.Highest(records, NPeriod, 'Close') # Declare the HC variable to get the maximum value of the closing price in the NPeriod period.
        LL = TA.Lowest(records, NPeriod, 'Low') # Declare the LL variable to get the minimum value of the lowest price in the NPeriod period.
        LC = TA.Lowest(records, NPeriod, 'Close') # Declare LC variable to get the minimum value of the closing price in the NPeriod period. For specific TA-related applications, please refer to the official API documentation.
        
        Range = max(HH - LC, HC - LL)  # Calculate the range 
        UpTrack = _N(Bar['Open'] + (Ks * Range))  # The upper track value is calculated based on the upper track factor Ks of the interface parameters such as the opening price of the latest K-line bar.
        DownTrack = _N(Bar['Open'] - (Kx * Range)) # Calculate the down track value
        if LastBarTime > 0: # Because the value of LastBarTime initialization is set to 0, LastBarTime>0 must be false when running here for the first time. The code in the if block will not be executed, but the code in the else block will be executed.
            PreBar = records[-2] # Declare a variable means "the previous Bar" assigns the value of the penultimate Bar of the current K-line to it.
            chart.add(0, [PreBar['Time'], PreBar['Open'], PreBar['High'], PreBar['Low'], PreBar['Close']], -1) # Call the add function of the chart icon control class to update the K-line data (use the penultimate bar of the obtained K-line data to update the last bar of the icon, because a new K-line bar is generated).
        else:  # For the specific usage of the chart.add function, see the API documentation and the articles in the forum. When the program runs for the first time, it must execute the code in the else block. The main function is to add all the K-lines obtained for the first time to the chart at one time.
            for i in range(len(records) - min(len(records), NPeriod * 3), len(records)): # Here, a for loop is executed. The number of loops uses the minimum of the K-line length and 3 times the NPeriod, which can ensure that the initial K-line will not be drawn too much and too long. Indexes vary from large to small.
                b = records[i] # Declare a temporary variable b to retrieve the K-line bar data with the index of records.length - i for each loop.
                chart.add(0,[b['Time'], b['Open'], b['High'], b['Low'], b['Close']]) # Call the chart.add function to add a K-line bar to the chart. Note that if the last parameter of the add function is passed in -1, it will update the last Bar (column) on the chart. If no parameter is passed in, it will add Bar to the last. After executing the loop of i=2 (i-- already, now it's 1), it will trigger i > 1 for false to stop the loop. It can be seen that the code here only processes the bar of records.length - 2, and the last Bar is not processed.                
        chart.add(0,[Bar['Time'], Bar['Open'], Bar['High'], Bar['Low'], Bar['Close']]) # Since the two branches of the above if do not process the bar of records.length - 1, it is processed here. Add the latest Bar to the chart.
        ChartCfg['yAxis']['plotLines'][0]['value'] = UpTrack  # Assign the calculated upper track value to the chart object (different from the chart control object chart) for later display.
        ChartCfg['yAxis']['plotLines'][1]['value'] = DownTrack # Assign lower track value
        ChartCfg['subtitle'] = { # Set subtitle
            'text': 'upper tarck' + str(UpTrack) + 'down track' + str(DownTrack) # Subtitle text setting. The upper and down track values are displayed on the subtitle.
        }
        chart.update(ChartCfg) # Update charts with chart class ChartCfg.
        chart.reset(PeriodShow) # Refresh the PeriodShow variable set according to the interface parameters, and only keep the K-line bar of the number of PeriodShow values.
        
        LastBarTime = Bar['Time'] # The timestamp of the newly generated Bar is updated to LastBarTime to determine whether the last Bar of the K-line data acquired in the next loop is a newly generated one.
    else: # If LastBarTime is equal to Bar.Time, that is, no new K-line Bar is generated. Then execute the code in {..}.
        chart.add(0,[Bar['Time'], Bar['Open'], Bar['High'], Bar['Low'], Bar['Close']], -1) # Update the last K-line bar on the chart with the last Bar of the current K-line data (the last Bar of the K-line, i.e. the Bar of the current period, is constantly changing).        
    LogStatus("Price:", Bar["Close"], "up:", UpTrack, "down:", DownTrack, "wins:", Counter['w'], "losses:", Counter['l'], "Date:", time.time()) # The LogStatus function is called to display the data of the current strategy on the status bar.
    msg = "" # Define a variable msg.
    if State == STATE_IDLE or State == STATE_SHORT: # Judge whether the current state variable State is equal to idle or whether State is equal to short position. In the idle state, it can trigger long position, and in the short position state, it can trigger a long position to be closed and sell the opening position.
        if Bar['Close'] >= UpTrack: # If the closing price of the current K-line is greater than the upper track value, execute the code in the if block.
            msg = "Go long, trigger price:" + str(Bar['Close']) + "upper track" + str(UpTrack) # Assign a value to msg and combine the values to be displayed into a string.
            Log(msg) # message
            Trade(State, STATE_LONG) # Call the Trade function above to trade.
            State = STATE_LONG # Regardless of opening long positions or selling the opening position, the program status should be updated to hold long positions at the moment.
            chart.add(1,{'x': Bar['Time'], 'color': 'red', 'shape': 'flag', 'title': 'long', 'text': msg}) # Add a marker to the corresponding position of the K-line to show the open long position. 
    
    if State == STATE_IDLE or State == STATE_LONG: # The short direction is the same as the above, and will not be repeated. The code is exactly the same.
        if Bar['Close'] <= DownTrack:
            msg = "Go short, trigger price:" + str(Bar['Close']) + "down track" + str(DownTrack)
            Log(msg)
            Trade(State, STATE_SHORT)
            State = STATE_SHORT
            chart.add(1,{'x': Bar['Time'], 'color': 'green', 'shape': 'circlepin', 'title': 'short', 'text': msg})

OpenPrice = 0 # Initialize OpenPrice and ClosePrice
ClosePrice = 0
def Cal(OpenPrice, ClosePrice): # Define a Cal function to calculate the profit and loss of the strategy after it has been run.
    global AmountOP,State
    if State == STATE_SHORT:
        Log(AmountOP,OpenPrice,ClosePrice,"Profit and loss of the strategy:", (AmountOP * 100) / ClosePrice - (AmountOP * 100) / OpenPrice, "Currencies, service charge:", - (100 * AmountOP * 0.0003), "USD, equivalent to:", _N( - 100 * AmountOP * 0.0003/OpenPrice,8), "Currencies")
        Log(((AmountOP * 100) / ClosePrice - (AmountOP * 100) / OpenPrice) + (- 100 * AmountOP * 0.0003/OpenPrice))
    if State == STATE_LONG:
        Log(AmountOP,OpenPrice,ClosePrice,"Profit and loss of the strategy:", (AmountOP * 100) / OpenPrice - (AmountOP * 100) / ClosePrice, "Currencies, service charge:", - (100 * AmountOP * 0.0003), "USD, equivalent to:", _N( - 100 * AmountOP * 0.0003/OpenPrice,8), "Currencies")
        Log(((AmountOP * 100) / OpenPrice - (AmountOP * 100) / ClosePrice) + (- 100 * AmountOP * 0.0003/OpenPrice))

def main(): # The main function of the strategy program. (entry function)
    global LoopInterval,chart,LastAccount,InitAccount # Define the global scope
    if exchange.GetName() != 'Futures_OKCoin':  # Judge if the name of the added exchange object (obtained by the exchange.GetName function) is not equal to 'Futures_OKCoin', that is, the object added is not OKCoin futures exchange object.
        raise Error_noSupport # Throw an exception
    exchange.SetRate(1) # Set various parameters of the exchange.
    exchange.SetContractType(["this_week","next_week","quarter"][ContractTypeIdx])  # Determine which specific contract to trade.
    exchange.SetMarginLevel([10,20][MarginLevelIdx]) # Set the margin rate, also known as leverage.
    
    if len(exchange.GetPosition()) > 0: # Set up fault tolerance mechanism.
        raise Error_AtBeginHasPosition
    CancelPendingOrders()
    InitAccount = LastAccount = exchange.GetAccount()
    LoopInterval = min(1,LoopInterval)
    Log("Trading platforms:",exchange.GetName(), InitAccount)
    LogStatus("Ready...")
    
    LogProfitReset()
    chart = Chart(ChartCfg)
    chart.reset()
    
    LoopInterval = max(LoopInterval, 1)
    while True: # Loop the whole transaction logic and call the onTick function.
        onTick(exchange)
        Sleep(LoopInterval * 1000) # Take a break to prevent the API from being accessed too frequently and the account from being blocked.

コードを書いた後,戦略全体の書き込みが終わっていないことに注意してください.次に,戦略編集ページに戦略で使用されたパラメータを追加する必要があります.追加方法は非常に簡単です.戦略書き込みダイアログボックスの下にあるプラス記号をクリックして一つずつ追加してください.

img

追加する内容:

img

戦略の書き込みは完了しました 次に戦略のバックテストを始めましょう.

戦略のバックテスト

戦略を書いた後,まず最初にすべきことは,過去のデータでどのように振る舞うか確認するためにバックテストを行うことです.しかし,バックテストの結果が将来の予測に等しくないことに注意してください.バックテストは,当社の戦略の有効性を考慮するための参照としてのみ使用できます.市場が変化し,戦略が大きな損失を起こすと,私たちは問題を見つけ,上記の値などの新しい市場環境に適応するために戦略を変更する必要があります.戦略が10%以上の損失を持っている場合は,すぐに戦略の運用を停止し,その後問題を見つけることができます.私たちは値調整から始めることができます.

戦略編集ページでバックテストをクリックします. バックテストページでは,異なるニーズに応じてパラメータの調整を便利かつ迅速に実行できます. 特に複雑な論理と多くのパラメータを持つ戦略では,ソースコードに戻り,一つずつ修正する必要はありません.

バックテストの時間は 6ヶ月前です. OKCoin フューチャーズ取引所を追加する をクリックして BTC トレーディングターゲットを選択します.

img

この戦略は過去6ヶ月で 良い収益を得ています BTCの一方的なトレンドがとても良いからです

img img

メッセージを残してください.https://www.fmz.com/bbsプラットフォームの戦略や技術に関しても FMZ Quantプラットフォームには 専門家がいて 皆さんの質問に答えています


関連性

もっと