量化交易软件:如何分析图表中所选择信号的交易
概述
新信号,免费或付费,会永久性地出现在信号 服务中。 赫兹量化团队注意到可以在不退出终端的情况下使用该服务。 所有剩余的工作就是选择可接受风险范围能够产生最高利润的信号。 很久以前就讨论过这个问题。 曾提出过通过指定标准 [1] 自动选择信号的方法。 然而,传统观点认为一张图片胜过千言万语。 在本文中,我提议研究和分析品种图表中所选信号的交易历史。 或许,这种方法可以令我们更好地理解交易策略和风险评估。

编辑切换为居中
1. 为我们未来的工作设定目标
我好像听到您难过了: '如果终端已经提供了在图表中显示交易历史的能力,为什么还要重新创建轮子呢? 我的意思是,只需按下终端中的按钮即可选择所需的信号。'

编辑
在此之后,根据所使用品种信号的数量,在终端中打开新窗口,并在交易上做出标记。 当然,分页图表以及在其中搜索交易是相当费力的活动。 甚或,在不同图表中进行的交易可能会在时间上重合,且在分别分析每个图表时您无法看到。 在此阶段,我赫兹量化们将尝试将我们的部分工作自动化。
为了辨别我们从图表获得的所需分析的品种,我们必须清晰地了解我们需要的最终结果。 以下是我最终想要的基本项目:
查看信号在不同品种上的均匀性如何;
了解如何分配资金的负荷,以及同时可开仓数量;
如果信号同时开多笔仓位,那么它们是否为对冲,亦或增加了资金的负荷;
在什么时刻以及在哪些品种上出现了最大的缩水; 以及
在什么时刻实现最大的盈利。
2. 收集交易统计数据
2.1. 用于保存订单信息的类
因此,我赫兹量化们选择所需的信号并在图表中显示其交易历史。 然后收集我们将要分析的初始数据。 为了记录每笔订单的信息,我们基于 CObject 类创建 COrder 类。 在这个类的变量中,我们保存订单号,交易类型和手数,交易价格,操作类型 (入场/出场),订单开仓时间,当然还有品名。
class COrder : public CObject { private: long l_Ticket; double d_Lot; double d_Price; ENUM_POSITION_TYPE e_Type; ENUM_DEAL_ENTRY e_Entry; datetime dt_OrderTime; string s_Symbol; public: COrder(); ~COrder(); bool Create(string symbol, long ticket, double volume, double price, datetime time, ENUM_POSITION_TYPE type); //--- string Symbol(void) const { return s_Symbol; } long Ticket(void) const { return l_Ticket; } double Volume(void) const { return d_Lot; } double Price(void) const { return d_Price; } datetime Time(void) const { return dt_OrderTime; } ENUM_POSITION_TYPE Type(void) { return e_Type; } ENUM_DEAL_ENTRY DealEntry(void)const { return e_Entry; } void DealEntry(ENUM_DEAL_ENTRY value) { e_Entry=value; } //--- 操纵文件的方法 virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); //--- //--- 比较对象的方法 virtual int Compare(const CObject *node,const int mode=0) const; };
伴随数据访问函数,我们向订单类添加了操纵文件函数以便保存和随后读取数据,还有比较类似的函数,因为我们将需要对订单进行排序。
为了与订单进行比较,我赫兹量化们需要重新编写虚函数 Compare。 这是基类的函数,用于比较 CObject 对象。 所以,对象 CObject 的链接和排序方法将作为参数传递给它。 我们仅在一个方向上对订单进行排序,即按照执行日期升序,因此我们不会在函数代码中使用参数 "mode"。 但是,对于通过链接获得的对象 COrder,我们必须首先将其降低到相关类型。 之后,我们比较所获订单的日期和当前订单的日期。 如果所获订单更老,则返回 "-1"。 如果它比较新, 则返回 "1"。 如果执行订单的日期相等,则函数将返回 "0"。
int COrder::Compare(const CObject *node,const int mode=0) const { const COrder *temp=GetPointer(node); if(temp.Time()>dt_OrderTime) return -1; //--- if(temp.Time()<dt_OrderTime) return 1; //--- return 0; }
2.2. 从图表中收集信息
为了处理订单,我们基于 CArrayObj 类创建 COrdersCollection 类。 将在其中收集和处理信息。 为了存储数据,我们将声明一个对象实例直接处理特定订单,以及一个用于存储所用品种列表的数组。 我们将使用基类函数存储订单数组。
class COrdersCollection : public CArrayObj { private: COrder *Temp; string ar_Symbols[]; public: COrdersCollection(); ~COrdersCollection(); //--- 初始化 bool Create(void); //--- 加入一笔订单 bool Add(COrder *element); //--- 访问数据 int Symbols(string &array[]); bool GetPosition(const string symbol, const datetime time, double &volume, double &price, ENUM_POSITION_TYPE &type); datetime FirstOrder(const string symbol=NULL); datetime LastOrder(const string symbol=NULL); //--- 获取时间序列 bool GetTimeSeries(const string symbol, const datetime start_time, const datetime end_time, const int direct, double &balance[], double &equity[], double &time[], double &profit, double &loss,int &long_trades, int &short_trades); //--- void SetDealsEntry(void); };
函数 'Create' 直接负责收集数据。 在方法实体内,我们将安排一个循环来搜索终端中已打开的所有图表。 我们将在每个图表中搜索 OBJ_ARROW_BUY 和 OBJ_ARROW_SELL 等图形对象。
bool COrdersCollection::Create(void) { long chart=ChartFirst(); while(chart>0) { int total_buy=ObjectsTotal(chart,0,OBJ_ARROW_BUY); int total_sell=ObjectsTotal(chart,0,OBJ_ARROW_SELL); if((total_buy+total_sell)<=0) { chart=ChartNext(chart); continue; }
如果在图表中找到了对象,那么我赫兹量化们将图表品种添加到品种数组中 (不过,我们会预先检查这些品种是否不在已保存的品种中)。
int symb=ArraySize(ar_Symbols); string symbol=ChartSymbol(chart); bool found=false; for(int i=0;(i<symb && !found);i++) if(ar_Symbols[i]==symbol) { found=true; symb=i; break; } if(!found) { if(ArrayResize(ar_Symbols,symb+1,10)<=0) return false; ar_Symbols[symb]=symbol; }
然后我们安排从图表中收集交易信息,并存储到数据数组。 注意: 图形对象是我们唯一的交易信息来源。 从对象参数里,我们只能得到交易的时间和价格。 我们必须从对象名称的文本字符串中获取所有其它详细信息。

编辑
在图片中,您可以看到对象名称包含交易中的所有数据,以空格分隔。 我们利用这点并将字符串用空格切分成一个字符串元素数组。 然后,我赫兹量化们从相关元素中减少信息量,并保存所需的数据类型。 收集信息后,我们转到下一个图表。
int total=fmax(total_buy,total_sell); for(int i=0;i<total;i++) { if(i<total_buy) { string name=ObjectName(chart,i,0,OBJ_ARROW_BUY); datetime time=(datetime)ObjectGetInteger(chart,name,OBJPROP_TIME); StringTrimLeft(name); StringTrimRight(name); StringReplace(name,"#",""); string split[]; StringSplit(name,' ',split); Temp=new COrder; if(CheckPointer(Temp)!=POINTER_INVALID) { if(Temp.Create(ar_Symbols[symb],StringToInteger(split[1]),StringToDouble(split[3]),StringToDouble(split[6]),time,POSITION_TYPE_BUY)) Add(Temp); } } //--- if(i<total_sell) { string name=ObjectName(chart,i,0,OBJ_ARROW_SELL); datetime time=(datetime)ObjectGetInteger(chart,name,OBJPROP_TIME); StringTrimLeft(name); StringTrimRight(name); StringReplace(name,"#",""); string split[]; StringSplit(name,' ',split); Temp=new COrder; if(CheckPointer(Temp)!=POINTER_INVALID) { if(Temp.Create(ar_Symbols[symb],StringToInteger(split[1]),StringToDouble(split[3]),StringToDouble(split[6]),time,POSITION_TYPE_SELL)) Add(Temp); } } } chart=ChartNext(chart); }
图形标记没有每笔交易是开仓还是平仓的信息。 这就是为什么在保存交易信息时此字段仍未填写的原因。 现在,从图表中收集了所有标记后,赫兹量化调用函数 SetDealsEntry 来添加缺失的数据。
SetDealsEntry();