PLOについての興味深い見解 - ページ 11

 
Georgiy Merts:

「神格化」については、fxsaber氏の表現がありますが、彼自身は「このコードは繰り返しテストされており、動作する」と述べるだけで、その仕組みについては言及しませんでした。それは、あってはならないことだと私は思います。

このコードは、otfFilingType命令が実行 可能かどうかをチェックし、strSymbolで実行可能であればそれを返し、そうでなければ正解とするものです。


仕組みがまったくわからない。そして、fxsaberの権限にのみ依存すること。

どなたか解説していただけませんか?

それを理解するためには、この面倒な返り値を構成要素に分解すればよい。

 
Igor Makanu:

"目的は手段を正当化する "の例ですね。

このコードはスパゲティみたいなものですが、おそらく今コドベースで最も有用な作家であり、私自身彼のコードをそのまま使っていることを考えると、他の人に批評してもらいましょう。

 
Andrei Trukhanovich:

ちなみにこのコードはスパゲッティみたいなものですが、おそらく今コドベースで最も有用な作家であり、私自身も彼のコードをそのまま使っているので、他の人に批評してもらいましょう。

批判ではなく、何ができるかを考えているのですが......。しかし、インタビューにあるように、そして@fxsaber 自身も認めているように、頭痛以外の何物でもありません。


SZZ:小型モンスターの 初期型がより鮮明に見えた可能性が高いですね ;)

 
Igor Makanu:

ZS:オリジナル版のリトルモンスターは、もっとわかりやすく見える可能性が高いです ;)

ZIPから持って きた(一番最初のリリースが含まれている)。

  static bool VirtualOrderSelect( const TICKET_TYPE Index, const int Select, const int Pool = MODE_TRADES )
  {
    return(VIRTUAL::SelectOrders ? VIRTUAL::SelectOrders.OrderSelect(Index, Select, Pool) :
           #ifdef __MQL5__
             #ifdef __MT4ORDERS__
               ::OrderSelect(Index, Select, Pool)
             #else // __MT4ORDERS__
               false
             #endif // __MT4ORDERS__
           #else // __MQL5__
             ::OrderSelect(Index, Select, Pool)
           #endif // __MQL5__
           );
  }

しかし、その後もどんどん機能が必要になってきて、徐々にコードが肉付けされていきました。Filling機能では違いました。そこで肉付けされたものはなく、一気に書き上げた。

 
Vasiliy Sokolov:

正確に何をするかは、コンパイラーだけが知っている。最近のコンパイラは驚異的なユーティリティを備えている。一般的なコーダーに適応し、その人が何を必要としているのか、すでによく分かっているのです。コンパイラができることは、短い関数で シンプルでわかりやすいコードを書くことです。コンパイラは、多数の関数ノードからなるソースコードグラフを解析して、結果のプログラムを構築する方が簡単で効率的である。必要な機能が適切な場所に固定されるため、生産性にプラスの効果しかありません。

まったくその通りです。

MQL5の話をするならば、10~20~30年前の「最適化」手法は忘れてもいいのです。最も読みやすいコードを書く必要があります。それであって、ハッカーものでもなく、純粋なスマートさでもない。

なぜ?

コンパイラは5〜10サイクルのコード並べ替えを行うため、何十もの最適化パターンを使うまでもなく、驚くほど明快で簡潔なものとなっているのです。

MQL5コンパイラは、+2%の速度を出そうとする人間の試みを面白がっています。


興味のある方は、数学の計算は 分岐なしで進み、1つのコマンド(128ビットのデータ)で2つの根を一度に計算

