最近加入的功能可以双向开启订单,这种订单账户系统被称为锁仓。对这种订单系统的支持可以简单地把来自 MetaTrader 4 的交易算法迁移到版本5的平台上,并且享受到 MetaTrader 5 功能上的优势。取得 MetaTrader 5 中锁仓选项的更多信息, 请阅读文章 "MetaTrader 5 已具备锁仓账户系统"。
在本文中,我们将讨论合计仓位的属性,而这样的仓位正是锁仓系统的目标。
1. 锁仓仓位. 类型
锁仓(合计)仓位是一种市场上的仓位,它是由多个市场订单构成的。在狭义上,锁仓仓位包括不同方向(买入和卖出)上的订单,但是,我更倾向于在更广的范围内使用“锁仓”,在这里,锁仓仓位也可以由相同方向的订单组成,这种方法与 股票量化软件_赫兹股票量化软件终端的功能有关: 我们可以在一个方向上开启订单,也可在不同的方向。
有几种方法可以对合计仓位进行分类,也许最常用的标准是根据组成市场订单的类型进行划分。所以,什么样的订单可以构成这样的仓位呢?下面的表格 1 显示了不同的组合。
编号 | 类型 | 描述 |
---|---|---|
1 | Hedge buy(锁仓买入) | 只进行买入操作 |
2 | Hedge netting buy(锁仓净额买入) | 净额买入 |
3 | Hedge sell(锁仓卖出) | 只进行卖出操作 |
4 | Hedge netting sell(锁仓净额卖出) | 净额卖出 |
5 | Hedge locked(锁仓锁定) | 锁定(全部锁仓) |
表格 1. 锁仓类型
让我们简要解释一下这些类型,如果合计仓位只有买入或者只有卖出订单 (根据 股票量化软件_赫兹股票量化软件采用的方式), 这个仓位就当成是锁仓买入或者锁仓卖出仓位。如果仓位中有不同的订单(买入和卖出都有), 我们将会确定哪种订单占优势,如果有更多的买入订单,仓位就会被判断为“锁仓净额买入”,如果由更多的卖出订单,这就是个“锁仓净额卖出”仓位。为了更加清晰,我们需要处理订单的交易量而不是它们的数量。假定,一个仓位有1个1.25手的买入订单和两个分别为0.5和0.6手的卖出订单,合计仓位将是交易量为0.15手的"锁仓净额买入"仓位。
1.25 – (0.5 + 0.6) = 0.15.
一种特殊的混合仓位是锁定,其中买入和卖出的交易量达到了平衡。
让我们把这些描述的锁仓类型使用下面的枚举来表示:
//+------------------------------------------------------------------+ //| 锁仓类型 | //+------------------------------------------------------------------+ enum ENUM_HEDGE_TYPE { HEDGE_BUY=0, // 买入 HEDGE_SELL=1, // 卖出 HEDGE_NETTING_BUY=2, // 净额买入 HEDGE_NETTING_SELL=3, // 净额卖出 HEDGE_LOCKED=4, // 锁定 };
在净额系统中的仓位也是合计的,只能是两种类型之一: 买入或者卖出。类型标识符是 ENUM_POSITION_TYPE 枚举中的数值之一:股票量化软件_赫兹股票量化软件
1) POSITION_TYPE_BUY;
2) POSITION_TYPE_SELL.
在锁仓系统中,我们有五种类型的合计仓位,
在下一节,我们将创建一个类来处理锁仓仓位的属性。
2. CHedgePositionInfo 类
标准库中提供了股票量化软件_赫兹股票量化软件 它可以用于访问开启的市场仓位中的属性。对于我们的情况,我们会部分使用这个类,因为这个类处理的仓位将表现为一个独立的市场订单(对于 MetaTrader 4 来说)。我们需要一个类,它可以处理合计仓位(锁仓)中的所有仓位。
让我们使用 OOP (面向对象编程) 工具来创建 CHedgePositionInfo 类:
//+------------------------------------------------------------------+ //| Class CHedgePositionInfo | //| 目标: 用于访问锁仓仓位信息的类 | //| 派生于 CObject 类. | //+------------------------------------------------------------------+ class CHedgePositionInfo : public CObject { //--- === 数据成员 === --- private: ENUM_HEDGE_TYPE m_type; double m_volume; double m_price; double m_stop_loss; double m_take_profit; ulong m_magic; //--- 对象 CArrayLong m_tickets; CSymbolInfo m_symbol; CPositionInfo m_pos_info; //--- === 方法 === --- public: //--- 构造函数/析构函数 void CHedgePositionInfo(void){}; void ~CHedgePositionInfo(void){}; //--- 初始化 bool Init(const string _symbol,const ulong _magic=0); //--- get 方法 CSymbolInfo *Symbol(void) {return GetPointer(m_symbol);}; CArrayLong *HedgeTickets(void) {return GetPointer(m_tickets);}; CPositionInfo *PositionInfo(void) {return GetPointer(m_pos_info);}; ulong Magic(void) const {return m_magic;}; //--- 快速访问锁仓的整数属性的方法 datetime Time(void); ulong TimeMsc(void); datetime TimeUpdate(void); ulong TimeUpdateMsc(void); ENUM_HEDGE_TYPE HedgeType(void); //--- 快速访问锁仓的双精度浮点型属性的方法 double Volume(double &_buy_volume,double &_sell_volume); double PriceOpen(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL); double StopLoss(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL); double TakeProfit(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL); double PriceCurrent(const ENUM_TRADE_TYPE_DIR _dir_type=TRADE_TYPE_ALL); double Commission(const bool _full=false); double Swap(void); double Profit(void); double Margin(void); //--- 快速访问锁仓字符串型属性的方法 string TypeDescription(void); //--- 信息方法 string FormatType(string &_str,const uint _type) const; //--- 选择 bool Select(void); //--- 状态 void StoreState(void); bool CheckState(void); private: //--- 计算方法 bool AveragePrice( const SPositionParams &_pos_params, double &_avg_pr, double &_base_volume, double &_quote_volume ); int CheckLoadHistory(ENUM_TIMEFRAMES period,datetime start_date); }; //+------------------------------------------------------------------+
简单介绍一下类的数据成员。
首先,有一个唯一的交易品种名称,也就是说,锁仓仓位可以包含相同交易品种的任意仓位。交易品种是根据 m_symbol 栏位来判断的, 它代表股票量化软件_赫兹股票量化软件类的样本。
第二,幻数 (m_magic) 可以用于过滤订单。过滤可以创建由一个EA交易所管理的仓位,这可以创建相同交易品种中的多个锁仓仓位。
还有一个动态数组用于订单的管理 (m_tickets),锁仓仓位订单的单号将会被加到这个数组中。
取得任意选定仓位属性(也就是以 MetaTrader 4 方式) 的功能是由 股票量化软件_赫兹股票量化 类实例 (m_pos_info) 来进行的,
其它的锁仓属性用于取得它的状态:
- 类型 (m_type);
- 交易量 (m_volume);
- 建仓价格 (m_price);
- 止损价格 (m_stop_loss);
- 获利价格 (m_take_profit).
类的构建是基于股票量化软件_赫兹股票量化软件类的逻辑的,这很自然。这也是为什么新的类中含有返回整数型属性、双精度型属性的方法等等,当然还会有特殊的方法。
2.1初始化方法
在使用类功能之前,我们需要初始化对应的实例。检查 EA 是否在锁仓系统条件下运行的方法以及所需交易品种是否被选中,还有幻数是否设置的方法。
//+------------------------------------------------------------------+ //| 初始化 | //+------------------------------------------------------------------+ bool CHedgePositionInfo::Init(const string _symbol,const ulong _magic=0) { //--- 账户预付款模式 ENUM_ACCOUNT_MARGIN_MODE margin_mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); if(margin_mode!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { Print(__FUNCTION__+": 不是锁仓模式!"); return false; } if(!m_symbol.Name(_symbol)) { Print(__FUNCTION__+": 交易品种没有被选择!"); return false; } ENUM_SYMBOL_CALC_MODE symbol_calc_mode=(ENUM_SYMBOL_CALC_MODE)SymbolInfoInteger(_symbol,SYMBOL_TRADE_CALC_MODE); if(symbol_calc_mode!=SYMBOL_CALC_MODE_FOREX) { Print(__FUNCTION__+": 只用于外汇交易模式!"); return false; } m_magic=_magic; //--- return true; } //+------------------------------------------------------------------+
这个方法对于后续锁仓类所使用的功能是必需的,请注意,在方法中检查了预付款计算模式,如果它不是“锁仓”模式,方法就返回 false。还检查了协议计算方法,我们将只会操作外汇交易协议。
2.2整数型属性
整数型属性是使用下面的方法访问的:
- datetime Time(void);
- ulong TimeMsc(void);
- datetime TimeUpdate(void);
- ulong TimeUpdateMsc(void);
- ENUM_HEDGE_TYPE HedgeType(void).
这里是 CHedgePositionInfo::Time() 方法的代码:
//+------------------------------------------------------------------+ //| 取得锁仓开启的时间 | //+------------------------------------------------------------------+ datetime CHedgePositionInfo::Time(void) { datetime hedge_time=WRONG_VALUE; int hedge_pos_num=m_tickets.Total(); //--- 如果有仓位 if(hedge_pos_num>0) { //--- 找到第一个开启的仓位 for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++) { ulong curr_pos_ticket=m_tickets.At(pos_idx); if(curr_pos_ticket<LONG_MAX) if(m_pos_info.SelectByTicket(curr_pos_ticket)) { datetime curr_pos_time=m_pos_info.Time(); if(curr_pos_time>0) { if(hedge_time==0) hedge_time=curr_pos_time; else { if(curr_pos_time<hedge_time) hedge_time=curr_pos_time; } } } } } //--- return hedge_time; } //+------------------------------------------------------------------+
为了取得锁仓开启的时间,也就是锁仓中第一个仓位实际开启的时间,我们需要遍历其中所有的仓位并找到最早 的一个。股票量化软件_赫兹股票量化软件
为了取得锁仓改变的时间,例如最后改变的仓位的时间,我们需要稍微修改一下之前的方法:
//+------------------------------------------------------------------+ //| 取得锁仓更新的时间 | //+------------------------------------------------------------------+ datetime CHedgePositionInfo::TimeUpdate(void) { datetime hedge_time_update=0; int hedge_pos_num=m_tickets.Total(); //--- 如果有仓位 if(hedge_pos_num>0) { //--- 找到第一个开启的仓位 for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++) { ulong curr_pos_ticket=m_tickets.At(pos_idx); if(curr_pos_ticket<LONG_MAX) if(m_pos_info.SelectByTicket(curr_pos_ticket)) { //--- 取得当前仓位的更新时间 datetime curr_pos_time_update=m_pos_info.TimeUpdate(); if(curr_pos_time_update>0) if(curr_pos_time_update>hedge_time_update) hedge_time_update=curr_pos_time_update; } } } //--- return hedge_time_update; } //+------------------------------------------------------------------+
这里是判断锁仓类型方法的代码:
//+------------------------------------------------------------------+ //| 取得锁仓类型 | //+------------------------------------------------------------------+ ENUM_HEDGE_TYPE CHedgePositionInfo::HedgeType(void) { ENUM_HEDGE_TYPE curr_hedge_type=WRONG_VALUE; int hedge_pos_num=m_tickets.Total(); //--- 如果有仓位 if(hedge_pos_num>0) { //--- 取得交易量 double total_vol,buy_volume,sell_volume; buy_volume=sell_volume=0.; total_vol=this.Volume(buy_volume,sell_volume); //--- 定义锁仓类型 if(buy_volume>0. && sell_volume>0.) { if(buy_volume>sell_volume) curr_hedge_type=HEDGE_NETTING_BUY; else if(buy_volume<sell_volume) curr_hedge_type=HEDGE_NETTING_SELL; else curr_hedge_type=HEDGE_LOCKED; } else if(buy_volume>0. && sell_volume==0.) curr_hedge_type=HEDGE_BUY; else if(buy_volume==0. && sell_volume>0.) curr_hedge_type=HEDGE_SELL; } //--- return curr_hedge_type; }; //+------------------------------------------------------------------+
锁仓类型依赖于买入和卖出交易量之间的差距。首先我们检查仓位是否是锁仓状态,如果买入和卖出的交易量是相等的,这就是一个完全的锁仓,如果它们不相等,就表示是一个部分锁仓。
然后我们检查仓位是包含不同方向上的交易量,即不仅仅是买入或者仅有卖出。股票量化软件_赫兹股票量化软件
2.3双精度型属性
双精度型属性是使用下面的方法访问的:
- double Volume(double &_buy_volume,double &_sell_volume);
- double PriceOpen(void);
- double StopLoss(void);
- double TakeProfit(void);
- double PriceCurrent(void);
- double Commission(void);
- double Swap(void);
- double Profit(void);
- double Margin(void).
确定锁仓交易量的方法是使用引用型参数的,这种实现方法可以使我们立即取得锁仓交易量和它的组成部分(买入和卖出订单)的交易量。股票量化软件_赫兹股票量化软件
//+------------------------------------------------------------------+ //| 取得锁仓交易量 | //+------------------------------------------------------------------+ double CHedgePositionInfo::Volume(double &_buy_volume,double &_sell_volume) { double total_vol=0.; int hedge_pos_num=m_tickets.Total(); //--- 如果有仓位 if(hedge_pos_num>0) { _buy_volume=_sell_volume=0.; //--- 取得买入/卖出交易量 for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++) { ulong curr_pos_ticket=m_tickets.At(pos_idx); if(curr_pos_ticket<LONG_MAX) if(m_pos_info.SelectByTicket(curr_pos_ticket)) { ENUM_POSITION_TYPE curr_pos_type=m_pos_info.PositionType(); double curr_pos_vol=m_pos_info.Volume(); if(curr_pos_vol>0.) { //--- 对于一个买入仓位 if(curr_pos_type==POSITION_TYPE_BUY) _buy_volume+=curr_pos_vol; //--- 对于卖出仓位 else if(curr_pos_type==POSITION_TYPE_SELL) _sell_volume+=curr_pos_vol; } } } total_vol=_buy_volume-_sell_volume; } //--- return total_vol; } //+------------------------------------------------------------------+
辅助方法 CHedgePositionInfo::AveragePrice() 方法是创建用于处理价格属性的,这里是根据价格水平的类型来计算平均锁仓价格的代码块。
//--- 如果计算了锁仓交易量 if(hedge_base_volume!=0. && hedge_quote_volume!=0.) { _avg_pr=fabs(hedge_quote_volume/hedge_base_volume); _base_volume=hedge_base_volume; _quote_volume=hedge_quote_volume; return true; }
这里使用的是数值方法: 报价币别的最终锁仓数值与存款币别的最终锁仓数值的比例。
晚些时候我们将会在单独的例子中分析这样的计算。
隔夜息和利润的取得方法就是计算锁仓中每个仓位的对应变量的总和,再返回结果值。取得手续费的方法会分析参与建仓的交易,这里有参数可以选择如何计算手续费大小。使用默认值可以只根据入场交易计算手续费,如果入场和出场手续费都要计算,就把参数设为 true。但是,请注意后一个选项在实际中很少使用,有以下原因,首先,选择的仓位可以使用反向仓位关闭,这样,就会有 DEAL_ENTRY_OUT_BY类型的交易, 这样的交易不会收取手续费。第二,如果 账户币别和基础币别不符,入场和出场的花费在汇率改变的时候可能会不同。股票量化软件_赫兹股票量化软件
//+------------------------------------------------------------------+ //| 取得锁仓的手续费 | //+------------------------------------------------------------------+ double CHedgePositionInfo::Commission(const bool _full=false) { double hedge_commission=0.; int hedge_pos_num=m_tickets.Total(); //--- 如果有仓位 if(hedge_pos_num>0) for(int pos_idx=0;pos_idx<hedge_pos_num;pos_idx++) { ulong curr_pos_ticket=m_tickets.At(pos_idx); if(curr_pos_ticket<LONG_MAX) if(m_pos_info.SelectByTicket(curr_pos_ticket)) { long curr_pos_id=m_pos_info.Identifier(); if(curr_pos_id>0) //--- 取得与所选仓位相关的交易历史 if(HistorySelectByPosition(curr_pos_id)) { CDealInfo curr_deal; int deals_num=HistoryDealsTotal(); for(int deal_idx=0;deal_idx<deals_num;deal_idx++) if(curr_deal.SelectByIndex(deal_idx)) { ENUM_DEAL_ENTRY curr_deal_entry=curr_deal.Entry(); if(curr_deal_entry==DEAL_ENTRY_IN) { double curr_deal_commission=NormalizeDouble(curr_deal.Commission(),2); if(curr_deal_commission!=0.) { double fac=1.; if(_full) fac=2.; hedge_commission+=(fac*curr_deal_commission); } } } } } } //--- return hedge_commission; } //+------------------------------------------------------------------+
类中包含 CHedgePositionInfo::Margin() 方法,它可以计算锁仓仓位的预付款数量。这个方法实际上很难编程,可以另写一篇独立的文章,来描述如何正确计算有开启仓位和挂单情况的预付款。
2.3.1锁仓仓位预付款
根据开发者的描述,有两种方法来未双向仓位计算保证金,使用的类型是由经纪商决定的。第一种方法使用的是基础计算,而第二种是根据大头计算的。股票量化软件_赫兹股票量化软件
我从来没有遇到过第二种计算类型,但是,还是在代码中实现了它。但是首先还是让我们探讨第一种方法,它将有更加复杂的算法,包含了预付款的计算:
- 对于未覆盖交易量
- 对于锁仓 (已覆盖) 交易量 (如果制定了锁仓预付款的大小)
- 对于挂单
在本文中,预付款是针对外汇交易和期货模型来计算的,没有考虑挂单的预付款计算。
锁仓仓位预付款的计算需要下面的参数信息才能完成:
- 存款币别. 账户通常有以下存款币别: USD, EUR, GBP, CHF.
- 预付款币别. 通常,它就是基础币别,例如,对于 EURUSD,预付款币别是欧元 (EUR); 而对于 AUDNZD 交叉货币对, 它就是澳元 (AUD)。
- 杠杆.
也请注意,终端的交易页面中,余额那一行中的预付款是以存款币别计算的,所以,计算结果应当是以存款币别为单位的预付款值。
因为预付款币别可能与存款币别不同,有几个计算选项:
- 当锁仓仓位交易品种中存款币别是基础币别的时候,例如您在一个美元账户中交易 USDCHF。
- 当锁仓仓位交易品种中存款币别是报价货币的时候,例如,如果您在一个美元账户中交易 EURUSD。
- 当锁仓仓位交易品种中不包含存款币别时,例如,如果您在一个美元账户中交易 AUDNZD。
第一个选项将是最容易计算的,而最后一种则是最难的。现在,让我们为每个选项探讨计算实例。
变化 1
假定有一个美元账户 (存款),含有五个开启的 USDCHF 仓位 (图1).
图1 市场上的 USDCHF 仓位
基本参数:
账户币别 - USD.
预付款币别 - USD.
杠杆 - 1:100.
其中三个是买入仓位,仓位的总交易量是 5.55 手, 等于 $555,000。另外两个是卖出仓位,这些仓位的总交易量是 7.5 手, 等于 $750,000。
A) 未覆盖交易量的计算
未覆盖交易量等于 1.95 手, 也就是 $195,000,它是卖出的交易量,因为卖出比买入要多。但是,在我们的例子中买入或者卖出都没有关系,因为我们不需要计算加权平均价格,
这个数量的预付款是考虑到杠杆的情况下计算的:
$195,000 / 100 = $1,950.
B) 锁仓交易量的计算
锁仓交易量等于 5.55 手, 也就是 $555,000.
这个数量的预付款是考虑到杠杆的情况下计算的:
$555,000 / 100 = $5,550.
预付款的结果值是以锁仓中未覆盖交易量的预付款总和来计算的。
$1,950 + $5,550 = $7,500.
这就是在交易终端中显示的 "Margin(预付款)"数值。
变化 2
现在,有一个美元账户 (存款),含有五个开启的 EURUSD 仓位 (图2)。
图2 市场上的 EURUSD 仓位
基本参数:
- 账户币别 - USD.
- 预付款币别 - EUR.
- 杠杆 - 1:300.
其中三个是买入仓位,仓位的总交易量是 5.55 手, 等于 €555,000 或者 $645,617.20,买入仓位的加权平均价格是 $1.163274。
还有两个卖出仓位,这些仓位的总交易量是 7.5 手, 等于 €750,000 或者 $872 409。加权平均卖出价格是 $1.163212。
所有仓位都显示于表格2中。
类型 | 交易量 | 价格 | 价值, $ |
---|---|---|---|
买入 | 1.75 | 1.16329 | 203,575.75 |
买入 | 2.55 | 1.16329 | 296,638.95 |
买入 | 1.25 | 1.16322 | 145,402.50 |
卖出 | 3.00 | 1.16323 | 348,969.00 |
卖出 | 4.50 | 1.16320 | 523,440.00 |
总共 | 13.05 | 1.1632385 | 1,518,026.20 |
表格 2. 市场 EURUSD 仓位
一共有5个仓位,仓位的总交易量是 13.05 手, 它等于 €1,305,000 或 $1,518,026.20,加权平均仓位价格为 $1.16324。
A) 未覆盖交易量的计算
未覆盖交易量等于 1.95 手,也就是 €195,000。它是卖出的交易量,因为卖出比买入要多。所以,我们使用加权平均卖出价格来计算交易量的花费。
$1.163212 * €195,000 = $226,826.34.
这个数量的预付款是考虑到杠杆的情况下计算的:
$226,826.34 / 300 = $756.09.
B) 锁仓交易量的计算
锁仓交易量等于 5.55 手, 也就是 €555,000。让我们使用所有仓位的加权平均价格来确定这个交易量的值:
$1.1632385 * €555,000 = $645,597.35.
这个数量的预付款是考虑到杠杆的情况下计算的:
$645,597.35 / 300 = $2,151.99.
这样,从理论上看,整体的全账户总体预付款如下:
$756.09 + $2,151.99 = $2,908.08.
然而,终端中显示的数值是 $1,832.08。
这是因为交易品种规格中的 "Hedged margin" 参数是考虑到了覆盖(锁仓的)交易量。如果它小于合约大小,我们就要做些加倍,这个参数在我们的交易品种规格中等于 50000。然后:
锁仓交易量 = $1.1632385 * €555,000 / (100,000 / 50,000) = $322,798,67.
用于锁仓交易量的预付款 = $322,798.67 / 300 = $1,076.00.
计算总和: $756.09 + $1,076.00 = $1,832.08. 这是终端中显示的数值。