Un point de vue intéressant sur l'OLP - page 11

 
Georgiy Merts:

"Apothéose" Je considère une expression de fxsaber, à propos de laquelle il n'a pas pu lui-même dire comment elle fonctionne, déclarant simplement que "le code a été testé à plusieurs reprises, et il fonctionne". À mon avis, cela ne devrait pas être le cas :

Ce code vérifie siun ordre otfFilingType peut êtreexécuté, et le renvoie s'il est disponible sur strSymbol, sinon il est correct.


Je n'ai absolument aucune idée de comment cela fonctionne. Et ne vous fiez qu'à l'autorité de Fxsaber.

Peut-être que quelqu'un peut expliquer ?

Pour le comprendre, il suffit de décomposer cette expression de retour encombrante en ses composantes.

 
Igor Makanu:

Votre exemple de "la fin justifie les moyens".

d'ailleurs ce code ressemble à du spaghetti, mais vu que c'est probablement l'auteur le plus utile de kodobase en ce moment et que j'utilise moi-même ses codes tels quels, laissons quelqu'un d'autre critiquer

 
Andrei Trukhanovich:

d'ailleurs ce code ressemble à du spaghetti, mais étant donné que c'est probablement l'auteur le plus utile de kodobase en ce moment et que j'utilise moi-même ses codes tels quels, laissons quelqu'un d'autre critiquer

il ne s'agit pas de critiquer, j'essaie de comprendre ce qu'il peut faire.... Mais comme le montre l'interview, et@fxsaber lui-même l'a admis - rien que des maux de tête.


SZZ : il est fort probable que la variante initiale du petit monstre soit plus claire ;)

 
Igor Makanu:

ZS : forte probabilité que la version originale du petit monstre soit plus évidente ;)

Je l'ai pris de ZIP (contient la toute première version).

  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__
           );
  }

Mais après cela, de plus en plus de fonctionnalités étaient nécessaires, et le code s'étoffait lentement. C'était différent avec la fonction de remplissage. Rien n'a été étoffé là-bas, tout a été écrit d'un coup.

 
Vasiliy Sokolov:

Seul le compilateur sait exactement ce qu'il va faire. Les compilateurs modernes ont une euristique stupéfiante. Ils s'adaptent au codeur moyen et savent déjà mieux ce dont il a besoin. La meilleure chose qu'un compilateur puisse faire est d'écrire un code simple et direct avec des fonctions courtes. Il est plus facile et plus efficace pour le compilateur d'analyser le graphe du code source composé de nombreux nœuds de fonction pour construire le programme résultant. Cela n'aura qu'un effet positif sur la productivité puisque les fonctions nécessaires seront verrouillées à tous les bons endroits.

Vous avez tout à fait raison.

Si nous parlons de MQL5, vous pouvez oublier les méthodes d'"optimisation" d'il y a 10-20-30 ans. Vous devez écrire le code le plus lisible. C'est ça, et pas les trucs de hacker et de pur esprit d'initiative.

Pourquoi ?

Comme le compilateur passe par 5 à 10 cycles de réorganisation du code, il est étonnamment clair et concis, sans parler de l'utilisation de dizaines de schémas d'optimisation.

Le compilateur MQL5 s'amuse des tentatives humaines de gagner +2% de vitesse.


Si cela vous intéresse, regardez comment calculs mathématiques SQRT + se font sans branchement et une commande (128 bits de données) calcule deux racines à la fois

Ce code se transforme en code assembleur SSE suivant :

         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