このコードは次のアセンブラSSEコードに変わる:

         D1=sqrt((X1-X)*(X1-X)+(Y1-Y)*(Y1-Y));
         D2=sqrt((X2-X)*(X2-X)+(Y2-Y)*(Y2-Y));
         D3=sqrt((X3-X)*(X3-X)+(Y3-Y)*(Y3-Y));
         D4=sqrt((X4-X)*(X4-X)+(Y4-Y)*(Y4-Y));
         D5=sqrt((X5-X)*(X5-X)+(Y5-Y)*(Y5-Y));
         D6=sqrt((X6-X)*(X6-X)+(Y6-Y)*(Y6-Y));
         D7=sqrt((X7-X)*(X7-X)+(Y7-Y)*(Y7-Y));
         D8=sqrt((X8-X)*(X8-X)+(Y8-Y)*(Y8-Y));
        ...
        sqrtsd  xmm1, xmm1
        unpcklpd        xmm4, xmm4
        movapd  xmm3, xmmword ptr [rsp + 432]
        unpcklpd        xmm3, xmmword ptr [rsp + 384]
        subpd   xmm3, xmm4
        mulpd   xmm3, xmm3
        unpcklpd        xmm0, xmm0
        movapd  xmm5, xmmword ptr [rsp + 416]
        unpcklpd        xmm5, xmmword ptr [rsp + 400]
        subpd   xmm5, xmm0
        mulpd   xmm5, xmm5
        addpd   xmm5, xmm3
        sqrtpd  xmm8, xmm5
        movapd  xmm5, xmmword ptr [rsp + 464]
        subpd   xmm5, xmm4
        mulpd   xmm5, xmm5
        movapd  xmm7, xmm9
        subpd   xmm7, xmm0
        mulpd   xmm7, xmm7
        addpd   xmm7, xmm5
        movapd  xmm6, xmm10
        unpcklpd        xmm6, xmm11
        subpd   xmm6, xmm4
        movapd  xmm3, xmmword ptr [rsp + 368]
        unpcklpd        xmm3, xmmword ptr [rsp + 352]
        subpd   xmm3, xmm0
        movapd  xmm4, xmm8
        shufpd  xmm4, xmm4, 1
        sqrtpd  xmm5, xmm7
        mulpd   xmm6, xmm6
        mulpd   xmm3, xmm3
        addpd   xmm3, xmm6
        sqrtpd  xmm15, xmm3
        movapd  xmm0, xmm14
        unpcklpd        xmm0, xmmword ptr [rsp + 336]
        subpd   xmm0, xmm2
        mulpd   xmm0, xmm0
        movapd  xmm2, xmm0
        shufpd  xmm2, xmm2, 1
        addsd   xmm2, xmm0
        movapd  xmm0, xmm15
        shufpd  xmm0, xmm0, 1
        sqrtsd  xmm12, xmm2
