用于读取仓位特性的函数
根据特性类型,MQL 程序可以使用几个 PositionGet 函数来获取仓位特性。在所有函数中,被请求的特定特性在第一个参数中定义,该参数采用上一节中讨论的ENUM_POSITION_PROPERTY 枚举之一的 ID。
对于每种类型的特性,都存在函数的简短形式和完整形式:第一种形式直接返回特性的值,第二种形式将其写入第二个参数,通过引用传递。
整数型特性及其兼容类型(datetime 和枚举)特性可以通过 PositionGetInteger 函数获得。
long PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property)
bool PositionGetInteger(ENUM_POSITION_PROPERTY_INTEGER property, long &value)
如果失败,该函数将返回 0 或 false。
PositionGetDouble 函数用于获取实数型特性。
double PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property)
bool PositionGetDouble(ENUM_POSITION_PROPERTY_DOUBLE property, double &value)
最后,字符串型特性由 PositionGetString 函数返回。
string PositionGetString(ENUM_POSITION_PROPERTY_STRING property)
bool PositionGetString(ENUM_POSITION_PROPERTY_STRING property, string &value)
如果失败,函数的第一种形式将返回一个空字符串。
为了读取仓位特性,我们已经有了一个现成的摘要界面 MonitorInterface (TradeBaseMonitor.mqh),我们用其来编写订单监视器。现在很容易实现一个类似的仓位监视器。结果附在 PositionMonitor.mqh 文件中。
PositionMonitorInterface 类继承自 MonitorInterface,其用于为所考虑的 ENUM_POSITION_PROPERTY 枚举的模板类型 I、D 和 S 赋值,并且考虑到仓位特性的具体情况,重写了两个 stringify 方法。
class PositionMonitorInterface:
public MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,
ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
{
public:
virtual string stringify(const long v,
const ENUM_POSITION_PROPERTY_INTEGER property) const override
{
switch(property)
{
case POSITION_TYPE:
return enumstr<ENUM_POSITION_TYPE>(v);
case POSITION_REASON:
return enumstr<ENUM_POSITION_REASON>(v);
case POSITION_TIME:
case POSITION_TIME_UPDATE:
return TimeToString(v, TIME_DATE | TIME_SECONDS);
case POSITION_TIME_MSC:
case POSITION_TIME_UPDATE_MSC:
return STR_TIME_MSC(v);
}
return (string)v;
}
virtual string stringify(const ENUM_POSITION_PROPERTY_DOUBLE property,
const string format = NULL) const override
{
if(format == NULL &&
(property == POSITION_PRICE_OPEN || property == POSITION_PRICE_CURRENT
|| property == POSITION_SL || property == POSITION_TP))
{
const int digits = (int)SymbolInfoInteger(PositionGetString(POSITION_SYMBOL),
SYMBOL_DIGITS);
return DoubleToString(PositionGetDouble(property), digits);
}
return MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,
ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
::stringify(property, format);
}
|
准备好查看仓位的特定监视器类是继承链中的下一个类,其基于 PositionGet 函数。通过订单号选择仓位是在构造函数中完成的。
class PositionMonitor: public PositionMonitorInterface
{
public:
const ulong ticket;
PositionMonitor(const ulong t): ticket(t)
{
if(!PositionSelectByTicket(ticket))
{
PrintFormat("Error: PositionSelectByTicket(%lld) failed: %s",
ticket, E2S(_LastError));
}
else
{
ready = true;
}
}
virtual long get(const ENUM_POSITION_PROPERTY_INTEGER property) const override
{
return PositionGetInteger(property);
}
virtual double get(const ENUM_POSITION_PROPERTY_DOUBLE property) const override
{
return PositionGetDouble(property);
}
virtual string get(const ENUM_POSITION_PROPERTY_STRING property) const override
{
return PositionGetString(property);
}
...
};
|
一个简单脚本将允许你记录第一个仓位的所有特性(如果至少有一个可用)。
void OnStart()
{
PositionMonitor pm(PositionGetTicket(0));
pm.print();
}
|
在日志中,我们应会看到此类内容。
MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER, »
» ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING>
ENUM_POSITION_PROPERTY_INTEGER Count=9
0 POSITION_TIME=2022.03.24 23:09:45
1 POSITION_TYPE=POSITION_TYPE_BUY
2 POSITION_MAGIC=0
3 POSITION_IDENTIFIER=1291755067
4 POSITION_TIME_MSC=2022.03.24 23:09:45'261
5 POSITION_TIME_UPDATE=2022.03.24 23:09:45
6 POSITION_TIME_UPDATE_MSC=2022.03.24 23:09:45'261
7 POSITION_TICKET=1291755067
8 POSITION_REASON=POSITION_REASON_EXPERT
ENUM_POSITION_PROPERTY_DOUBLE Count=8
0 POSITION_VOLUME=0.01
1 POSITION_PRICE_OPEN=1.09977
2 POSITION_PRICE_CURRENT=1.09965
3 POSITION_SL=0.00000
4 POSITION_TP=1.10500
5 POSITION_COMMISSION=0.0
6 POSITION_SWAP=0.0
7 POSITION_PROFIT=-0.12
ENUM_POSITION_PROPERTY_STRING Count=3
0 POSITION_SYMBOL=EURUSD
1 POSITION_COMMENT=
2 POSITION_EXTERNAL_ID=
|
如果目前没有未结仓位,我们会看到一条错误消息。
Error: PositionSelectByTicket(0) failed: TRADE_POSITION_NOT_FOUND
|
但是,监视器的用处不仅仅在于将特性输出到日志中。基于 PositionMonitor,我们创建了一个按条件选择仓位的类,类似于我们对订单 (OrderFilter) 所创建的。最终目标是为了改进我们的网格 EA 交易。
有了 OOP,创建新的筛选器类就变得非常简单。以下是完整的源代码(文件 PositionFilter.mqh)。
class PositionFilter: public TradeFilter<PositionMonitor,
ENUM_POSITION_PROPERTY_INTEGER,
ENUM_POSITION_PROPERTY_DOUBLE,
ENUM_POSITION_PROPERTY_STRING>
{
protected:
virtual int total() const override
{
return PositionsTotal();
}
virtual ulong get(const int i) const override
{
return PositionGetTicket(i);
}
};
|
例如,现在我们可以编写这样一个脚本,在给定魔术编号的仓位上获得特定的盈利。
input ulong Magic;
void OnStart()
{
PositionFilter filter;
ENUM_POSITION_PROPERTY_DOUBLE properties[] =
{POSITION_PROFIT, POSITION_VOLUME};
double profits[][2];
ulong tickets[];
string symbols[];
filter.let(POSITION_MAGIC, Magic).select(properties, tickets, profits);
filter.select(POSITION_SYMBOL, tickets, symbols);
for(int i = 0; i < ArraySize(symbols); ++i)
{
PrintFormat("%s[%lld]=%f",
symbols[i], tickets[i], profits[i][0] / profits[i][1]);
}
}
|
在这种情况下,我们必须调用 select 方法两次,因为我们感兴趣的特性类型是不同的:真实盈利和手数以及工具的字符串名称。在本章开头的其中一节中,当我们开发交易品种的筛选器类时,对 元组的概念进行了说明。在 MQL5 中,我们可以将其实现为具有任意类型字段的结构体模板。这种元组对于最终确定筛选器类的层次结构体非常方便,因为这样就能说明用任何类型的字段填充元组数组的 select 方法。
元组在 Tuples.mqh 文件中说明。其中的所有结构体都具有名称 TupleN<T1,...>,其中 N 为 2 到 8 之间的数字,并且对应于模板参数(Ti 类型)的数量。例如,Tuple2:
template<typename T1,typename T2>
struct Tuple2
{
T1 _1;
T2 _2;
static int size() { return 2; };
// M order, position, deal monitor class, any MonitorInterface<>
template<typename M>
void assign(const int &properties[], M &m)
{
if(ArraySize(properties) != size()) return;
_1 = m.get(properties[0], _1);
_2 = m.get(properties[1], _2);
}
};
|
在 TradeFilter (TradeFilter.mqh) 类中,我们添加一个带有元组的 select 函数版本。
template<typename T,typename I,typename D,typename S>
class TradeFilter
{
...
template<typename U> // type U must be Tuple<>, e.g. Tuple3<T1,T2,T3>
bool select(const int &property[], U &data[], const bool sort = false) const
{
const int q = ArraySize(property);
static const U u; // PRB: U::size() does not compile
if(q != u.size()) return false; // required condition
const int n = total();
// cycle through orders/positions/deals
for(int i = 0; i < n; ++i)
{
const ulong t = get(i);
// access to properties via monitor T
T m(t);
// check all filter conditions for different types of properties
if(match(m, longs)
&& match(m, doubles)
&& match(m, strings))
{
// for a suitable object, store the properties in an array of tuples
const int k = EXPAND(data);
data[k].assign(property, m);
}
}
if(sort)
{
sortTuple(data, u._1);
}
return true;
}
|
元组数组可以根据第一个 field_1 进行排序,因此你可以另外研究 sortTuple 辅助方法。
使用元组,你可以在一次 select 调用中查询一个筛选对象的三种不同类型的特性。
下面有一些显示 Magic 编号的仓位,对于每个额外获得一个交易品种和订单号,按盈利排序。
input ulong Magic;
void OnStart()
{
int props[] = {POSITION_PROFIT, POSITION_SYMBOL, POSITION_TICKET};
Tuple3<double,string,ulong> tuples[];
PositionFilter filter;
filter.let(POSITION_MAGIC, Magic).select(props, tuples, true);
ArrayPrint(tuples);
}
|
当然,元组数组(本例中为 Tuple3<double,string,ulong>)说明中的参数类型必须与请求的特性枚举类型 (POSITION_PROFIT, POSITION_SYMBOL, POSITION_TICKET) 相匹配。
现在我们可以稍微简化一下网格 EA 交易(这不仅意味着代码更短,而且更容易理解)。新版本名为 PendingOrderGrid2.mq5。这些变化将会影响与仓位管理相关的所有函数。
GetMyPositions 函数用于填充通过引用传递的元组的 types4tickets 数组。在每个 Tuple2 元组中,应存储仓位的类型和标签。在这种特殊情况下,我们可以只用一个二维数组 ulong 来代替元组,因为两个特性都是相同的基础类型。但是,我们使用元组来演示如何在调用代码中使用它们。
#include <MQL5Book/Tuples.mqh>
#include <MQL5Book/PositionFilter.mqh>
int GetMyPositions(const string s, const ulong m,
Tuple2<ulong,ulong> &types4tickets[])
{
int props[] = {POSITION_TYPE, POSITION_TICKET};
PositionFilter filter;
filter.let(POSITION_SYMBOL, s).let(POSITION_MAGIC, m)
.select(props, types4tickets, true);
return ArraySize(types4tickets);
}
|
注意,select 方法的最后一个参数(第三个)等于 true,它指示按照第一个字段(即仓位类型)对数组进行排序。因此,我们将在开始时买入,在结束时卖出。这是对冲平仓所必需的。
CompactPositions 方法的循环如下。
uint CompactPositions(const bool cleanup = false)
{
uint retcode = 0;
Tuple2<ulong,ulong> types4tickets[];
int i = 0, j = 0;
int n = GetMyPositions(_Symbol, Magic, types4tickets);
if(n > 0)
{
Print("CompactPositions: ", n);
for(i = 0, j = n - 1; i < j; ++i, --j)
{
if(types4tickets[i]._1 != types4tickets[j]._1) // as long as the types are different
{
retcode = CloseByPosition(types4tickets[i]._2, types4tickets[j]._2);
if(retcode) return retcode; // error
}
else
{
break;
}
}
}
if(cleanup && j < n)
{
retcode = CloseAllPositions(types4tickets, i, j + 1);
}
return retcode;
}
|
CloseAllPositions 函数几乎相同:
uint CloseAllPositions(const Tuple2<ulong,ulong> &types4tickets[],
const int start = 0, const int end = 0)
{
const int n = end == 0 ? ArraySize(types4tickets) : end;
Print("CloseAllPositions ", n - start);
for(int i = start; i < n; ++i)
{
MqlTradeRequestSyncLog request;
request.comment = "close down " + (string)(i + 1 - start)
+ " of " + (string)(n - start);
const ulong ticket = types4tickets[i]._2;
if(!(request.close(ticket) && request.completed()))
{
Print("Error: position is not closed ", ticket);
return request.result.retcode; // error
}
}
return 0; // success
}
|
你可以在测试程序中比较 EA 交易 PendingOrderGrid1.mq5 和 PendingOrderGrid2.mq5 的效果。
报告将略有不同,因为如果有几个仓位,它们会以相反的组合平仓,因此其他不成对的仓位平仓是相对于它们各自的点差进行的。