C'est une œuvre d'art en fait. 8 racines ont été calculées en 4 appels de la commande assembleur. Deux numéros doubles ont été calculés en un seul appel.

  • Lorsque l'on travaille dans un tableau, tout est normal, avec des contrôles, des branchements et des pertes sur la conversion d'index double -> entier.

  • En travaillant avec des tableaux dans cet exemple, il y a un mélange constant de FPU/ALU qui est très mauvais pour les performances.

  • L'optimisation de l'accès dynamique aux tableaux est formidable - au-delà des éloges. Mais mélanger les opérations FPU/ALU + double -> entier + branchement fait perdre du temps.

  • La conclusion générale : les mathématiques ont gagné dans MQL5 grâce à une optimisation parfaite. Ce ne sont pas les tableaux qui perdent ici, mais les mathématiques qui gagnent.


     
    Georgiy Merts:

    Pourquoi ça ?

    Au contraire, c'est beaucoup plus facile avec deux "si" qu'avec l'opérateur "ou".

    Il est plus facile de tracer une condition en premier, et de quitter la fonction si elle est vraie, puis de vérifier l'autre condition, et de la quitter également si elle est vraie, que de deviner le résultat d'une condition complexe avec un "ou" logique (qui peut être facilement confondu avec "et"), et de tracer les deux options de retour.

    C'est assez drôle de lire plus bas que "la justification d'un tel code est le débogage", car cela signifie qu'un tel code est beaucoup plus compréhensible (sinon pourquoi est-il en débogage ?).

    "Apothéose" Je considère une expression de fxsaber, à propos de laquelle il n'a pas pu lui-même dire comment elle fonctionne, déclarant simplement que "le code a été testé à plusieurs reprises, et il fonctionne". À mon avis, cela ne devrait pas être le cas :

    Ce code vérifie siun ordre otfFilingType peut êtreexécuté, et le renvoie s'il est disponible sur strSymbol, sinon il est correct.


    Je n'ai absolument aucune idée de comment cela fonctionne. Et ne vous fiez qu'à l'autorité de Fxsaber.

    Peut-être quequelqu'un peut expliquer ?

    Je me suis assis une fois et je l'ai démonté étape par étape, il semble que j'ai eu besoin d'un stylo et de papier).

    J'ai compris qu'en cas de changement de la structure de l'énumération, tout serait cassé, puisque des relations entières entre les valeurs de l'énumération sont utilisées (qui, selon l'idée même des énumérations, devraient être encapsulées). J'essaie moi-même d'éviter cela, tout au plus le rapport plus-moins. Pour ceux qui s'occupent de WinAPI, elle est probablement familière.

     
    Andrey Khatimlianskii:

    Pour comprendre, il suffit de démonter cette expression difficile à manier qu'est le retournee.

    Oui, c'est ce que j'ai essayé. Mais je n'étais pas assez motivé pour le démonter...

     
    Renat Fatkhullin:

    Tout à fait exact.

    Si nous parlons de MQL5, vous pouvez oublier les méthodes d'"optimisation" d'il y a 10-20-30 ans. Vous devez écrire le code le plus lisible. C'est ça, et pas les trucs de hacker et de pur esprit d'initiative.

    Exactement. Je suis arrivé à cette conclusion il y a longtemps. Mais pas parce que je pense que le compilateur le rendra meilleur. Mais parce que la principale source de problèmes dans le code est l'être humain lui-même, ce qui signifie qu'il faut écrire le code de manière à le rendre aussi simple et transparent que possible.

    Si vous ne pouvez pas le rendre "transparent", vous devez écrire des commentaires détaillés expliquant pourquoi c'est comme ça et pas comme ça.

    Et le compilateur... Même s'il ne le fait pas assez efficacement, c'est moins un problème qu'un bug potentiel dû au fait que vous ne prenez pas en compte certains aspects du programme.

     
    fxsaber:

    Le code est très simple et court(description). Si vous l'écrivez sur FP, il serait intéressant de comparer.

    S'il vous plaît. C# dans le style 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();
                    }
                );
            }
        }
    }
    

    Le langage FP est bien sûr assez tordu (puisqu'il a été écrit par un codeur FP tordu, dans le langage FP incomplet C#), mais le but est de montrer qu'avec le jargon moderne de la POO, on peut aussi le faire en FP. Bien sûr, c'est limité, ce n'est pas F# ou Haskell, mais personne n'interdit d'écrire dans le style FP. Utilisez l'immuabilité, les fonctions d'ordre supérieur, les fermetures, les mappings, etc. - vous êtes le bienvenu pour tout faire. Mais cela ne rend pas le code parfait pour une raison quelconque.

    s.w. La structure globale du code imite délibérément l'original. Bien que dans un bon sens il devrait être tout à fait différent dans FP, sans objets complexes et foreach imitant la carte, mais l'artiste a peint comme il pouvait.

     
    Vasiliy Sokolov:

    S'il vous plaît. C# dans le style FP :

    Je comprends que c'est une question d'habitude et de connaissance de la syntaxe, mais j'ai beaucoup de mal à entrer dans le code alors que je suis l'auteur original.

    Forum sur le trading, les systèmes de trading automatisés et les tests de stratégie

    Opinion intéressante sur la POO

    fxsaber, 2021.01.29 13:39

    Une bagatelle.

    #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);
    }

    Techniquement, c'est probablement un OOP. Mais la partie la plus primitive de tout ça. Je ne pouvais pas le faire sans, cependant. Possible, réarrangement stupide du cerveau et rivetage de ceci.