Interessante presa di posizione sull'OLP - pagina 11

 
Georgiy Merts:

"Apoteosi" considero un'espressione di fxsaber, sulla quale lui stesso non poteva dire come funziona, affermando semplicemente che "il codice è stato ripetutamente testato, e funziona". Questo, secondo me, non dovrebbe essere il caso:

Questo codice controlla seun ordine otfFilingType può essereeseguito, e lo restituisce se è disponibile su strSymbol, altrimenti è corretto.


Non ho assolutamente idea di come funzioni. E fare affidamento solo sull'autorità di fxsaber.

Forse qualcuno può spiegare?

Per capirlo, basta smontare questa ingombrante espressione di ritorno nei suoi componenti.

 
Igor Makanu:

Il tuo esempio di "il fine giustifica i mezzi"

comunque questo codice è proprio come degli spaghetti, ma considerando che è probabilmente l'autore più utile in kodobase in questo momento e io stesso uso i suoi codici così come sono, lasciamo che qualcun altro critichi

 
Andrei Trukhanovich:

comunque questo codice è proprio come degli spaghetti, ma dato che è probabilmente l'autore più utile in kodobase in questo momento e io stesso uso i suoi codici così come sono, lasciamo che qualcun altro critichi

non si tratta di critica, sto cercando di capire cosa può fare.... Ma come mostra l'intervista, e come ha ammesso lo stesso@fxsaber- nient'altro che mal di testa


SZZ: è molto probabile che la variante iniziale del piccolo mostro abbia un aspetto più chiaro ;)

 
Igor Makanu:

ZS: alta probabilità che la versione originale del mostriciattolo sembrasse più evidente ;)

Preso da ZIP (contiene la primissima versione).

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

Ma in seguito c'era bisogno di sempre più funzionalità, e il codice stava lentamente diventando più corposo. Era diverso con la funzione Filling. Non c'era niente di definito, è stato scritto in una volta sola.

 
Vasiliy Sokolov:

Solo il compilatore sa esattamente cosa farà. I compilatori moderni hanno un'euristica sbalorditiva. Si adattano al codificatore medio e sanno già meglio di cosa ha bisogno. La cosa migliore che un compilatore può fare è scrivere codice semplice e diretto con funzioni brevi. È più facile ed efficiente per il compilatore analizzare il grafo del codice sorgente composto da molti nodi di funzione per costruire il programma risultante. Avrà solo un effetto positivo sulla produttività, poiché le funzioni necessarie saranno bloccate in tutti i posti giusti.

Hai assolutamente ragione.

Se stiamo parlando di MQL5, potete dimenticare i metodi di "ottimizzazione" di 10-20-30 anni fa. Dovete scrivere il codice più leggibile. È così, e non la roba da hacker e la pura furbizia.

Perché?

Poiché il compilatore passa attraverso 5-10 cicli di riordino del codice, è incredibilmente chiaro e conciso, per non parlare dell'uso di decine di modelli di ottimizzazione.

Il compilatore MQL5 è divertito dai tentativi umani di fare +2% di velocità.


Se vi interessa, guardate come calcoli SQRT + matematici vanno senza ramificazioni e un comando (128 bit di dati) calcola due radici in una volta

