Il Semplice Esempio di Creazione di un Indicatore Utilizzando la Logica Fuzzy

Максим Востров | 16 dicembre, 2021

Introduzione

L'uso di vari metodi per l'analisi dei mercati finanziari è diventato sempre più popolare tra i trader negli ultimi anni. Vorrei dare il mio contributo e mostrare come creare un buon indicatore scrivendo un paio di dozzine di righe di codice. Inoltre, ti rivelerò brevemente le basi della logica fuzzy.

Chiunque sia interessato a questo tema e voglia approfondirlo può leggere i seguenti lavori:

1.  Leonenkov А. "Fuzzy Simulation in MATLAB e fuzzyTECH" (in russo).
2.  Bocharnikov V. "Fuzzy Technology: Contesto Matematico. Pratica di Simulazione in Economia" (in russo).
3.  S.N. Sivanandam, S. Sumathi, S.N. Deepa. Introduzione alla Logica Fuzzy utilizzando MATLAB.
4.  C. Kahraman. Fuzzy Engineering Economics with Applications (studi su Fuzziness e Soft Computing ).


1. Nozioni di Base sulla Logica Fuzzy

Come spiegare alle nostre macchine di calcolo il significato di espressioni così semplici come "...un po' di più...", "...troppo veloce...", "...quasi niente..."? Infatti, è abbastanza possibile utilizzando gli elementi di teoria degli insiemi fuzzy, ovvero le cosiddette "funzioni di appartenenza". Ecco un esempio da А. Il libro di Leonenkov:

Descriviamo la funzione di appartenenza alla frase "caffè caldo": la temperatura del caffè va considerata compresa tra 0 e 100 gradi centigradi per il semplice motivo che a temperature inferiori a 0 gradi si trasformerà in ghiaccio, mentre a temperature superiori 100 gradi evaporerà. È abbastanza ovvio che una tazzina con una temperatura di 20 gradi non può essere definita calda, ovvero la funzione di appartenenza alla categoria "caldo" è pari a 0, mentre una tazzina con una temperatura di 70 gradi appartiene sicuramente alla categoria "caldo" e, quindi, il valore della funzione è uguale a 1 in questo caso.

Per quanto riguarda i valori di temperatura ​che si trovano tra questi due valori estremi, la situazione non è così definita. Alcune persone potrebbero considerare "calda" una tazza di caffè con una temperatura di 55 gradi, mentre altre potrebbero considerarla "non così calda". Questa è la "sfocatura".

Tuttavia, possiamo immaginare l'aspetto approssimativo della funzione di appartenenza: è "monotonicamente crescente":


La figura sopra mostra la funzione di appartenenza "lineare a tratti".

Pertanto, la funzione può essere definita dalla seguente espressione analitica:


Useremo tali funzioni per il nostro indicatore.


2. Funzione di Appartenenza

In un modo o nell'altro, il compito di qualsiasi indicatore tecnico è la determinazione dello stato attuale del mercato (piatto, trend rialzista, trend ribassista), nonché la generazione di segnali di ingresso/uscita dal mercato. Come si può fare questo con l'aiuto delle funzioni di appartenenza? Abbastanza facile.

Prima di tutto, dobbiamo definire le condizioni al contorno. Poniamo le seguenti condizioni al contorno: per «100% trend rialzista» sarà l'incrocio di EMA con periodo 2, basato sul prezzo tipico (H+L+C)/3 con bordo superiore Envelopes con parametri 8, 0.08, SMA , Close, mentre per «100% trend ribassista» si tratterà dell'attraversamento della stessa EMA con bordo inferiore Envelopes. Tutto ciò che si trova tra queste condizioni sarà considerato piatto. Aggiungiamo un altro envelope con i parametri 32, 0.15, SMA, Close. 

Di conseguenza, otterremo due funzioni di appartenenza identiche. Il segnale di acquisto verrà attivato quando entrambe le funzioni sono pari a 1, mentre il segnale di vendita verrà attivato quando entrambe le funzioni sono rispettivamente pari a -1. Poiché è conveniente costruire grafici con l'intervallo da -1 a 1, il grafico risultante sarà ottenuto come media aritmetica di due funzioni F(x)= (f1(x)+f2(x))/2.

Ecco come appare sul grafico:


In questo caso la funzione di appartenenza avrà la seguente rappresentazione grafica:


Analiticamente può essere scritto come segue:

,

dove a e b sono rispettivamente le linee di envelope superiore e inferiore, mentre х è un valore di EMA(2).

Con la funzione definita, possiamo ora passare alla scrittura del codice dell'indicatore.


3. Creazione del Codice del Programma

Prima di tutto, dovremmo definire cosa e come disegneremo.

I risultati dei calcoli della funzione di appartenenza verranno visualizzati come una linea rispettivamente rossa e blu.

La media aritmetica verrà visualizzata come un istogramma dalla linea dello zero e dipinta in uno dei cinque colori a seconda del valore della funzione risultante:

Per questo verrà utilizzato lo stile di disegno DRAW_COLOR_HISTOGRAM.

Tracciamo rettangoli blu e rossi come segnali di acquisto/uscita sulle barre dell'istogramma, i cui valori sono uguali a 1 o -1.

Ora è il momento di eseguire MetaEditor e iniziare. Nuovo->Indicatore personalizzato->Avanti... Compila il campo "Parametri":


 Crea i buffer:


Dopo aver fatto clic sul pulsante "Fine", riceviamo un codice sorgente e iniziamo a migliorarlo.

Prima di tutto, definiamo il numero di buffer. Sette di questi sono già stati creati dalla procedura guidata (5 per i dati, 2 per il colore). Noi abbiamo bisogno di altri 5.

#property indicator_minimum -1.4 // Setting fractional values
#property indicator_maximum 1.4  // Expert Advisors wizard ignores fractional parts for some reason
#property indicator_buffers 12   // Changing the value from 7 to 12 (5 more buffers have been added)
Modifichiamo i parametri di input:
input string txt1="----------";
input int                  Period_Fast=8;
input ENUM_MA_METHOD        Method_Fast = MODE_SMA; /*Smoothing method*/ //moving average smoothing method 
input ENUM_APPLIED_PRICE    Price_Fast  = PRICE_CLOSE;
input double               Dev_Fast=0.08;
input string txt2="----------";
input int                  Period_Slow=32;
input ENUM_MA_METHOD        Method_Slow = MODE_SMA;
input ENUM_APPLIED_PRICE    Price_Slow  = PRICE_CLOSE;
input double               Dev_Slow=0.15;  /*Deviation parameter*/
input string txt3="----------";
input int                  Period_Signal=2;
input ENUM_MA_METHOD        Method_Signal = MODE_EMA;
input ENUM_APPLIED_PRICE    Price_Signal  = PRICE_TYPICAL;
input string txt4="----------";

I commenti che seguono la variabile dichiarata sono molto utili. Il testo dei commenti viene inserito nella finestra dei parametri dell'indicatore.

Molto utile anche la possibilità di creare liste:


Riservando le variabili per gli handle e i buffer dell'indicatore:

int Envelopes_Fast;     // Fast envelope
int Envelopes_Slow;     // Slow envelope
int MA_Signal;          // Signal line

double Env_Fast_Up[];   // Fast envelope upper border
double Env_Fast_Dn[];   // Fast envelope lower border

double Env_Slow_Up[];   // Slow envelope upper border
double Env_Slow_Dn[];   // Slow envelope lower border

double Mov_Sign[];      // Signal line

Adesso passa alla funzione OnInit().

Aggiungiamo un po' di bellezza: specifica il nome dell'indicatore e rimuovi gli zeri decimali extra:

IndicatorSetInteger(INDICATOR_DIGITS,1); // setting display accuracy, we do not need some outstanding accuracy values 
string name;                           // indicator name 
StringConcatenate(name, "FLE ( ", Period_Fast, " , ", Dev_Fast, " | ", Period_Slow, " , ", Dev_Slow, " | ", Period_Signal, " )"); 
IndicatorSetString(INDICATOR_SHORTNAME,name);

e aggiungi i buffer mancanti:

SetIndexBuffer(7,Env_Fast_Up,INDICATOR_CALCULATIONS);
SetIndexBuffer(8,Env_Fast_Dn,INDICATOR_CALCULATIONS);
SetIndexBuffer(9,Env_Slow_Up,INDICATOR_CALCULATIONS);
SetIndexBuffer(10,Env_Slow_Dn,INDICATOR_CALCULATIONS);
SetIndexBuffer(11,Mov_Sign,INDICATOR_CALCULATIONS); 

Il parametro INDICATOR_CALCULATIONS significa che i dati del buffer sono necessari solo per i calcoli intermedi. Non verrà visualizzato sul grafico.

Nota come vengono dichiarati gli indicatori con buffer di colore:

SetIndexBuffer(4,SignalBuffer1,INDICATOR_DATA);      // All indicator buffers at first 
SetIndexBuffer(5,SignalBuffer2,INDICATOR_DATA);      // as this is Color Histogram2, then it has 2 data buffers
SetIndexBuffer(6,SignalColors,INDICATOR_COLOR_INDEX);// the color buffer comes next.

Riempire gli handle:

Envelopes_Fast = iEnvelopes(NULL,0,Period_Fast,0,Method_Fast,Price_Fast,Dev_Fast);
Envelopes_Slow = iEnvelopes(NULL,0,Period_Slow,0,Method_Slow,Price_Slow,Dev_Slow);
MA_Signal      = iMA(NULL,0,Period_Signal,0,Method_Signal,Price_Signal);

Tutti i lavori con la funzione OnInit() sono finiti.

Ora creiamo la funzione che calcolerà il valore della funzione di appartenenza:

double Fuzzy(double x,double a, double c)
{
double F;
     if (a<x)          F=1;                 // 100% uptrend
else if (x<=a && x>=c)  F=(1-2*(a-x)/(a-c));// Flat
else if (x<c)           F=-1;               // 100% downtrend
return (F);
}

I preparativi sono finiti. Le variabili e i buffer vengono dichiarati, gli handle vengono assegnati.

Ora è il momento di procedere con la funzione di base OnCalculate().

Prima di tutto, scriviamo i valori degli indicatori necessari nei buffer intermedi. Usa la funzione CopyBuffer():

CopyBuffer(Envelopes_Fast,  // Indicator handle
           UPPER_LINE,      // Indicator buffer
           0,              // The point to start 0 - from the very beginning
           rates_total,    // How many to be copied - All 
           Env_Fast_Up);   // The buffer the values are written in
// - the rest are done in a similar way
CopyBuffer(Envelopes_Fast,LOWER_LINE,0,rates_total,Env_Fast_Dn);
CopyBuffer(Envelopes_Slow,UPPER_LINE,0,rates_total,Env_Slow_Up);
CopyBuffer(Envelopes_Slow,LOWER_LINE,0,rates_total,Env_Slow_Dn);
CopyBuffer(MA_Signal,0,0,rates_total,Mov_Sign);

 Qui dobbiamo aggiungere il codice per l'ottimizzazione dei calcoli (viene eseguito il ricalcolo solo dell'ultima barra):

// declaring start variable for storing the index of the bar, recalculation of the indicator buffers will be
// carried out from.

int start;              
if (prev_calculated==0// in case no bars have been calculated
    {
    start = Period_Slow; // not all indicators have been calculated up to this value, therefore, there is no point in executing the code
    }
else start=prev_calculated-1;

for (int i=start;i<rates_total;i++)
      {
      // All remaining code will be written here
      }

Non è rimasto molto del codice.

Impostando i parametri x, a, b, eseguendo il calcolo del valore della funzione di appartenenza e scrivendolo nell'apposito buffer:
double x = Mov_Sign[i]; // Signal
// Setting the first membership function parameters:
double a1 = Env_Fast_Up[i]; // Upper border
double b1 = Env_Fast_Dn[i];
// setting the first membership function value and writing it to the buffer
Rule1Buffer[i] = Fuzzy(x,a1,b1);
// Setting the second membership function parameters:
double a2 = Env_Slow_Up[i]; // Upper border
double b2 = Env_Slow_Dn[i];
// setting the second membership function value and writing it to the buffer
Rule2Buffer[i] = Fuzzy(x,a2,b2);

Vengono costruite due linee di indicatori.

Ora calcoliamo il valore risultante.

ResultBuffer[i] = (Rule1Buffer[i]+Rule2Buffer[i])/2;

Quindi dovremmo dipingere le barre dell'istogramma con colori appropriati: poiché abbiamo cinque colori, allora ResultColors[i] può avere qualsiasi valore da 0 a 4.

In genere, il numero di colori possibili è 64. Pertanto, è una grande opportunità per applicare le proprie capacità creative.

for (int ColorIndex=0;ColorIndex<=4;ColorIndex++) 
    { 
    if (MathAbs(ResultBuffer[i])>0.2*ColorIndex && MathAbs(ResultBuffer[i])<=0.2*(ColorIndex+1)) 
        { 
        ResultColors[i] = ColorIndex; 
        break; 
        } 
    }

Quindi dovremmo disegnare i rettangoli di segnale. Useremo lo stile di disegno DRAW_COLOR_HISTOGRAM2.

Ha due buffer di dati con una barra dell'istogramma e un buffer di colore che viene costruito tra di loro.

I valori dei buffer di dati saranno sempre gli stessi: 1.1 e 1.3 per un segnale di acquisto, rispettivamente -1.1 e -1.3 per un segnale di vendita.

EMPTY_VALUE significherà l'assenza del segnale.

      if (ResultBuffer[i]==1)
        {
        SignalBuffer1[i]=1.1;
        SignalBuffer2[i]=1.3;
        SignalColors[i]=1;
        }
      else if (ResultBuffer[i]==-1)
        {
        SignalBuffer1[i]=-1.1;
        SignalBuffer2[i]=-1.3;
        SignalColors[i]=0;
        }
      else
        {
        SignalBuffer1[i]=EMPTY_VALUE;
        SignalBuffer2[i]=EMPTY_VALUE;
        SignalColors[i]=EMPTY_VALUE;
        }

Fai clic su "Compila" e voilà!



Conclusione

Cos'altro si può aggiungere? In questo articolo ho toccato l'approccio più elementare alla logica fuzzy.

C'è abbastanza spazio qui per vari esperimenti. Ad esempio, possiamo utilizzare la seguente funzione:


Penso che non sarà difficile per te scrivere l'espressione analitica per esso e trovare le condizioni adatte.

Buona fortuna!