English Русский Español Deutsch 日本語
preview
使用Python和MQL5进行多交易品种分析(第一部分):纳斯达克集成电路制造商

使用Python和MQL5进行多交易品种分析(第一部分):纳斯达克集成电路制造商

MetaTrader 5示例 | 20 五月 2025, 08:29
274 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

投资者有多种方式来多样化他们的投资组合。此外,还有许多不同的指标可以作为衡量投资组合优化程度的标准。不太可能有任何一位投资者会有足够的时间或资源,在做出如此重大决定之前仔细考虑所有的选项。在本系列文章中,我们将带您了解在同时交易多个品种的过程中,您面前的众多选择。我们的目标是帮助您决定哪些策略适合保留,而哪些可能不适合您。


交易策略概述

在本次探讨中,我们选择了一组相互关联的股票。我们选择了5只在业务周期中设计和销售集成电路的公司的股票。这些公司分别是Broadcom、Cisco、Intel、NVIDIA和Comcast。这5家公司都在美国全国证券交易商协会自动报价系统(NASDAQ)交易所上市。纳斯达克成立于1971年,按交易量计算是美国最大的交易所。


集成电路已经成为我们日常生活中不可或缺的一部分。这些电子芯片渗透到我们现代生活的各个方面,从托管您正在阅读本文网站所依赖的MetaQuotes专属服务器,到您用来阅读本文的设备,所有这些所依赖的技术,很可能是由这5家公司中的某一家开发的。世界上第一个集成电路是由英特尔开发的,品牌名为英特尔4004,于1971年推出,同年纳斯达克交易所成立。英特尔4004大约有2600个晶体管,与现代芯片相比相去甚远,现代芯片轻松拥有数十亿个晶体管。

由于受到全球对集成电路需求的驱动,我们希望理智地进入芯片市场。鉴于这5只股票的组合,我们将展示如何通过谨慎地在它们之间分配资本来最大化您的投资组合回报。在现代波动性较大的市场中,将资本均匀分配在所有5只股票之间的传统方法是不够的。相反,我们将构建一个模型,判断是否应该买入或卖出每只股票,以及应该交易的最优数量。换句话说,我们正在使用手头上的数据,通过算法学习我们的仓位规模和数量。


方法论概述

我们首先使用MetaTrader 5 Python库,从我们的MetaTrader 5终端为我们股票池中的5只股票获取十万行M1(1分钟)的市场数据。在将普通价格数据转换为百分比后,我们对市场收益率数据进行了探索性数据分析。

我们观察到这5只股票之间的相关性水平很弱。此外,我们的箱线图清楚地显示,每只股票的平均回报接近于0。我们还将每只股票的回报以叠加的方式绘制出来,可以清楚地观察到NVIDIA的股票回报似乎波动最大。最后,我们为所选的5只股票创建了成对的图表,遗憾的是,我们没有观察到任何可以利用的明显关系。

从此处开始,我们使用SciPy库为投资组合中的每只股票找到最优权重。我们将允许所有5个权重在-1到1之间变化。每当我们的投资组合权重低于0时,算法提示我们卖出,反之,当我们的权重高于0时,数据建议我们买入。

在计算出最优的投资组合权重后,我们将这些数据整合到交易应用程序中,以确保始终在每个市场中保持最优数量的仓位。我们的交易应用程序设计为,如果持仓达到终端用户指定的盈利水平,将自动平仓。


获取数据 

开始之前,让我们首先导入需要的库。

#Import the libraries we need
import pandas              as pd
import numpy               as np
import seaborn             as sns
import matplotlib.pyplot   as plt
import MetaTrader5         as mt5
from   scipy.optimize      import minimize

现在让我们初始化MetaTrader 5终端。

#Initialize the terminal
mt5.initialize()
情况相符

定义我们希望交易的股票池。

#Now let us fetch the data we need on chip manufacturing stocks
#Broadcom, Cisco, Comcast, Intel, NVIDIA
stocks = ["AVGO.NAS","CSCO.NAS","CMCSA.NAS","INTC.NAS","NVDA.NAS"]

让我们创建一个数据结构来存储市场数据。

#Let us create a data frame to store our stock returns
amount = 100000
returns = pd.DataFrame(columns=stocks,index=np.arange(0,amount))

现在,我们将获取市场数据。

#Fetch the stock returns
for stock in stocks:
    temp = pd.DataFrame(mt5.copy_rates_from_pos(stock,mt5.TIMEFRAME_M1,0,amount))
    returns[[stock]] = temp[["close"]].pct_change()