Questo codice si trasforma nel seguente codice assembler 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
Questa è un'opera d'arte in realtà. 8 radici sono state calcolate in 4 chiamate del comando assembler. Due numeri doppi sono stati calcolati in una chiamata.

  • Quando si lavora attraverso un array, tutto è normale, con controlli, ramificazioni e perdite sulla conversione di indici doppi -> interi

  • Quando si lavora con gli array in questo esempio c'è una costante mescolanza FPU/ALU che è molto negativa per le prestazioni

  • L'ottimizzazione dell'accesso dinamico all'array è grande - oltre ogni lode. Ma mischiare operazioni FPU/ALU + doppio -> intero + ramificazione fa perdere tempo

  • La conclusione generale: la matematica ha vinto in MQL5 grazie alla perfetta ottimizzazione. Qui non sono gli array a perdere, ma vince la matematica.


     
    Georgiy Merts:

    Perché?

    Al contrario, è molto più facile con due "se" che con l'operatore "o".

    È più facile tracciare prima una condizione, e lasciare la funzione se è vera, e poi controllare l'altra condizione, e lasciare anche questa se è vera, che indovinare il risultato di una condizione complessa con "o" logico (che può essere facilmente confuso con "e"), e tracciare entrambe le opzioni di ritorno.

    È piuttosto divertente leggere qui sotto che "la giustificazione per tale è il debugging", perché significa che tale codice è molto più comprensibile (altrimenti perché è in debugging?).

    "Apoteosi" considero un'espressione di fxsaber, sulla quale lui stesso non poteva dire come funziona, affermando semplicemente che "il codice è stato ripetutamente testato, e funziona". Questo, secondo me, non dovrebbe essere il caso:

    Questo codice controlla seun ordine otfFilingType può essereeseguito, e lo restituisce se è disponibile su strSymbol, altrimenti è corretto.


    Non ho assolutamente idea di come funzioni. E fare affidamento solo sull'autorità di fxsaber.

    Forsequalcuno può spiegare?

    Una volta mi sono seduto e l'ho smontato passo dopo passo, sembra che abbia avuto bisogno di carta e penna)

    Perché questo parsing è stato utile - ho capito che in caso di cambiamento della struttura di enum tutto sarà rotto) dato che vengono usate relazioni intere di valori enum (che secondo l'idea stessa di enumerazioni dovrebbero essere incapsulate). Io stesso cerco di evitarlo, al massimo il rapporto più-meno. Per coloro che hanno a che fare con WinAPI, è probabilmente familiare.

     
    Andrey Khatimlianskii:

    Per capire, basta smontare quell'espressione ingombrante di retournee.

    Sì, è quello che ho cercato di fare. Ma non c'era abbastanza motivazione per smontarlo...

     
    Renat Fatkhullin:

    Assolutamente giusto.

    Se stiamo parlando di MQL5, potete dimenticare i metodi di "ottimizzazione" di 10-20-30 anni fa. Dovete scrivere il codice più leggibile. È così, e non la roba da hacker e la pura furbizia.

    Esattamente. Sono arrivato a questa conclusione molto tempo fa. Ma non perché penso che il compilatore lo renderà migliore. Ma perché la principale fonte di problemi nel codice è l'essere umano stesso, il che significa che si dovrebbe scrivere il codice in modo da renderlo il più semplice e trasparente possibile.

    Quando non può essere "trasparente", bisogna scrivere commenti dettagliati sul perché è così e non così.

    E il compilatore... Anche se non lo fa in modo abbastanza efficiente, è meno un problema che un potenziale bug dovuto al fatto che non si tiene conto di alcuni aspetti del programma.

     
    fxsaber:

    Il codice è molto semplice e breve(descrizione). Se lo scrivi su FP, sarebbe interessante confrontarlo.

    Per favore. C# in stile 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 è abbastanza storto naturalmente (poiché è stato scritto da un codificatore FP storto, nel linguaggio FP incompleto C#).Ma l'obiettivo è mostrare che sul moderno gergo OOP si può fare anche in FP. Certo, è limitato, non è F# o Haskell, ma nessuno vieta di scrivere in stile FP. Usare l'immutabilità, funzioni di ordine superiore, chiusure, mappature, ecc. - sei il benvenuto a fare tutto. Ma questo non rende il codice perfetto per qualche motivo.

    s.w. La struttura generale del codice imita deliberatamente l'originale. Anche se in senso buono dovrebbe essere molto diverso in FP, senza oggetti complessi e foreach che imitano Map, ma l'artista ha dipinto come poteva.

     
    Vasiliy Sokolov:

    Per favore. C# in stile FP:

    Capisco che è una questione di abitudine e di conoscenza della sintassi, ma ho molta difficoltà ad entrare nel codice anche se sono l'autore originale.

    Forum sul trading, sistemi di trading automatico e test di strategia

    Interessante opinione su OOP

    fxsaber, 2021.01.29 13:39

    Un'inezia.

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

    Tecnicamente, è probabilmente un OOP. Ma la parte più primitiva. Non potrei farlo senza, però. Possibilmente, stupido riarrangiamento del cervello e avvincente questo.

    Motivazione: