【问题标题】:Speeding up this code — 192 trillion for() loops加速这段代码——192 万亿个 for() 循环
【发布时间】:2017-05-26 07:55:59
【问题描述】:

我有一个用于算法股票交易的小程序。代码必须在我的 8 核台式机上循环大约 192 万亿次。我想过租一台 64 核的机器来运行它,但它并不划算。

只是这段代码。但是 for 循环必须在每个要计算的柱上循环(大约 180 万),然后它循环检查匹配项的列表大约是 800k 项。

我现在能想到的加快速度的唯一方法是删除匹配的项目,因为它只发生一次(日期时间)。

还有其他人有办法加快这段代码的速度吗?我的桌面野兽需要大约 45 小时才能运行一次程序迭代。

基本上我所做的是计算每个柱,查看当前柱 DateTime 是否与我在手动创建的 CSV 文件中的 DateTime 匹配。然后从对象列表中,获取交易方向并设置一个布尔值来持仓。

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 循环。

【问题讨论】:

  • 在tradeList.ElementAt(i).direction上使用开关可能会更快。
  • 使用字典或以time 为键的排序树进行交易而不是列表。字典查找摊销为 O(1),树查找为 O(lgN)。在这里,咨询费用为 100 万美元 - 每次循环迭代的百万分之一肯定不会过多:)
  • 你可以试试Parallel.For
  • 您的 GetEcal 函数很慢,因为您正在将整个文件读入内存 (ReadAllLines),然后由于 ToList() 而复制数组两次。然后使用 String.Split 也会不必要地浪费内存 - 您应该使用基于有限状态机的 CSV 解析器 - 这将显着减少内存使用和运行时间。
  • 索引!索引!索引!索引。尤其是在贸易清单不经常变化的情况下。

标签: c# .net algorithmic-trading


【解决方案1】:

您是否尝试过分析此方法?我们的信息太少了。例如,可能最昂贵的操作是

Bars.Time[0] == tradeList.ElementAt(i).time

我们不知道。您应该先对其进行概要分析。

接下来是什么...

tradeList.ElementAt(i).direction == "Up"

不要使用字符串。字符串很慢。您可以在这里使用枚举,它将针对整数进行优化,并且整数比较比字符串快得多。

不要使用 ElementAt 方法。仅使用 [] 运算符。它更快。

考虑使用字典而不是列表。它比列表快得多。列表必须遍历每个元素才能找到您需要的内容。字典不要。这可能是这里非常关键的部分。

考虑使用整数而不是 dateTimes。将整数视为秒。它会比 DateTime 快。

并使用 Parallel.ForEach 而不是普通的 for。然后它将使用其他内核。普通的可能只使用一个核心。

哦,还有一件事。如果是股票应用程序,也许您可​​以尝试使用神经网络?但这是一个完全不同的故事。

【讨论】:

  • 这很有帮助。我可以使用 1/-1 作为交易方向,而不是字符串“Up”和“Down”。我没有意识到 ElementAt 比 [] 慢得多。我认为您使用字典是对的。我知道必须有一种方法来搜索匹配的 DateTIme 值,而不是遍历列表中的每个值以找到匹配项。交易软件会自动并行调用 CalcBar() 方法,因此它实际上使用了所有 8 个内核。另外,我正在使用神经网络,但只是为了创建我从中获取交易信息的 CSV 文件。
  • 抱歉,我花了这么长时间才回复这个问题,但我花了一段时间来优化和实施。这个答案的细节使我能够将处理速度提高 5 倍。
【解决方案2】:
  • RemoveAt 将处理列表的其余部分以移动每个项目之后 你删除了一个地方。 see here。 在您的情况下,这会带来巨大的成本。

    解决方案是使用一个临时列表,在其中添加您需要的元素 稍后将在循环外删除 (sourceList.Except(removedList)) ;或者只是以某种方式将您的项目标记为已删除 并且永远不要触摸源列表。

  • 您将 CSV 的所有行加载到内存中只是为了读取它们并从每一行中创建一个强类型对象。

    您可以改为逐行读取文件,然后创建您的对象。

  • ElementAt 可能比索引器慢。由于您使用的是列表,因此只需使用 [] 访问项目以避免疑虑。

  • 要使用更少的内存并加快比较速度,请将 direction 设为具有“向上”“向下”值的枚举。

如果您不并行化代码,您就无法利用许多内核。 一旦你做对了,如果程序仍然需要几个小时,你可以尝试Parallel.For 而不是 for。在这种情况下,“将项目标记为已删除解决方案”比使用并发列表并将要删除的项目提供给它更简单且性能可能更高。

【讨论】:

    【解决方案3】:

    对于大型列表,哈希集通常是提高性能的好方法。更多信息在这里:

    https://softwareengineering.stackexchange.com/questions/280361/list-comparing-techniques-for-faster-performance

    或者为什么不使用字典并使用 DateTime 作为您的键(如果您不需要存储更多详细信息,您可以使用任何其他类型作为虚拟值)

    然后,您实际上是根据键进行匹配,因此您要么命中,要么未命中。

    【讨论】:

      【解决方案4】:

      我的建议是通过分解来优化您的流程。首先,您检查:

      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() 方法中,该语句应为您提供所有不应删除的条的列表。现在我不确定布尔标志(LE 和 SE)的用途是什么,但它们会在第一次命中后变为真,所以有更好的方法来做到这一点。但这应该能让你从某个地方开始。

      【讨论】:

      • 你在告诉他这样的事情:好吧,你正在以最糟糕的方式做事,不了解你编写的每一行代码的成本和意义,但你可以更简洁地编写它们并并行化它们跨度>
      • 嗨 Huda,我的印象是 LINQ 比 for() 循环更昂贵。我错了吗?
      猜你喜欢
      • 1970-01-01
      • 2021-12-08
      • 1970-01-01
      • 2017-04-13
      • 1970-01-01
      • 2014-01-30
      • 1970-01-01
      • 2021-09-25
      • 2012-01-28
      相关资源
      最近更新 更多