让我们格式化数据。

#Format the data set 
returns.dropna(inplace=True)
returns.reset_index(inplace=True,drop=True)
returns

最后,将数据乘以100,以百分比形式保存。

#Convert the returns to percentages
returns = returns * 100
returns


探索性数据分析

有时,我们可以直观地看到系统中变量之间的关系。让我们分析数据中的相关性水平,看看是否存在我们可以利用的线性组合。遗憾的是,我们的相关性水平比较弱,到目前为止,似乎没有任何线性依赖关系可供我们利用。

#Let's analyze if there is any correlation in the data
sns.heatmap(returns.corr(),annot=True)

图1:我们的相关性热力图

让我们分析数据的成对散点图。在处理大型数据集时,非显而易见的关系可能会轻易地被我们忽视。成对的散点图将最大限度地减少这种情况发生的可能性。遗憾的是,我们的图表并没有揭示出数据中任何容易被观察到的关系。

#Let's create pair plots of our data
sns.pairplot(returns)

图2:我们的一些成对散点图

绘制我们在通过数据观察到的回报率,显示NVIDIA的回报率似乎波动最大。

#Lets also visualize our returns
returns.plot()

图3:绘制我们的市场回报率

将我们的市场回报率以箱线图的形式可视化,清楚地显示了平均市场回报率为0。

#Let's try creating box-plots 
sns.boxplot(returns)

图4:以箱线图形式可视化我们的市场回报率



投资组合优化

我们现在开始计算每只股票的最优资本分配权重。最初,我们将随机分配权重。此外,我们还将创建一个数据结构,用于存储优化算法的进展。

#Define random weights that add up to 1
weights = np.array([1,0.5,0,0.5,-1])
#Create a data structure to store the progress of the algorithm
evaluation_history = []

我们优化程序的目标函数将是给定权重下的投资组合回报率。请注意,我们的投资组合回报率将通过资产回报率的几何平均数来计算。我们选择使用几何平均数而不是算术平均数,因为当我们处理正数和负数时,计算平均值不再是一个简单的任务。如果我们轻率地处理这个问题并使用算术平均数,可能会很容易计算出一个投资组合回报率为0。我们可以通过在将投资组合回报率返回给优化算法之前将其乘以-1,来使用最小化算法解决最大化问题。

#Let us now get ready to maximize our returns
#First we need to define the cost function
def cost_function(x):
    #First we need to calculate the portfolio returns with the suggested weights
    portfolio_returns = np.dot(returns,x)
    geom_mean         =  ((np.prod( 1 + portfolio_returns ) ** (1.0/99999.0)) - 1)
    #Let's keep track of how our algorithm is performing
    evaluation_history.append(-geom_mean)
    return(-geom_mean)

现在让我们定义一个约束条件,确保所有的权重加起来等于1。请注意,SciPy中只有少数优化程序支持等式约束。等式约束告诉SciPy模块,我们希望这个函数的结果等于0。因此,我们希望权重的绝对值与1之间的差为0。

#Now we need to define our constraints
def l1_norm_constraint(x):
    return(((np.sum(np.abs(x))) - 1))

constraints = ({'type':'eq','fun':l1_norm_constraint})

我们所有的权重都应该在-1和1之间。可以通过为我们的算法定义界限来强制实现。

#Now we need to define the bounds for our weights
bounds = [(-1,1)] * 5

执行优化过程。

#Perform the optimization
results = minimize(cost_function,weights,method="SLSQP",bounds=bounds,constraints=constraints)

我们优化过程的结果。

结果
信息:成功完成优化
成功状态:True
  状态:0
     fun: 0.0024308603411499208
       x: [ 3.931e-01  1.138e-01 -5.991e-02  7.744e-02 -3.557e-01]
     nit: 23
     jac: [ 3.851e-04  2.506e-05 -3.083e-04 -6.868e-05 -3.186e-04]
    nfev: 158
    njev: 23

让我们存储计算出的最优系数值。

optimal_weights = results.x
optimal_weights
array([ 0.39311134,  0.11379942, -0.05991417,  0.07743534, -0.35573973])

我们还应该存储程序中的最优解。

optima_y = min(evaluation_history)
optima_x = evaluation_history.index(optima_y)
inputs = np.arange(0,len(evaluation_history))

让我们来可视化优化算法的性能历史。从图表中可以看出,我们的算法在最初的50次迭代中似乎遇到了一些困难。然而,其似乎已经找到了一个最优解,能够最大化我们的投资组合回报率。

