在赫兹量化交易系统中我认为从本系列的前几篇文章中可以清楚地看出,我们需要实现一些额外的要点。更好地组织工作是绝对必要的,尤其是一些深入的改进。如果您计划仅用回放/模拟系统来操控一种资产,那么您就不需要我们将要实现的许多东西。您可以把它们放在一边 — 我的意思是它们不必出现在配置文件之中。
不过,由于能够把目录设置在一处,这些类型的错误就能少得多。这并不意味它们根本不会发生,只是更少见。请记住,我们可以将对象组织到更具体的目录当中,从而将图例 01 和图例 02 结合到一起。不过,在此,我将把所有内容都保留在一个更简单的水平上。您的实现方式可随意符合您的数据处理和组织风格。
我们已经见识过理论,现在是时候看看如何在实践中做到这一点了。这个过程相对简单明了,至少与我们尚未做的事情相比是这样。首先,我们为该类创建一个新的私密变量,如下代码所示:
private :
enum eTranscriptionDefine {Transcription_INFO, Transcription_DEFINE};
string m_szPath;
当我们将该变量添加到此位置时,它对于该类内部的所有过程均可见。不过,无法从类的外部访问它。这样可以防止它被覆盖修改。这是因为在类的某些内部过程当中,我们能在不知不觉中更改变量的值。我们也许很难明白为什么代码没有按预期工作。
一旦这些完成后,我们需要告诉我们的类开始识别配置文件中的新命令。此过程是在非常具体的点上完成的,但可能会因我们所添加的内容而异。在我们的例子中,我们将按如下所示的顺序来做:
inline bool Configs(const string szInfo) {const string szList[] = {"POINTSPERTICK", "PATH" };string szRet[]; char cWho;if (StringSplit(szInfo, '=', szRet) == 2) {StringTrimRight(szRet[0]);StringTrimLeft(szRet[1]);for (cWho = 0; cWho < ArraySize(szList); cWho++) if (szList[cWho] == szRet[0]) break;switch (cWho){ case 0: m_PointsPerTick = StringToDouble(szRet[1]); return true; case 1: m_szPath = szRet[1]; return true; }Print("Variable >>", szRet[0], "<< undefined."); }els Print("Definition of configuratoin >>", szInfo, "<< invalid."); return false; }
注意,当所有代码的结构都得以改进时,这就容易得多。不过,我们必须小心。如果我们采取预先措施,我们将毫无问题地把我们需要的所有一切添加到代码之中。
我们要做的第一件事是把配置文件中要用到的命令名称或标签添加到顺序数据数组之内。注意,所有这些都必须用大写字母编写。我们可以令其大小写敏感,但这会让用户更难键入,以及将其放置在配置文件当中。如果您是唯一使用该系统,并打算用同一标签但具有不同值的人,那么区分大小写的系统大概是一个好主意。否则,这个想法会令整个工作复杂化。个人而言,我认为使用相同的标签来表达不同的含义只会让我们的生活更加困难。这就是为什么我不会这样做。
将标签添加到命令矩阵后,我们需要实现其功能。此刻完成恰到好处。就这么简单。由于它是链条中的第二环,并且链条从零开始,因此我们以数字 1 来表示我们正在实现该特定功能。该思路是仅指定目录名称,所以命令十分简单。最后,我们将返回 true 给调用方,指示该命令已被识别,并成功实现。
往系统里附加任何东西的顺序与所示完全相同。一旦开始这样做,我们就能使用配置文件中提供的数据。不过,有一点我忘了提,它很简单,但值得关注。在某些情况下,添加的新资源也许会导致问题出现,而实际上或许只是因为它未正确初始化。在这种情况下,每当我们添加私密全局变量时,我们都需要确保它在类构造函数中被正确初始化。您可以在下面的代码中看到这一点,其中我们正在初始化一个新变量。
C_ConfigService():m_szPath(NULL){}
按这样做,我们确保为尚未赋值的变量会有一个已知值。在某些状况下,这个细节可能看起来微不足道,但在其它情况下,它可以避免严重的问题,并节省时间,且被认为是良好的编程实践。完成这项工作之后,变量已在类构造函数中初始化,并且我们已明确如何基于配置文件中指定的内容为其赋值,到了使用该值的时候了。该值将仅在一个负责控制数据库加载的函数中用到。
看看如何实现这一点:
bool SetSymbolReplay(const string szFileConfig)
{
#define macroFileName ((m_szPath != NULL ? m_szPath + "\\" : "") + szInfo)
int file,
iLine;
char cError,
cStage;
string szInfo;
bool bBarsPrev;
C_FileBars *pFileBars;
if ((file = FileOpen("Market Replay\\" + szFileConfig, FILE_CSV | FILE_READ | FILE_ANSI)) == INVALID_HANDLE)
{
Print("Failed to open the configuration file [", szFileConfig, "]. Closing the service...");
return false;
}
Print("Loading ticks for replay. Please wait....");
ArrayResize(m_Ticks.Rate, def_BarsDiary);
m_Ticks.nRate = -1;
m_Ticks.Rate[0].time = 0;
iLine = 1;
cError = cStage = 0;
bBarsPrev = false;
while ((!FileIsEnding(file)) && (!_StopFlag) && (cError == 0))
{
switch (GetDefinition(FileReadString(file), szInfo))
{
case Transcription_DEFINE:
cError = (WhatDefine(szInfo, cStage) ? 0 : 1);
break;
case Transcription_INFO:
if (szInfo != "") switch (cStage)
{
case 0:
cError = 2;
break;
case 1:
pFileBars = new C_FileBars(macroFileName);
if ((m_dtPrevLoading = (*pFileBars).LoadPreView()) == 0) cError = 3; else bBarsPrev = true;
delete pFileBars;
break;
case 2:
if (LoadTicks(macroFileName) == 0) cError = 4;
break;
case 3:
if ((m_dtPrevLoading = LoadTicks(macroFileName, false)) == 0) cError = 5; else bBarsPrev = true;
break;
case 4:
if (!BarsToTicks(macroFileName)) cError = 6;
break;
case 5:
if (!Configs(szInfo)) cError = 7;
break;
}
break;
};
iLine += (cError > 0 ? 0 : 1);
}
FileClose(file);
switch(cError)
{
case 0:
if (m_Ticks.nTicks <= 0)
{
Print("No ticks to use. Closing the service...");
cError = -1;
}else if (!bBarsPrev) FirstBarNULL();
break;
case 1 : Print("Command in line ", iLine, " cannot be recognized by the system..."); break;
case 2 : Print("The system did not expect the content of the line ", iLine); break;
default : Print("Error in line ", iLine);
}
return (cError == 0 ? !_StopFlag : false);
#undef macroFileName
}
鉴于我们将以独特的方式同时在几个不同的地方使用它,故我选用宏定义来简化编码。所有标记为黄色之处都将完整接收宏定义中包含的代码。这大大简化了任务,因为没有必要多次编写相同的内容。这也避免了维护时在多个不同位置修改所用代码可能发生的错误。现在我们来仔细看看宏定义的作用。