これは、実は芸術作品なんです。アセンブラコマンドの4回の呼び出しで8個の根が計算された。1回の呼び出しで2つのダブルナンバーが計算される。

  • 配列を処理する場合、チェック、分岐、ダブルから整数へのインデックス変換での損失など、すべて通常通りです。

  • この例で配列を扱う場合、FPUとALUが常に混在することになり、パフォーマンス上非常に不利です

  • 動的配列アクセスの最適化は、賞賛に値する素晴らしいものです。しかし、FPU/ALU演算+ダブル→整数+分岐の混在は時間を浪費する

  • 一般的な結論は、完璧な最適化により、MQL5では数学が勝利した、というものです。ここで負けるのはアレイではなく、数学の勝利です。


     
    Georgiy Merts:

    それはなぜでしょうか?

    それどころか、「or」演算子よりも「if」2つの方がずっと簡単です。

    複雑な条件の結果を論理的な "or"("and "と混同しやすい)で把握し、両方の戻り方を追跡するよりも、まず1つの条件を追跡し、それが真なら関数を離れ、次に他の条件をチェックし、それが真なら離れる方が明らかに簡単です。

    このようなコードの正当性はデバッグにある」と書いてあるのは、かなり面白いです。なぜなら、このようなコードはずっと理解しやすいということだからです(さもなければ、なぜデバッグにあるのでしょうか ?)

    「神格化」については、fxsaber氏の表現がありますが、彼自身は「このコードは繰り返しテストされており、動作する」と述べるだけで、その仕組みについては言及しませんでした。それは、あってはならないことだと私は思います。

    このコードは、otfFilingType命令が実行 可能かどうかをチェックし、strSymbolで実行可能であればそれを返し、そうでなければ正解とするものです。


    仕組みがまったくわからない。そして、fxsaberの権限にのみ依存する。

    どなたか解説していただけませんか?

    一度座って一歩一歩分解したことがあるのですが、ペンと紙が必要だったようです)

    なぜこの解析が有用だったかというと、enumの値の整数の関係(enumerationの考え方自体でカプセル化されているはず)を使用しているため、enum構造を変更した場合、すべてが壊れてしまうことが理解できたからです。)私自身は、せいぜいモアレくらいで、これを回避するようにしています。WinAPIを扱っている方にとっては、おなじみでしょう。

     
    Andrey Khatimlianskii:

    理解するには、その扱いにくいリトルーンの表現を分解すればよいのです。

    そう、それを試してみたのです。でも、分解するほどのモチベーションはなく...。

     
    Renat Fatkhullin:

    全くその通りです。

    MQL5の話であれば、10~20~30年前の「最適化」手法は忘れてもいいのです。最も読みやすいコードを書く必要があります。それであって、ハッカーものでもなく、純粋なスマートさでもない。

    その通りです。この結論に達したのは、ずいぶん前のことです。でも、コンパイラでよくなると思っているからではありません。しかし、コードの問題の主な原因は人間自身であるため、できるだけシンプルで透明性のあるコードを書くべきだということです。

    透明化」できない場合は、なぜこうで、ああでないのか、詳細なコメントを書く必要があります。

    そして、コンパイラは...。十分に効率的にできないとしても、プログラムのある側面を考慮していないことによるバグの可能性よりは問題ないでしょう。

     
    fxsaber:

    コードは非常にシンプルで短いものです(説明)。FPに書けば、比較できて面白いかもしれませんね。

    お願いします。C#をFPスタイルで。


    using System;
    using System.Linq;
    using System.Collections.Generic;
    using static FeesCalculator.FeesCalcs;
    using System.Text;
    using static System.Math;
    
    namespace FeesCalculator
    {
        public static class EnumerableExt
        {
            /// <summary>
            /// Call of function 'action' for each element if sequence (mapping).
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="seq">Enumerable sequence</param>
            /// <param name="action">Need action</param>
            public static void Map<T>(this IEnumerable<T> seq, Action<T> action)
            {
                foreach (var item in seq)
                    action(item);
            }
        }
        /// <summary>
        /// Инвестиционный результат
        /// </summary>
        public record AccountRecord
        {
            public double Investor { get; init; }
            public double Manager { get; init; }
            public double Summary { get; init; }
    
            public static AccountRecord operator *(AccountRecord r, double v)
            {
                return new AccountRecord
                {
                    Investor = r.Investor * v,
                    Manager = r.Manager * v,
                    Summary = r.Summary * v
                };
            }
            public override string ToString()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(nameof(Investor)).Append(" = ").Append(Investor.ToString("F4")).Append(' ');
                sb.Append(nameof(Manager)).Append(" = ").Append(Manager.ToString("F4")).Append(' ');
                sb.Append(nameof(Summary)).Append(" = ").Append(Summary.ToString("F4"));
                return sb.ToString();
            }
        }
        /// <summary>
        /// Параметры оферты
        /// </summary>
        public record PammSet
        {
            /// <summary>
            /// Доходность за расчетный период
            /// </summary>
            public double Perfomance { get; init; }
            /// <summary>
            /// Вознаграждение управляющего
            /// </summary>
            public double Gift { get; init; }
            /// <summary>
            /// Количество расчетных периодов
            /// </summary>
            public int N { get; init; }
            /// <inheritdoc/>
            public override string ToString()
            {
                StringBuilder str = new StringBuilder();
                str.Append("Доходность за расчетный период: ").Append((Perfomance * 100.0).ToString("F1")).Append("%\t\n");
                str.Append("Вознаграждение управляющего: ").Append((Gift * 100.0).ToString("F1")).Append("%\t\n");
                str.Append("Количество расчетных периодов: ").Append(N);
                return str.ToString();
            }
    
        }
        /// <summary>
        /// Формулы расчета
        /// </summary>
        public class FeesCalcs
        {
            /// <summary>
            /// Если управляющий снимает деньги в конце каждого расчетного периода
            /// </summary>
            /// <param name="Performance"></param>
            /// <param name="Gift"></param>
            /// <param name="N"></param>
            /// <returns></returns>
            public static AccountRecord Set1(PammSet set)
            {
                Func<double> investor = () => Pow(1 + (set.Perfomance) * (1 - set.Gift), set.N);
                Func<double> manager = () => (investor() - 1) * set.Gift / (1 - set.Gift);
                AccountRecord ac = new AccountRecord
                {
                    Investor = investor(),
                    Manager = manager(),
                    Summary = investor() + manager()
                };
                return ac;
            }
            /// <summary>
            /// Если ничего не делалось в расчетные периоды
            /// </summary>
            /// <param name="Performance"></param>
            /// <param name="Gift"></param>
            /// <param name="N"></param>
            /// <returns></returns>
            public static AccountRecord Set2(PammSet set)
            {
                Func<double> summary = () => Pow(set.Perfomance + 1, set.N);
                Func<double> manager = () => (summary() - 1) * set.Gift;
                double s = summary();
                double d = manager();
                AccountRecord ac = new AccountRecord
                {
                    Summary = summary(),
                    Manager = manager(),
                    Investor = summary() - manager()
                };
                return ac;
            }
            /// <summary>
            /// Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их
            /// </summary>
            /// <param name="Performance"></param>
            /// <param name="Gift"></param>
            /// <param name="N"></param>
            /// <returns></returns>
            public static AccountRecord Set3(PammSet set)
            {
                Func<double> summary = () => Pow(set.Perfomance + 1, set.N);
                Func<double> investor = () => Pow(1 + (set.Perfomance) * (1 - set.Gift), set.N);
                return new AccountRecord
                {
                    Summary = summary(),
                    Investor = investor(),
                    Manager = summary() - investor()
                };
            }
            /// <summary>
            /// Сопостовление: описание - функция расчета
            /// </summary>
            public static readonly Dictionary<string, Func<PammSet, AccountRecord>> DescrToAccountRecord = new Dictionary<string, Func<PammSet, AccountRecord>>
            {
                {"Если управляющий снимает деньги в конце каждого расчетного периода (1)", Set1 },
                {"Если ничего не делалось в расчетные периоды (2)", Set2 },
                {"Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их (3)", Set3 },
            };
        }
        class Program
        {
            static void Main(string[] args)
            {
                var _ = args.Select((a) => double.Parse(a)).Take(3).ToList();
                PammSet pamm = new PammSet
                {
                    Perfomance = _[0]/100.0,
                    Gift = _[1]/100.0,
                    N = (int)_[2]
                };
                Console.WriteLine($"Данные для инвестиционного счета со следующими характеристиками:\n\n{pamm}\n");
                Console.WriteLine("Конечный результат для Инвестора, Управляющего и Суммарно:\n");
                Func<KeyValuePair<string, Func<PammSet, AccountRecord>>, string> f = (kvp) => kvp.Key + ".\n" + kvp.Value(pamm).ToString() + "\n";
                Enumerable.Select(DescrToAccountRecord, f).Map((s) => Console.WriteLine(s));
                Console.WriteLine(pamm);
                Func<int, PammSet> toPamm = (n) => new PammSet { Perfomance = pamm.Perfomance, Gift = pamm.Gift, N = n };
                Func<PammSet, IEnumerable<AccountRecord>> toAccs = (n) => DescrToAccountRecord.Select((s) => s.Value(n));
                var accounts = Enumerable.Repeat(1, pamm.N).Select((a, b) => a + b).Select(toPamm).Select(toAccs);
                Func<AccountRecord, string> toString = (ar) => ar.Investor.ToString("F2") + "\t" +
                                                               ar.Manager.ToString("F2") + "\t" +
                                                               ar.Summary.ToString("F2") + "\t";
                int period = default;
                accounts.Map
                (
                    (en) => 
                    {
                        Console.Write($"{++period}:\t");
                        en.Map((ar) =>
                            Console.Write(toString(ar)));
                        Console.WriteLine();
                    }
                );
            }
        }
    }
    

    もちろん、FPはかなり曲がっています(曲がったFPコーダーによって、不完全なFP言語C#で書かれたため)。しかし、目標は、現代のOOPジャーゴンで、FPでもそれができることを示すことなのです。もちろん、F#やHaskellのような限定的なものではありませんが、FPスタイルで書くことは誰も禁じていません。不変性、高階関数、クロージャ、マッピングなどを利用する。- 全部やってもいいんですよ。しかし、これではなぜかコードが完璧にならない。

    s.w.全体のコード構成は意図的にオリジナルを模倣しています。いい意味でFPとは全く違うはずなのに、複雑なオブジェクトやMapを模したフォアグラウンドがなく、作者は自分のできる範囲で絵を描いている。

     
    Vasiliy Sokolov:

    お願いします。C#をFPスタイルで。

    習慣や構文の知識の問題であることは理解していますが、原作者であるにもかかわらず、コードに入り込むのに非常に苦労しています。

    トレーディング、自動売買システム、ストラテジーテストに関するフォーラム

    OOPに関する興味深い意見

    fxsaber, 2021.01.29 13:39

    些細なことです。

    #property strict
    #property script_show_inputs
    
    input double inPerformance = 100; // Сколько процентов дает система за период
    input double inGift = 50;         // Награда управляющего в процентах (< 100)
    input int inN = 12;               // Сколько расчетных периодов
    
    struct BASE
    {
      double Investor;
      double Manager;
      double Summary;
    
      // Если управляющий снимает деньги в конце каждого расчетного периода.
      void Set1( const double Performance, const double Gift, const int N )
      {
        this.Investor = ::MathPow(1 + (Performance - 1)* (1 - Gift), N);
        this.Manager = (this.Investor - 1) * Gift / (1 - Gift);
        this.Summary = this.Investor + this.Manager;
    
        return;
      }
    
      // Если ничего не делалось в расчетные периоды.
      void Set2( const double Performance, const double Gift, const int N )
      {
        this.Summary = ::MathPow(Performance, N);
        this.Manager = (this.Summary - 1) * Gift;
        this.Investor = this.Summary - this.Manager;
    
        return;
      }
    
      // Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их.
      void Set3( const double Performance, const double Gift, const int N )
      {
        this.Summary = ::MathPow(Performance, N);
        this.Investor = ::MathPow(1 + (Performance - 1)* (1 - Gift), N);
        this.Manager = this.Summary - this.Investor;
    
        return;
      }
    
      void operator *=( const double Deposit = 1 )
      {
        this.Investor *= Deposit;
        this.Manager *= Deposit;
        this.Summary *= Deposit;
    
        return;
      }
    #define  TOSTRING(A) #A + " = " + ::DoubleToString(A, 4) + " "
      string ToString( const bool FlagName = true ) const
      {
        return(FlagName ? TOSTRING(Investor) + TOSTRING(Manager) + TOSTRING(Summary)
                        : ::StringFormat("||%-12.4f||%-12.4f||%-12.4f||", this.Investor, this.Manager, this.Summary));
      }
    #undef  TOSTRING
    
    };
    
    struct PAMM
    {
      double Performance; // Доходность за расчетный период
      double Gift;        // Вознагражение управляющего.
      int N;              // Сколько расчетных периодов.
    
      BASE Base1; // Если управляющий снимает деньги в конце каждого расчетного периода.
      BASE Base2; // Если ничего не делалось в расчетные периоды.
      BASE Base3; // Если управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их.
    
      void Set( const double dPerformance, const double dGift, const int iN )
      {
        this.Performance = dPerformance;
        this.Gift = dGift;
        this.N = iN;
    
        this.Base1.Set1(1 + this.Performance / 100, this.Gift / 100, this.N);
        this.Base2.Set2(1 + this.Performance / 100, this.Gift / 100, this.N);
        this.Base3.Set3(1 + this.Performance / 100, this.Gift / 100, this.N);
      }
    
      void operator *=( const double Deposit = 1 )
      {
        this.Base1 *= Deposit;
        this.Base2 *= Deposit;
        this.Base3 *= Deposit;
    
        return;
      }
    
      string GetDescription( void ) const
      {
        return("Доходность за расчетный период " + ::DoubleToString(this.Performance, 1) + "%\n" +
               "Вознагражение управляющего " + ::DoubleToString(this.Gift, 1) + "%\n" +
               "Количество расчетных периодов. " + (string)this.N);
      }
    
      string ToString( const bool FlagName = true, const bool FlagDescription = true ) const
      {
        return(FlagDescription ? "Данные для инвестиционного счета со следующими характеристиками:\n\n" + this.GetDescription() +
                                 "\n\nКонечный результат для Инвестора, Управляющего и Суммарно:"
                                 "\n\nЕсли управляющий снимает деньги в конце каждого расчетного периода (1).\n" + this.Base1.ToString(FlagName) +
                                 "\n\nЕсли ничего не делалось в расчетные периоды (2).\n" + this.Base2.ToString(FlagName) +
                                 "\n\nЕсли управляющий снимает деньги в конце каждого расчетного периода и реинвестирует их (3).\n" + this.Base3.ToString(FlagName)
                               : this.Base1.ToString(FlagName) + this.Base2.ToString(FlagName) + this.Base3.ToString(FlagName));
      }
    };
    
    void OnStart()
    {
      PAMM Pamm;
    
      Pamm.Set(inPerformance, inGift, inN);
    
      Print(Pamm.ToString());
    
      string Str = Pamm.GetDescription() + "\n   ";
    
      for (int i = 1; i <= 3; i++ )
        Str += ::StringFormat("||%-12s||%-12s||%-12s||", "Investor" + (string)i, "Manager" + (string)i, "Summary" + (string)i);
    
      for (int i = 1; i <= inN; i++ )
      {
        Pamm.Set(inPerformance, inGift, i);
    
        Str += StringFormat("\n%-2d:", i) + Pamm.ToString(false, false);
      }
    
      Print(Str);
    }

    技術的にはOOPなんでしょうけど。でも、一番原始的なところ。それがないとできないんですけどね。可能性、間抜けな脳内リア充とリベンジこれ。