plt.scatter(inputs,evaluation_history)
plt.plot(optima_x,optima_y,'s',color='r')
plt.axvline(x=optima_x,ls='--',color='red')
plt.axhline(y=optima_y,ls='--',color='red')
plt.title("Maximizing Returns")

图5:我们的SLSQP优化算法的性能

让我们检查权重的绝对值是否加起来等于1,换句话说,我们希望验证没有违反L1范数约束。

#Validate the weights add up to 1
np.sum(np.abs(optimal_weights))
1.0

我们可以直观地解释这些最优系数。假设我们想要开设10个仓位,我们首先将系数乘以10。然后我们进行整除1的操作,去掉所有小数部分。剩下的整数为认为我们应该在每个市场开设的仓位数量。我们的数据似乎表明,为了最大化投资回报,我们应该在Broadcom开设3个多头仓位,在Cisco开设1个多头仓位,在Comcast开设1个空头仓位,在Intel不开设任何仓位,并在NVIDIA开设4个空头仓位。 

#Here's an intuitive way of understanding the data
#If we can only open 10 positions, our best bet may be
#3 buy positions in Broadcom
#1 buy position in Cisco
#1 sell position sell position in Comcast
#No positions in Intel
#4 sell postions in NVIDIA
(optimal_weights * 10) // 1
array([ 3.,  1., -1.,  0., -4.])


使用 MQL5 实现

现在让我们在MQL5中实现交易策略。首先,我们将定义在应用程序中使用的全局变量。

//+------------------------------------------------------------------+
//|                                                 NASDAQ IC AI.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    rsi_handler,bb_handler;
double bid,ask;
int    optimal_weights[5] = {3,1,-1,0,-4};
string stocks[5]          = {"AVGO.NAS","CSCO.NAS","CMCSA.NAS","INTC.NAS","NVDA.NAS"};
vector current_close      = vector::Zeros(1);
vector rsi_buffer         = vector::Zeros(1);
vector bb_high_buffer     = vector::Zeros(1);
vector bb_mid_buffer      = vector::Zeros(1);
vector bb_low_buffer      = vector::Zeros(1);

导入交易库以帮助我们管理仓位。

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include  <Trade/Trade.mqh>
CTrade Trade;

通过允许控制的输入,程序的终端用户可以调整EA的行为。

//+------------------------------------------------------------------+
//| User inputs                                                      |
//+------------------------------------------------------------------+
input double profit_target =  1.0; //At this profit level, our position will be closed
input int    rsi_period    =   20; //Adjust the RSI period
input int    bb_period     =   20; //Adjust the Bollinger Bands period
input double trade_size    =  0.3; //How big should our trades be?

当首次设置交易算法时,我们需要确保之前计算中涉及的所有5个交易品种都是可用的。否则,我们将终止初始化过程。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Validate that all the symbols we need are available
   if(!validate_symbol())
     {
      return(INIT_FAILED);
     }
//--- Everything went fine
   return(INIT_SUCCEEDED);
  }

如果把程序从图表中移除,那么我们应该释放不再使用的资源。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release resources we no longer need
   release_resources();
  }

每当收到更新的价格时,我们首先希望将当前的卖出价(bid)和买入价(ask)存储于全局变量中,检查交易机会,并最终将已经准备好的利润兑现。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

//--- Update market data
   update_market_data();

//--- Check for a trade oppurtunity in each symbol
   check_trade_symbols();

//--- Check if we have an oppurtunity to take ourt profits
   check_profits();
  }

负责兑现利润的函数将遍历我们池中的所有交易品种。如果能够成功找到该交易品种,它将检查我们在该市场是否有任何持仓。假设有持仓,我们将检查利润是否超过了用户定义的利润目标,如果超过,我们将平仓。否则,我们将继续进行下一步。

//+------------------------------------------------------------------+
//| Check for opportunities to collect our profits                   |
//+------------------------------------------------------------------+
void check_profits(void)
  {
   for(int i =0; i < 5; i++)
     {
      if(SymbolSelect(stocks[i],true))
        {
         if(PositionSelect(stocks[i]))
           {
            if(PositionGetDouble(POSITION_PROFIT) > profit_target)
              {
               Trade.PositionClose(stocks[i]);
              }
           }
        }
     }
  }

每当收到更新的价格时,我们都希望将其存储于全局变量中,因为这些变量可能会被程序的各个部分调用。

//+------------------------------------------------------------------+
//| Update markte data                                               |
//+------------------------------------------------------------------+
void update_market_data(void)
  {
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
  }

