加速这段代码——192 万亿次 for() 循环
Speeding up this code — 192 trillion for() loops
我有一个用于算法股票交易的小程序。代码必须在我的 8 核台式机上循环大约 192 万亿次。想过租个64核的机器给运行这个,但是不划算
就是这么一段代码。但是 for 循环必须在每个要计算的柱上循环(大约 180 万),然后它循环检查匹配项的列表大约有 80 万个项目。
目前我能想到的唯一加快速度的方法是删除匹配项,因为它只发生一次 (DateTime)。
有没有其他人有办法加快这段代码的速度?通过该程序的一次迭代,我的台式机怪兽 运行 花费了大约 45 小时。
基本上我正在做的是在每个柱上计算,查看当前柱的日期时间是否与我在手动创建的 CSV 文件中的日期时间相匹配。然后从列表对象中,我获取交易方向并设置一个 bool 来建仓。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using PowerLanguage.Function;
using ATCenterProxy.interop;
using System.IO;
using System.IO.Compression;
namespace PowerLanguage.Strategy
{
public class Ecal_v1 : SignalObject
{
public List<Trades> tradeList = new List<Trades>();
public List<string> csvList = new List<string>();
public bool exitOn24 = false;
public string ecalPath = @"C:\Users\Skynet\OneDrive\Trading\Economic Calendars\backtest1.csv";
PowerLanguage.Indicator.Bollinger_Bands bb;
public Ecal_v1(object _ctx):base(_ctx){}
//[Input]
//public bool exitOn24 { get; set; }
[Input]
public double bbTopOffset { get; set; }
775
[Input]
public double bbBotOffset { get; set; }
[Input]
public double longTPMod { get; set; }
[Input]
public double shortTPMod { get; set; }
[Input]
public double longSLMod { get; set; }
[Input]
public double shortSLMod { get; set; }
//[Input]
//public double buyTrail { get; set; }
//[Input]
//public double sellTrail { get; set; }
double bbUpperDiff;
double bbLowerDiff;
double bbBasis;
double longTP;
double shortTP;
double longSL;
double shortSL;
double ptValue;
public DateTime tradeTime;
private IOrderMarket longEntry, shortEntry, longExit, shortExit;
protected override void Create()
{
// create variable objects, function objects, order objects etc.
bb = ((PowerLanguage.Indicator.Bollinger_Bands)AddIndicator("Bollinger_Bands"));
longEntry = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.Buy));
shortEntry = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.SellShort));
longExit = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.Sell));
shortExit = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.BuyToCover));
}
protected override void StartCalc()
{
// assign inputs
GetEcal();
ptValue = Bars.Point;
longTP = longTPMod;
longSL = longSLMod;
shortTP = shortTPMod;
shortSL = shortSLMod;
}
protected override void CalcBar()
{
bool LE = false;
bool SE = false;
bool LX = false;
bool SX = false;
for(int i=0; i<tradeList.Count; i++)
{
if(Bars.Time[0] == tradeList.ElementAt(i).time)
{
if (tradeList.ElementAt(i).direction == "Up")
{
LE = true;
tradeList.RemoveAt(i);
}
else if (tradeList.ElementAt(i).direction == "Down")
{
SE = true;
tradeList.RemoveAt(i);
}
else
{
}
}
}
if(exitOn24 == true)
{
if (Bars.Time[0] > tradeTime.AddHours(24))
{
LX = true;
SX = true;
}
}
if (StrategyInfo.MarketPosition == 0)
{
if (LE)
{
longEntry.Send();
tradeTime = Bars.Time[0];
setLongStops();
}
else if (SE)
{
shortEntry.Send();
tradeTime = Bars.Time[0];
setShortStops();
}
}
else if (StrategyInfo.MarketPosition > 0)
{
if (LX)
{
longExit.Send();
}
else if (LE)
{
longEntry.Send();
tradeTime = Bars.Time[0];
setLongStops();
}
else
{
CurSpecOrdersMode = ESpecOrdersMode.PerPosition;
GenerateStopLossPt(longSL);
GenerateProfitTargetPt(longTP);
//GenerateTrailingStopPt(buyTrail);
}
}
else if (StrategyInfo.MarketPosition < 0)
{
if (SX)
{
shortExit.Send();
}
else if (SE)
{
shortEntry.Send();
tradeTime = Bars.Time[0];
setShortStops();
}
else
{
CurSpecOrdersMode = ESpecOrdersMode.PerPosition;
GenerateStopLossPt(shortSL);
GenerateProfitTargetPt(shortTP);
//GenerateTrailingStopPt(sellTrail);
}
}
}
private void GetEcal()
{
csvList = File.ReadAllLines(ecalPath).Skip(1).ToList();
foreach(string line in csvList)
{
string[] values = line.Split(',');
tradeList.Add(new Trades { time = Convert.ToDateTime(values[0]), direction = values[1] });
}
}
}
public class Trades
{
public DateTime time { get; set; }
public string direction { get; set; }
}
}
减速的罪魁祸首是 CalcBar() 方法中的 For 循环。
对于大型列表,哈希集通常是实现更好性能的好方法。此处提供更多信息:
或者为什么不使用字典并使用 DateTime 作为键(如果不需要存储更多详细信息,可以使用任何其他类型作为虚拟值)
然后,你实际上是基于键匹配,所以你要么命中要么失败。
你试过分析这个方法吗?我们的信息太少了。例如,成本最高的操作可能是
Bars.Time[0] == tradeList.ElementAt(i).time
我们不知道。你应该先分析它。
下一步是什么...
tradeList.ElementAt(i).direction == "Up"
不要使用字符串。弦很慢。您可以在此处使用枚举,它将针对整数进行优化,并且整数比较比字符串快得多。
不要使用 ElementAt 方法。仅使用 [] 运算符。它更快。
考虑使用字典而不是列表。它比列表快得多。列表必须遍历每个元素才能找到你需要的东西。字典不要。这可能是这里非常关键的部分。
考虑使用整数而不是日期时间。将整数视为秒。它会比 DateTime 更快。
并使用Parallel.ForEach代替普通的for。然后它将使用其他核心。普通for大概只用一个核心。
哦,还有一件事。如果它是股票应用程序,也许您可以尝试使用神经网络?但那是完全不同的故事。
RemoveAt 将处理列表的其余部分,将每个项目移到一个项目之后
你删除了一个地方。 see here。
这对你来说是一个巨大的成本。
解决方案是使用一个临时列表,在其中添加您需要的元素
稍后将移除,退出循环 (sourceList.Except(removedList)
) ;或者只是以某种方式将您的项目标记为已删除
永远不要触摸源列表。
您正在将 CSV 的所有行加载到内存中,只是为了读取它们并从每一行中创建一个强类型对象。
您可以改为逐行阅读文件,然后创建您的对象。
ElementAt 可能比索引器慢。由于您使用的是列表,因此只需使用 [] 访问项目即可避免疑虑。
要使用更少的内存并进行更快的比较,请将 direction
设为具有 'Up' 'Down' 个值的枚举。
如果不并行化代码,就无法利用许多内核。
一旦你把事情弄对了,如果程序仍然需要几个小时,你可以尝试 Parallel.For
而不是 for。在那种情况下,'mark an item as removed solution' 比使用并发列表并向其提供要删除的项目更简单,而且性能可能更高。
我的建议是通过分解流程来优化流程。首先,您检查:
Bars.Time[0] == tradeList.ElementAt(i).time;
Tom 利用此将其添加到 LINQ 语句中,以仅过滤满足您条件的语句:
tradeList.Where(t => t.time == Bars.Time[0]);
现在您有另一组 if 条件来控制您是否删除该项目:
tradeList.ElementAt(i).direction == "Down" || tradeList.ElementAt(i).direction == "Up";
这些可以使用 LINQ 进一步简化为:
tradeList.RemoveAll(d => d.direction == "Down" || d => d.Direction == "Up");
现在您可以在使用 Tom 的技术过滤后调用 RemoveAll 方法:
tradeList.Where(t => t.time == Bars.Time[0])
.RemoveAll(d => d.direction == "Up" || d => d.direction == "Down");
此语句的所有意图和目的都与您的相同,它使用 foreach 循环遍历列表。但是现在我们可以使用 PLINQ 来优化它。好吧,那么继续使用 PLINQ,您可以将此语句更改为:
tradeList.AsParallel().tradeList.Where(t => t.time != Bars.Time[0]
&& (d => d.direction != "Up" || d => d.direction != "Down"));
我将 RemoveAll() 中的逻辑合并到 Where() 方法中,此语句应为您提供所有不应删除的柱的列表。现在我不确定你拥有的 bool 标志(LE 和 SE)的目的是什么,但它们会在第一次命中后变为 true,所以有更好的方法来做到这一点。但这应该能让你从某个地方开始。
我有一个用于算法股票交易的小程序。代码必须在我的 8 核台式机上循环大约 192 万亿次。想过租个64核的机器给运行这个,但是不划算
就是这么一段代码。但是 for 循环必须在每个要计算的柱上循环(大约 180 万),然后它循环检查匹配项的列表大约有 80 万个项目。
目前我能想到的唯一加快速度的方法是删除匹配项,因为它只发生一次 (DateTime)。
有没有其他人有办法加快这段代码的速度?通过该程序的一次迭代,我的台式机怪兽 运行 花费了大约 45 小时。
基本上我正在做的是在每个柱上计算,查看当前柱的日期时间是否与我在手动创建的 CSV 文件中的日期时间相匹配。然后从列表对象中,我获取交易方向并设置一个 bool 来建仓。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using PowerLanguage.Function;
using ATCenterProxy.interop;
using System.IO;
using System.IO.Compression;
namespace PowerLanguage.Strategy
{
public class Ecal_v1 : SignalObject
{
public List<Trades> tradeList = new List<Trades>();
public List<string> csvList = new List<string>();
public bool exitOn24 = false;
public string ecalPath = @"C:\Users\Skynet\OneDrive\Trading\Economic Calendars\backtest1.csv";
PowerLanguage.Indicator.Bollinger_Bands bb;
public Ecal_v1(object _ctx):base(_ctx){}
//[Input]
//public bool exitOn24 { get; set; }
[Input]
public double bbTopOffset { get; set; }
775
[Input]
public double bbBotOffset { get; set; }
[Input]
public double longTPMod { get; set; }
[Input]
public double shortTPMod { get; set; }
[Input]
public double longSLMod { get; set; }
[Input]
public double shortSLMod { get; set; }
//[Input]
//public double buyTrail { get; set; }
//[Input]
//public double sellTrail { get; set; }
double bbUpperDiff;
double bbLowerDiff;
double bbBasis;
double longTP;
double shortTP;
double longSL;
double shortSL;
double ptValue;
public DateTime tradeTime;
private IOrderMarket longEntry, shortEntry, longExit, shortExit;
protected override void Create()
{
// create variable objects, function objects, order objects etc.
bb = ((PowerLanguage.Indicator.Bollinger_Bands)AddIndicator("Bollinger_Bands"));
longEntry = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.Buy));
shortEntry = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.SellShort));
longExit = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.Sell));
shortExit = OrderCreator.MarketNextBar(new SOrderParameters(Contracts.Default, EOrderAction.BuyToCover));
}
protected override void StartCalc()
{
// assign inputs
GetEcal();
ptValue = Bars.Point;
longTP = longTPMod;
longSL = longSLMod;
shortTP = shortTPMod;
shortSL = shortSLMod;
}
protected override void CalcBar()
{
bool LE = false;
bool SE = false;
bool LX = false;
bool SX = false;
for(int i=0; i<tradeList.Count; i++)
{
if(Bars.Time[0] == tradeList.ElementAt(i).time)
{
if (tradeList.ElementAt(i).direction == "Up")
{
LE = true;
tradeList.RemoveAt(i);
}
else if (tradeList.ElementAt(i).direction == "Down")
{
SE = true;
tradeList.RemoveAt(i);
}
else
{
}
}
}
if(exitOn24 == true)
{
if (Bars.Time[0] > tradeTime.AddHours(24))
{
LX = true;
SX = true;
}
}
if (StrategyInfo.MarketPosition == 0)
{
if (LE)
{
longEntry.Send();
tradeTime = Bars.Time[0];
setLongStops();
}
else if (SE)
{
shortEntry.Send();
tradeTime = Bars.Time[0];
setShortStops();
}
}
else if (StrategyInfo.MarketPosition > 0)
{
if (LX)
{
longExit.Send();
}
else if (LE)
{
longEntry.Send();
tradeTime = Bars.Time[0];
setLongStops();
}
else
{
CurSpecOrdersMode = ESpecOrdersMode.PerPosition;
GenerateStopLossPt(longSL);
GenerateProfitTargetPt(longTP);
//GenerateTrailingStopPt(buyTrail);
}
}
else if (StrategyInfo.MarketPosition < 0)
{
if (SX)
{
shortExit.Send();
}
else if (SE)
{
shortEntry.Send();
tradeTime = Bars.Time[0];
setShortStops();
}
else
{
CurSpecOrdersMode = ESpecOrdersMode.PerPosition;
GenerateStopLossPt(shortSL);
GenerateProfitTargetPt(shortTP);
//GenerateTrailingStopPt(sellTrail);
}
}
}
private void GetEcal()
{
csvList = File.ReadAllLines(ecalPath).Skip(1).ToList();
foreach(string line in csvList)
{
string[] values = line.Split(',');
tradeList.Add(new Trades { time = Convert.ToDateTime(values[0]), direction = values[1] });
}
}
}
public class Trades
{
public DateTime time { get; set; }
public string direction { get; set; }
}
}
减速的罪魁祸首是 CalcBar() 方法中的 For 循环。
对于大型列表,哈希集通常是实现更好性能的好方法。此处提供更多信息:
或者为什么不使用字典并使用 DateTime 作为键(如果不需要存储更多详细信息,可以使用任何其他类型作为虚拟值)
然后,你实际上是基于键匹配,所以你要么命中要么失败。
你试过分析这个方法吗?我们的信息太少了。例如,成本最高的操作可能是
Bars.Time[0] == tradeList.ElementAt(i).time
我们不知道。你应该先分析它。
下一步是什么...
tradeList.ElementAt(i).direction == "Up"
不要使用字符串。弦很慢。您可以在此处使用枚举,它将针对整数进行优化,并且整数比较比字符串快得多。
不要使用 ElementAt 方法。仅使用 [] 运算符。它更快。
考虑使用字典而不是列表。它比列表快得多。列表必须遍历每个元素才能找到你需要的东西。字典不要。这可能是这里非常关键的部分。
考虑使用整数而不是日期时间。将整数视为秒。它会比 DateTime 更快。
并使用Parallel.ForEach代替普通的for。然后它将使用其他核心。普通for大概只用一个核心。
哦,还有一件事。如果它是股票应用程序,也许您可以尝试使用神经网络?但那是完全不同的故事。
RemoveAt 将处理列表的其余部分,将每个项目移到一个项目之后 你删除了一个地方。 see here。 这对你来说是一个巨大的成本。
解决方案是使用一个临时列表,在其中添加您需要的元素 稍后将移除,退出循环 (
sourceList.Except(removedList)
) ;或者只是以某种方式将您的项目标记为已删除 永远不要触摸源列表。您正在将 CSV 的所有行加载到内存中,只是为了读取它们并从每一行中创建一个强类型对象。
您可以改为逐行阅读文件,然后创建您的对象。
ElementAt 可能比索引器慢。由于您使用的是列表,因此只需使用 [] 访问项目即可避免疑虑。
要使用更少的内存并进行更快的比较,请将
direction
设为具有 'Up' 'Down' 个值的枚举。
如果不并行化代码,就无法利用许多内核。
一旦你把事情弄对了,如果程序仍然需要几个小时,你可以尝试 Parallel.For
而不是 for。在那种情况下,'mark an item as removed solution' 比使用并发列表并向其提供要删除的项目更简单,而且性能可能更高。
我的建议是通过分解流程来优化流程。首先,您检查:
Bars.Time[0] == tradeList.ElementAt(i).time;
Tom 利用此将其添加到 LINQ 语句中,以仅过滤满足您条件的语句:
tradeList.Where(t => t.time == Bars.Time[0]);
现在您有另一组 if 条件来控制您是否删除该项目:
tradeList.ElementAt(i).direction == "Down" || tradeList.ElementAt(i).direction == "Up";
这些可以使用 LINQ 进一步简化为:
tradeList.RemoveAll(d => d.direction == "Down" || d => d.Direction == "Up");
现在您可以在使用 Tom 的技术过滤后调用 RemoveAll 方法:
tradeList.Where(t => t.time == Bars.Time[0])
.RemoveAll(d => d.direction == "Up" || d => d.direction == "Down");
此语句的所有意图和目的都与您的相同,它使用 foreach 循环遍历列表。但是现在我们可以使用 PLINQ 来优化它。好吧,那么继续使用 PLINQ,您可以将此语句更改为:
tradeList.AsParallel().tradeList.Where(t => t.time != Bars.Time[0]
&& (d => d.direction != "Up" || d => d.direction != "Down"));
我将 RemoveAll() 中的逻辑合并到 Where() 方法中,此语句应为您提供所有不应删除的柱的列表。现在我不确定你拥有的 bool 标志(LE 和 SE)的目的是什么,但它们会在第一次命中后变为 true,所以有更好的方法来做到这一点。但这应该能让你从某个地方开始。