
PythonとMQL5による多銘柄分析(第2回):ポートフォリオ最適化のための主成分分析
ポートフォリオが直面する総リスクを管理することは、私たちの取引コミュニティの全てのメンバーが直面する非常に難しい課題です。現代の投資家に提供される膨大な投資機会を考慮すると、市場がますます拡大し続ける今日の世界で、どのようにして包括的に分析し、適切な資産配分を決定できるのでしょうか。前回のディスカッションでは、SciPyを使用してポートフォリオ全体のリターンを最大化する方法を紹介しました。今回は、手元のポートフォリオにおけるリスクや分散を管理する方法に焦点を当てます。ポートフォリオのリスクを管理するためには、さまざまなモデルを使用できますが、統計学で広く使われている主成分分析(PCA)を用いることで、ポートフォリオの総分散を効果的に管理することができます。
エキスパートアドバイザー(EA)の販売を検討しているコミュニティのメンバーに向けて、この記事ではエンドユーザーにシームレスな体験を提供する方法を説明します。私たちの取引アプリケーションは、柔軟性と堅牢性を兼ね備えています。顧客が高リスク、中リスク、低リスクの取引モードを簡単に切り替えられるアプリケーションを作成する方法を紹介します。PCAアルゴリズムは、エンドユーザーの面倒な処理をバックグラウンドで担い、スムーズな取引体験を提供します。
方法論の概要
この記事では、10種類の暗号通貨を管理するポートフォリオを取り扱います。暗号通貨は、ブロックチェーンと呼ばれる特殊なタイプのネットワーク上に存在するデジタル資産です。ブロックチェーン技術は、ネットワーク内のメンバーが不正な取引を行うことをほぼ不可能にするセキュリティプロトコルです。したがって、基盤となるネットワークの強力さを考慮すると、ブロックチェーン技術の最初の一般的な用途の一つがデジタル通貨であったことが理解できます。しかし、これらのデジタル資産は非常に変動が激しいことで知られており、暗号資産への投資において投資家がリスクレベルを適切に管理することは困難な場合があります。幸いなことに、PCA(主成分分析)などの統計的手法を使用することで、暗号通貨への投資時に希望するリスクの変動範囲を管理することが可能です。主成分分析(PCA)は、因子分析と呼ばれる多変量統計学の一技法です。PCAは、画像解析から機械学習に至るまで、さまざまな分野で使用されています。画像解析では、PCAはデータ圧縮などのタスクでよく利用され、機械学習では主に次元削減の目的で使用されます。
PCAの考え方は、データセット内の各列に対して1つの係数を見つけることです。この係数を各列に掛けると、新たに得られる列がデータセットの分散を最大化することになります。この手順を正しく完了すると、最初の主成分が得られたことになります。
第2主成分は第1主成分と直交しており、これは完全に無相関であることを意味します。同時に、データセットの全体的な分散を最大化します。このプロセスは、元のデータセットのすべての分散を説明するのに十分な数の主成分が得られるまで続きます。
各主成分がどのように個別のリスクレベルに対応し、それを取引アプリケーションの設定として活用できるかを示します。数分でインテリジェントにリスク設定を管理する方法がわかります。
はじめに
アルゴリズムの動作を視覚的にデモンストレーションすることは、初めてアルゴリズムに触れる読者にとって非常に有益だと考えています。そこで、図1のMQL5ロゴの画像を使用し、まず白黒に変換します。この白黒フィルタを適用することで、PCAアルゴリズムがデータに対してどのように機能するのかを、より分かりやすく視覚的に確認できるようになります。
図1:上記のMQL5ロゴを使用してPCAアルゴリズムの動作を説明します。
作業する画像が用意できたので、Pythonライブラリを読み込みます。
#Let's go import pandas as pd import numpy as np import MetaTrader5 as mt5 import seaborn as sns import matplotlib.pyplot as plt from sklearn.decomposition import PCA from skimage import io, color
画像を読み込み、白黒に変換します。
# Load and preprocess the image image = io.imread('mql5.png') # Replace with your image path image_gray = image_gray = color.rgb2gray(image) # Convert to grayscale
画像を2次元に平坦化し、5つの異なる成分レベルを対象としてPCAを適用します。元の画像は図2の左端にあります。最初の主成分は入力データの分散を最大化します。「1成分」とラベル付けされた画像を見ると、元の画像の全体的な構造はある程度保持されていることがわかります。中央の「MQL5」という文字は判読できなくなっていますが、黒い背景の中央に白い構造があることは推測できます。
5つの主成分を使用すると、画像内のテキストは判読可能になります。しかし、背景に配置された薄い灰色のアイコンのような細かいディテールは失われ、復元するにはより多くの主成分が必要になります。
この演習を通じて、PCAアルゴリズムが元の入力データをコンパクトかつ相関のない形で表現しようとしていることを直感的に理解できるはずです。PCAは、入力データの分散を最大化するように連続した線形結合を作成することで、このタスクを達成します。
# Flatten the image h, w = image_gray.shape image_flattened = image_gray.reshape(h, w) # Apply PCA with different numbers of components n_components_list = [1,5,20,50,100] # Number of components to keep fig, axes = plt.subplots(1, len(n_components_list) + 1, figsize=(20, 10)) axes[0].imshow(image_gray, cmap='gray') axes[0].set_title("Original Image") axes[0].axis('off') for i, n_components in enumerate(n_components_list): # Initialize PCA and transform the flattened image pca = PCA(n_components=n_components) pca.fit(image_flattened) # Transform and inverse transform the image transformed_image = pca.transform(image_flattened) reconstructed_image = pca.inverse_transform(transformed_image).reshape(h, w) # Plot the reconstructed image axes[i + 1].imshow(reconstructed_image, cmap='gray') axes[i + 1].set_title(f"{n_components} Components") axes[i + 1].axis('off') plt.tight_layout() plt.show()
図2:MQL5ロゴを要約した最初の2つの主成分
図3:最後の3つの主成分は、MQL5ロゴを要約したもの
私たちはイメージよりも、ポートフォリオの分散を最大化または最小化することに興味があります。これを実現するには、ポートフォリオ内の各資産の収益を含むデータセットをPCAアルゴリズムに渡します。
市場データの取得
始めるには、まずMetaTrader 5ターミナルが起動していることを確認する必要があります。
mt5.initialize()
次に、ポートフォリオに保有するすべての資産をリストします。
#List of cryptocurrencies we wish to invest crypto = [ "BCHUSD", #BitcoinCash "EOSUSD", #EOS "BTCUSD", #Bitcoin "ETHUSD", #Etherum "ADAUSD", #Cardano "XRPUSD", #Ripple "UNIUSD", #Monero "DOGUSD", #Dogecoin "LTCUSD", #Litecoin "SOLUSD" #Solana ]
6年間の日次市場データを取得したいと考えています。
fetch = 365 * 6
私たちのモデルは今後30日間を予測します。
look_ahead = 30
暗号通貨のリターンを保存するデータフレームを作成します。
data = pd.DataFrame(columns=crypto,index=range(fetch))
リストにある各銘柄の市場価格を取得します。
for i in range(0,len(crypto)): data[crypto[i]] = pd.DataFrame(mt5.copy_rates_from_pos(crypto[i],mt5.TIMEFRAME_M1,0,fetch)).loc[:,"close"]
市場データを見てみましょう。
data
図4:収集した市場データの一部を視覚化し、暗号通貨のコレクションの6年間の過去の価格を記録
探索的データ分析
暗号通貨の取引は、その価格変動が非常に大きいため、難しい場合があります。図5では、2018年から2024年までの6年間にわたる10種類の暗号通貨のパフォーマンスを視覚的に確認できます。資産間のスプレッドは時間とともに変化し、人間が直感的に計算したり効果的に活用したりするのは難しいかもしれません。
data_plotting = data.iloc[:,:]/data.iloc[0,:]
sns.lineplot(data_plotting)
図5:投資できたかもしれない10種類の暗号通貨のパフォーマンス
各暗号通貨によってポートフォリオにもたらされるリスクは、各銘柄のリターンのローリング標準偏差として視覚化できます。図6から、ボラティリティの高い期間が集中していることがわかります。ただし、一部の市場は他の市場よりも変動が激しい動きを示します。
plt.plot(data_plotting.iloc[:,0].rolling(14).std()) plt.plot(data_plotting.iloc[:,1].rolling(14).std()) plt.plot(data_plotting.iloc[:,2].rolling(14).std()) plt.plot(data_plotting.iloc[:,3].rolling(14).std()) plt.plot(data_plotting.iloc[:,4].rolling(14).std()) plt.plot(data_plotting.iloc[:,5].rolling(14).std()) plt.plot(data_plotting.iloc[:,6].rolling(14).std()) plt.plot(data_plotting.iloc[:,7].rolling(14).std()) plt.plot(data_plotting.iloc[:,8].rolling(14).std()) plt.plot(data_plotting.iloc[:,9].rolling(14).std()) plt.legend(crypto) plt.title("The Risk Associated With Each of our Cryptocurrencies") plt.xlabel("Time") plt.ylabel("Standard Deviation")
図6:私たちのポートフォリオ内の各暗号通貨の標準偏差
PCAアルゴリズムは、図6に示されたポートフォリオの総分散へのエクスポージャーを意図的に最小化または最大化するのに役立ちます。10種類の資産の中から、どの資産がポートフォリオの分散を最大化するのかを手作業で判断するのは、人間にとってほぼ不可能でしょう。同じ資産クラスの銘柄を分析する際、強い相関関係が見られるのはある程度予想されます。特に興味深いのは、以下の通貨ペア間の相関レベルです。
- EOSUSDとXRPUSD
- DOGUSDとBCHUSD
#Correlation analysis
sns.heatmap(data.corr(),annot=True)
図7:10種類の暗号通貨のポートフォリオにおける相関レベルを視覚化する
2週間にわたる価格レベルの変化を計算します。
#Calculate the change over 2 weeks data = data.diff(14).dropna().reset_index(drop=True)
次にスケーラーを学習させます。
scaler = RobustScaler() scaled_data = pd.DataFrame(scaler.fit_transform(data), columns=data.columns)
scikit learnのPCAオブジェクトを市場の日次リターンに適合させます。
pca = PCA() pca.fit(scaled_data)
それでは、最初の主成分を分析してみましょう。係数の符号は、各市場でロングポジションを取るべきか、それともショートポジションを取るべきかを示します。正の係数はロングを推奨し、負の係数はショートを推奨します。したがって、ポートフォリオの分散を最大化するために、PCAアルゴリズムは、選択したすべての市場でショートを提案しています。
スキャルピング戦略を構築しようとしているトレーダーにとって、この最初の主成分は非常に有用でしょう。
#High risk strategy pca.components_[0]
さらに、各成分は、それに応じて分散を最大化または最小化するための最適な資本配分比率も提供します。ただし、この情報を得るには、成分にいくつかの変換を適用する必要があります。
まず、成分をL1ノルムで割ります。L1ノルムとは、成分内の要素の絶対値の総和です。各主成分のローディングスコアをその合計で割ることで、ポートフォリオの分散を最大化するために各資産へ割り当てるべき最適な割合を求めることができます。
次に、成分内の各暗号通貨の割合を計算した後、資産配分比率に建てたいポジション数を掛けることで、各市場で取るべきポジション数を概算できます。また、資産配分比率を100倍すると、ポジションの総和が100になることを確認できます。
#High risk asset allocations can be estimated from the first principal component high_risk_asset_allocation = pca.components_[0] / np.linalg.norm(pca.components_[0],1) np.sum(high_risk_asset_allocation * 100)
たとえば、ハイリスク戦略に従って10ポジションを開きたい場合、主成分はBCHUSD(ビットコインキャッシュ)で1ポジションを売ることを提案します。係数の小数部分は、やや小さいロットサイズのポジションとして解釈できます。しかし、これをポートフォリオで正確に考慮するには、かなりの時間がかかる可能性があります。したがって、割り当ての整数部分に依存する方が簡単かもしれません。
high_risk_asset_allocation * 10
それでは、中リスクの主成分に移りましょう。
#Mid risk strategy pca.components_[len(crypto)//2]
そして最後に、低リスクの主成分は、全体としてまったく異なる取引戦略を示唆しています。
#Low risk strategy pca.components_[len(crypto)-1]
これらの主成分をテキストファイルに保存して、MetaTrader 5取引アプリケーションで結果を使用できるようにしましょう。
np.savetxt("LOW RISK COMPONENTS.txt",pca.components_[len(crypto)-1]) np.savetxt("MID RISK COMPONENTS.txt",pca.components_[len(crypto)//2]) np.savetxt("HIGH RISK COMPONENTS.txt",pca.components_[0])
MQL5での実装
MetaTrader 5取引アプリケーションの実装を開始する準備が整いました。
私たちの取引アプリケーションは、最適な資産配分の結果に従うことを目的としています。ユーザーが選択した現在のリスク設定に基づき、各市場で1つの取引のみを開きます。取引アプリケーションは、移動平均とストキャスティクスオシレーターを使ってエントリーのタイミングを決定します。
もし主成分が市場でロングポジションを取るべきだと示唆している場合、その市場で価格が移動平均を上回り、ストキャスティクスオシレーターが50を超えるまで待機します。両方の条件が満たされた時点でロングポジションを開き、その後ATRを使って利益確定と損切りレベルを設定します。このようにエントリータイミングを決定することで、時間の経過とともにより安定した一貫性のあるシグナルが得られることを期待しています。
まず最初に、エンドユーザーが取引アプリケーションのリスクパラメータを制御できるよう、カスタム列挙型を定義します。
//+------------------------------------------------------------------+ //| PCA For Portfolio Optimization.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/ja/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/ja/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Custom enumerations | //+------------------------------------------------------------------+ enum risk_level { High=0, Mid=1, Low=2 };
必要なライブラリをロードします。
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
いつでも変更されない定数値を定義します。
//+------------------------------------------------------------------+ //| Constants values | //+------------------------------------------------------------------+ const double high_risk_components[] = {#include "HIGH RISK COMPONENTS.txt"}; const double mid_risk_components[] = {#include "MID RISK COMPONENTS.txt"}; const double low_risk_components[] = {#include "LOW RISK COMPONENTS.txt"}; const string crypto[] = {"BCHUSD","EOSUSD","BTCUSD","ETHUSD","ADAUSD","XRPUSD","UNIUSD","DOGUSD","LTCUSD","SOLUSD"};
以下は、プログラム全体で使用するグローバル変数です。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ double current_risk_settings[]; double vol,bid,ask; int atr_handler; int stoch_handler; int ma_handler; double atr_reading[],ma_reading[],stoch_reading[];
エンドユーザーは、取引口座のリスク設定と希望ロットサイズを動的に制御できるようになります。
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "Risk Levels" input risk_level user_risk = High; //Which risk level should we use? input group "Money Management" input int lot_multiple = 1; //How big should out lot size be?
取引アプリケーションが読み込まれると、まずユーザーが選択したリスク設定に従って主成分が読み込まれます。これらすべては、setup関数によって処理されます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our market data setup(); //--- return(INIT_SUCCEEDED); }
EAを使用しなくなった場合は、使用していないリソースを解放します。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicators IndicatorRelease(atr_handler); IndicatorRelease(ma_handler); IndicatorRelease(stoch_handler); }
最後に、更新された価格を受け取るたびに、各暗号通貨市場で1つのポジションのポートフォリオを維持します。占めるポジションは、ユーザーが選択したリスク設定によって決まります。リスク設定に応じて、ポートフォリオの変動を制御するために「買い」または「売り」のポジションを取る必要がある場合があります。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Ensure we always have 10 positions if(PositionsTotal() < 10) { //--- Let's see which market we aren't in for(int i = 0; i < 10; i++) { //--- Check if we can now enter that market if(!PositionSelect(crypto[i])) { check_setup(i); } } } }
setup関数は、エンドユーザーが選択したリスク設定を判別し、対応する主成分を読み込みます。
//+------------------------------------------------------------------+ //| Setup our market data | //+------------------------------------------------------------------+ void setup(void) { //--- First let us define the current risk settings switch(user_risk) { //--- The user selected high risk case 0: { ArrayCopy(current_risk_settings,high_risk_components,0,0,WHOLE_ARRAY); Comment("EA in high risk mode"); break; } //--- The user selected mid risk case 1: { ArrayCopy(current_risk_settings,mid_risk_components,0,0,WHOLE_ARRAY); Comment("EA in mid risk mode"); break; } //--- The user selected low risk //--- Low risk is also the default setting for safety! default: { ArrayCopy(current_risk_settings,low_risk_components,0,0,WHOLE_ARRAY); Comment("EA in low risk mode"); break; } } }
10種類の異なる銘柄を積極的に取引しているため、各銘柄に関連する市場データを取得する関数が必要です。以下で定義されるupdate関数がこのタスクを処理します。この関数が呼び出されるたびに、現在のBid価格とAsk価格、および指定された市場から計算されたその他のテクニカル指標の読み取り値がメモリにロードされます。データを使用して実行するアクションを決定すると、関数は次の銘柄の市場データをメモリに読み込みます。
//+------------------------------------------------------------------+ //| Update our system varaibles | //+------------------------------------------------------------------+ void update(string market) { //--- Get current prices bid = SymbolInfoDouble(market,SYMBOL_BID); ask = SymbolInfoDouble(market,SYMBOL_ASK); //--- Get current technical readings atr_handler = iATR(market,PERIOD_CURRENT,14); stoch_handler = iStochastic(market,PERIOD_CURRENT,5,3,3,MODE_EMA,STO_CLOSECLOSE); ma_handler = iMA(market,PERIOD_CURRENT,30,0,MODE_EMA,PRICE_CLOSE); //--- Copy buffer CopyBuffer(atr_handler,0,0,1,atr_reading); CopyBuffer(ma_handler,0,0,1,ma_reading); CopyBuffer(stoch_handler,0,0,1,stoch_reading); //--- }
最後に、ユーザーが指定した現在のリスク設定に沿ったポジションを開くことができるかどうかを確認する関数が必要です。そうでない場合、その市場でポジションを開くことができない理由をエンドユーザーにわかりやすく説明します。
//+------------------------------------------------------------------+ //| Open a position if we can | //+------------------------------------------------------------------+ void check_setup(int idx) { //--- The function takes the index of the symbol as its only parameter //--- It will look up the principal component loading of the symbol to determine whether it should buy or sell update(crypto[idx]); vol = lot_multiple * SymbolInfoDouble(crypto[idx],SYMBOL_VOLUME_MIN); if(current_risk_settings[idx] > 0) { if((iClose(crypto[idx],PERIOD_D1,0) > ma_reading[0]) && (stoch_reading[0] > 50)) { Comment("Analyzing: ",crypto[idx],"\nMA: ",ma_reading[0],"\nStoch: ",stoch_reading[0],"\nAction: Buy"); Trade.Buy(vol,crypto[idx],ask,(ask - (atr_reading[0] * 3)),(ask + (atr_reading[0] * 3)),"PCA Risk Optimization"); return; } else { Comment("Waiting for an oppurtunity to BUY: ",crypto[idx]); } } else if(current_risk_settings[idx] < 0) { if((iClose(crypto[idx],PERIOD_D1,0) < ma_reading[0]) && (stoch_reading[0] < 50)) { Comment("Analyzing: ",crypto[idx],"\nClose: ","\nMA: ",ma_reading[0],"\nStoch: ",stoch_reading[0],"\nAction: Sell"); Trade.Sell(vol,crypto[idx],bid,(bid + (atr_reading[0] * 3)),(bid - (atr_reading[0] * 3)),"PCA Risk Optimization"); return; } else { Comment("Waiting for an oppurtunity to SELL: ",crypto[idx]); return; } } Comment("Analyzing: ",crypto[idx],"\nMA: ",ma_reading[0],"\nStoch: ",stoch_reading[0],"\nAction: None"); return; }; //+------------------------------------------------------------------+
図8:MetaTrader 5でEAを表示する
図9:取引アプリケーションのリスクパラメータを調整する
図10:取引アプリケーションは実際の市場データでリアルタイムにテストされている
結論
PCAは、その概念を学ぶために数学的な表記法が必要なため、通常はやや複雑なトピックと見なされがちです。しかし、この記事では、PCAを使いやすい方法で活用する方法を紹介しました。これにより、たとえ初めて学ぶ方でも、今日からすぐに実践に取り入れることができることを願っています。
ポートフォリオ配分におけるリスク管理は決して簡単な作業ではありません。金融市場で自分が抱えるリスクを測定し、管理するためには、数学的なツールやモデルが最適な方法です。特に、参加する市場の数が多くなるとその重要性は増します。
銘柄が10個でも100個でも、PCAは常に、ポートフォリオのリスクを最大化する組み合わせと最小化する組み合わせを示してくれるでしょう。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16273
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索