每当EA不用时,我们将释放不再需要的资源,以确保良好的终端用户体验。

//+-------------------------------------------------------------------+
//| Release the resources we no longer need                           |
//+-------------------------------------------------------------------+
void release_resources(void)
  {
   ExpertRemove();
  }
//+------------------------------------------------------------------+

在初始化时,我们检查所需的全部交易品种是否可用。以下函数负责执行该任务。它会遍历我们股票数组中的所有交易品种。如果我们无法选择任何一个交易品种,该函数将返回false,并停止初始化过程。否则,该函数将返回true。

//+------------------------------------------------------------------+
//| Validate that all the symbols we need are available              |
//+------------------------------------------------------------------+
bool validate_symbol(void)
  {
   for(int i=0; i < 5; i++)
     {
      //--- We failed to add one of the necessary symbols to the Market Watch window!
      if(!SymbolSelect(stocks[i],true))
        {
         Comment("Failed to add ",stocks[i]," to the market watch. Ensure the symbol is available.");
         return(false);
        }
     }

//--- Everything went fine
   return(true);
  }

该函数负责协调在我们的投资组合中开仓和管理仓位的过程。它将遍历数组中的所有交易品种,并检查我们在该市场是否持仓,以及我们是否应该在该市场持仓。如果我们应该持仓,但实际上没有,该函数将开始检查在该市场开仓的机会。否则,该函数将不执行任何操作。

//+------------------------------------------------------------------+
//| Check if we have any trade opportunities                         |
//+------------------------------------------------------------------+
void check_trade_symbols(void)
  {
//--- Loop through all the symbols we have
   for(int i=0;i < 5;i++)
     {
      //--- Select that symbol and check how many positons we have open
      if(SymbolSelect(stocks[i],true))
        {
         //--- If we have no positions in that symbol, optimize the portfolio
         if((PositionsTotal() == 0) && (optimal_weights[i] != 0))
           {
            optimize_portfolio(stocks[i],optimal_weights[i]);
           }
        }
     }
  }

优化投资组合函数接受两个参数:正在考虑的股票和分配给该股票的权重。如果权重为正,该函数将调用一个程序,在该市场建立多头仓位,直到满足权重参数;如果权重为负,情况则相反。

//+------------------------------------------------------------------+
//| Optimize our portfolio                                           |
//+------------------------------------------------------------------+
void optimize_portfolio(string symbol,int weight)
  {
//--- If the weight is less than 0, check if we have any oppurtunities to sell that stock
   if(weight < 0)
     {
      if(SymbolSelect(symbol,true))
        {
         //--- If we have oppurtunities to sell, act on it
         if(check_sell(symbol, weight))
           {
            Trade.Sell(trade_size,symbol,bid,0,0,"NASDAQ IC AI");
           }
        }
     }

//--- Otherwise buy
   else
     {
      if(SymbolSelect(symbol,true))
        {
         //--- If we have oppurtunities to buy, act on it
         if(check_buy(symbol,weight))
           {
            Trade.Buy(trade_size,symbol,ask,0,0,"NASDAQ IC AI");
           }
        }
     }
  }

现在我们需要定义可以买入持仓的条件。我们将依靠技术分析和价格行为的结合来确定入场时机。只有当价格水平高于最上方的布林带、我们的相对强弱指数(RSI)水平高于70,并且较高时间框架上的价格走势为看涨时,我们才会买入持仓。同样,我们认为这可能构成一种高概率的交易布局(或策略设定),这将使我们能够安全地实现盈利目标。最后,我们的最终条件是在这个市场上持有的总仓位数量不超过最优分配水平。如果满足条件,那么我们将返回true,这样会给optimize_portfolio函数授权买入持仓。

//+------------------------------------------------------------------+
//| Check for oppurtunities to buy                                   |
//+------------------------------------------------------------------+
bool check_buy(string symbol, int weight)
  {
//--- Ensure we have selected the right symbol
   SymbolSelect(symbol,true);

//--- Load the indicators on the symbol
   bb_handler  = iBands(symbol,PERIOD_CURRENT,bb_period,0,1,PRICE_CLOSE);
   rsi_handler = iRSI(symbol,PERIOD_CURRENT,rsi_period,PRICE_CLOSE);
//--- Validate the indicators
   if((bb_handler == INVALID_HANDLE) || (rsi_handler == INVALID_HANDLE))
     {
      //--- Something went wrong
      return(false);
     }

//--- Load indicator readings into the buffers
   bb_high_buffer.CopyIndicatorBuffer(bb_handler,1,0,1);
   rsi_buffer.CopyIndicatorBuffer(rsi_handler,0,0,1);
   current_close.CopyRates(symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);

//--- Validate that we have a valid buy oppurtunity
   if((bb_high_buffer[0] < current_close[0]) && (rsi_buffer[0] > 70))
     {
      return(false);
     }

//--- Do we allready have enough positions
   if(PositionsTotal() >= weight)
     {
      return(false);
     }
//--- We can open a position
   return(true);
  }

“check_sell”函数与“check_buy”函数的工作方式类似,只不过它会先将权重乘以-1,这样我们就能轻松地计算出在市场上应该持有的仓位数量。该函数会接着检查价格是否低于布林带下轨,以及相对强弱指数(RSI)读数是否小于30。如果以上三个条件都满足,我们还需要确保较高时间框架上的价格走势允许我们建立空头仓位。

//+------------------------------------------------------------------+
//| Check for oppurtunities to sell                                  |
//+------------------------------------------------------------------+
bool check_sell(string symbol, int weight)
  {
//--- Ensure we have selected the right symbol
   SymbolSelect(symbol,true);

//--- Negate the weight
   weight = weight * -1;

//--- Load the indicators on the symbol
   bb_handler  = iBands(symbol,PERIOD_CURRENT,bb_period,0,1,PRICE_CLOSE);
   rsi_handler = iRSI(symbol,PERIOD_CURRENT,rsi_period,PRICE_CLOSE);
//--- Validate the indicators
   if((bb_handler == INVALID_HANDLE) || (rsi_handler == INVALID_HANDLE))
     {
      //--- Something went wrong
      return(false);
     }

//--- Load indicator readings into the buffers
   bb_low_buffer.CopyIndicatorBuffer(bb_handler,2,0,1);
   rsi_buffer.CopyIndicatorBuffer(rsi_handler,0,0,1);
   current_close.CopyRates(symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,0,1);

//--- Validate that we have a valid sell oppurtunity
   if(!((bb_low_buffer[0] > current_close[0]) && (rsi_buffer[0] < 30)))
     {
      return(false);
     }

//--- Do we have enough trades allready open?
   if(PositionsTotal() >= weight)
     {
      //--- We have a valid sell setup
      return(false);
     }

//--- We can go ahead and open a position
   return(true);
  }

我们的系统正在运行

图6:对我们的算法进行前瞻性测试


结论

在我们的讨论中,展示了如何利用AI算法化地确定您的仓位规模和资本分配。我们可以优化投资组合的许多不同方面,比如投资组合的风险(方差)、投资组合与行业基准表现的相关性(贝塔系数)以及投资组合的风险调整回报率。在我们的实例中,保持了模型的简单性,只考虑最大化回报。随着这个系列的推进,我们将考虑许多重要的指标。然而,这个简单的例子让我们能够理解投资组合优化背后的主要思想,即使进一步深入到更复杂的优化程序中,读者也可以充满信心地应对问题,因为这里概述的主要思想不会改变。虽然我们不能保证讨论中的信息每次都能带来成功,但如果您认真考虑以算法化的方式交易多个交易品种,那么这些内容绝对值得参考。

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15909

附加的文件 |
NASDAQ_IC_AI.mq5 (9.45 KB)
神经网络实践:第一个神经元 神经网络实践:第一个神经元
在本文中,我们将开始构建一些简单而不起眼的东西:神经元。我们将使用非常少量的 MQL5 代码对其进行编程。神经元在我的测试中表现良好。让我们回到这一系列关于神经网络的文章中,了解一下我在说什么。
交易中的神经网络:层次化向量变换器(HiVT) 交易中的神经网络:层次化向量变换器(HiVT)
我们邀请您来领略层次化矢量转换器(HiVT)方法,其专为快速、准确地预测多模态时间序列而开发。
一个基于新指标和条件长短期记忆网络(LSTM)的实例 一个基于新指标和条件长短期记忆网络(LSTM)的实例
本文探讨了一种用于自动化交易的EA的开发,该EA结合了技术分析和深度学习预测。
HTTP和Connexus(第2部分):理解HTTP架构和库设计 HTTP和Connexus(第2部分):理解HTTP架构和库设计
本文探讨了HTTP协议的基础知识,涵盖了主要方法(GET、POST、PUT、DELETE)、状态码以及URL的结构。此外,还介绍了Conexus库的构建起点,以及CQueryParam和CURL类,这些类用于在HTTP请求中操作URL和查询参数。
OSZAR »