SyntheticsSource

0 Views
0 Downloads
0 Favorites
SyntheticsSource
#property indicator_separate_window 
#property indicator_buffers 10
#property indicator_plots   10

#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue

#property indicator_type2   DRAW_LINE
#property indicator_color2  clrBlack

#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed

#property indicator_type4   DRAW_LINE
#property indicator_color4  clrSilver

#property indicator_type5   DRAW_LINE
#property indicator_color5  clrLime

#property indicator_type6   DRAW_LINE
#property indicator_color6  clrMagenta

#property indicator_type7   DRAW_LINE
#property indicator_color7  clrOrange

#property indicator_type8   DRAW_LINE
#property indicator_color8  clrDeepSkyBlue

#property indicator_type9   DRAW_LINE
#property indicator_color9  clrAqua

#property indicator_type10  DRAW_LINE
#property indicator_color10 clrPurple

#include <Math/Alglib/alglib.mqh>
#include <AIV/Charts.mqh>

enum eMatrices 
{
    Logs,
    Limits
};

input int InpVector = 0;
input int InpFrame = 200;
input int InpDepth = 500;
input int InpForward = 0;
input int InpMaPeriod = 1;
input ENUM_TIMEFRAMES InpTimePeriod = PERIOD_CURRENT;
input bool InpSynthetics = true;
input eMatrices InpPrices = Logs;
input string InpSymbols = "AUDUSD,EURUSD,USDCHF,USDJPY,USDCAD";
input string InpMagic = "SNTS";

struct SGenerics
{
    double S0[], S1[], S2[], S3[], S4[], S5[], S6[], S7[], S8[], S9[], iVars[], iSigns[];
};

SGenerics iG;

int iOrder;
int iFrame; 
int iLines;
int iUpdate;
int iChartIndex;
CMatrixDouble iVectors;
CMatrixDouble iMatrices;
SSeries iSeries[];
SSeries iCharts[];

/**
 * Clean up and initialize
 */
int OnInit()
{
    Setup();
    HidePanel(InpMagic);
    EventSetMillisecondTimer(500);
    
    return 0;
}

/**
 * Preload quotes from missing charts by timer
 */
void OnTimer()
{
    int bars = getBars(iSeries, InpTimePeriod, iOrder);

    if (bars == 0)
    {
        return;
    }

    // Do not try to display more bars thanavailable in history

    if (iChartIndex > bars)
    {
        iChartIndex = MathMax(MathMin(bars - 2, iChartIndex), 0);
    }

    // Display history

    while (iChartIndex > 0)
    {
        if (Calculate(iChartIndex) < 1)
        {
            return;
        }
        
        iChartIndex--;
    }

    // If most recent bars has been drawn, end up with history

    if (Calculate(0) > 0)
    {
        ChartRedraw(ChartID());
        EventKillTimer();
        iUpdate = 1;
    }
}

/**
 * Call calculation only for current bar
 * Bars needs to be loaded by timer
 * It was made to update indicator even on a dead market without new ticks
 */
int OnCalculate(
    const int bars,
    const int counted,
    const int start,
    const double &price[])
{
    if (isNewPeriod())
    {
        iUpdate = 1;
    }

    if (iChartIndex < 1 && iUpdate == 1)
    {
        if (Calculate(0) > 0)
        {
            iUpdate = 0;
        }
    }

    return bars;
}

/**
 * Create indicator buffers and set chart properties
 */
void Setup()
{
    // Get list of currency pairs from provided string

    iUpdate = 0;
    iOrder = listPairs(iSeries, InpSymbols);
    iLines = InpSynthetics ? 1 : iOrder;
    
    // If user chose to show synthetics only, leave only one buffer
    
    if (iLines > 0) { SetIndexBuffer(0, iG.S0, INDICATOR_DATA); ArraySetAsSeries(iG.S0, true); ArrayInitialize(iG.S0, 0); }
    if (iLines > 1) { SetIndexBuffer(1, iG.S1, INDICATOR_DATA); ArraySetAsSeries(iG.S1, true); ArrayInitialize(iG.S1, 0); }
    if (iLines > 2) { SetIndexBuffer(2, iG.S2, INDICATOR_DATA); ArraySetAsSeries(iG.S2, true); ArrayInitialize(iG.S2, 0); }
    if (iLines > 3) { SetIndexBuffer(3, iG.S3, INDICATOR_DATA); ArraySetAsSeries(iG.S3, true); ArrayInitialize(iG.S3, 0); }
    if (iLines > 4) { SetIndexBuffer(4, iG.S4, INDICATOR_DATA); ArraySetAsSeries(iG.S4, true); ArrayInitialize(iG.S4, 0); }
    if (iLines > 5) { SetIndexBuffer(5, iG.S5, INDICATOR_DATA); ArraySetAsSeries(iG.S5, true); ArrayInitialize(iG.S5, 0); }
    if (iLines > 6) { SetIndexBuffer(6, iG.S6, INDICATOR_DATA); ArraySetAsSeries(iG.S6, true); ArrayInitialize(iG.S6, 0); }
    if (iLines > 7) { SetIndexBuffer(7, iG.S7, INDICATOR_DATA); ArraySetAsSeries(iG.S7, true); ArrayInitialize(iG.S7, 0); }
    if (iLines > 8) { SetIndexBuffer(8, iG.S8, INDICATOR_DATA); ArraySetAsSeries(iG.S8, true); ArrayInitialize(iG.S8, 0); }
    if (iLines > 9) { SetIndexBuffer(9, iG.S9, INDICATOR_DATA); ArraySetAsSeries(iG.S9, true); ArrayInitialize(iG.S9, 0); }

    for (int k = 0; k < 10; k++)
    {
        if (k < iLines) 
        {
            PlotIndexSetInteger(k, PLOT_DRAW_TYPE, DRAW_LINE);
            PlotIndexSetString(k, PLOT_LABEL, iSeries[k].mSymbol.mName); 
            continue;
        }

        PlotIndexSetInteger(k, PLOT_DRAW_TYPE, DRAW_NONE);
    }

    IndicatorSetInteger(INDICATOR_DIGITS, 5);
    IndicatorSetString(INDICATOR_SHORTNAME, InpMagic);

    // Initialize array of structures with symbol names and prices

    iFrame = InpFrame;
    iChartIndex = InpDepth;
    
    setupSeries(iSeries, iOrder, iFrame);
    setupSeries(iCharts, iOrder, iFrame);

    iVectors.Resize(iOrder, iOrder);
    iMatrices.Resize(iFrame, iOrder);
    
    ArrayResize(iG.iVars, iOrder);
    ArrayResize(iG.iSigns, iOrder);

    ZeroMemory(iG.iVars);
    ZeroMemory(iG.iSigns);
    
    GlobalVariablesDeleteAll(InpMagic);
}

/**
 * Calculate value for synthetics for selected position (bar)
 * User chooses InpDepth bars in params, so this method will be executed InpDepth times
 * Every call of this method is copying InpFrame bars and compute PCA based on it
 */
int Calculate(int position)
{
    int sign = 1;
    int result = 0;
    int last = iFrame - 1;

    // Synchronize prices by time, preferably, only for intraday time frames, where bars may be missing

    int bars = synchronize(iSeries, InpTimePeriod, iOrder, iFrame, MathMax(position, 1), false);

    if (bars < 1)
    {
        return 0;
    }

    // Normalize prices to prevent significant bias between prices, e.g. between EURUSD and USDJPY

    switch (InpPrices)
    {
        case Logs : getLogMatrix(iSeries, iCharts, iOrder, iFrame); break;
        case Limits : getLimitMatrix(iSeries, iCharts, iOrder, iFrame); break;
    }

    // Copy internal structure to alglib container

    for (int k = 0; k < iOrder; k++)
    {
        for (int n = 0; n < iFrame; n++)
        {
            iMatrices[n].Set(k, iCharts[k].mPoints[n].mPoint);
        }
    }

    double synthetics = 0;

    // If current bar is equal to InpForward, then draw line that shows OOS period

    if (position == InpForward)
    {
        datetime dates[];
        CopyTime(Symbol(), InpTimePeriod, position, 1, dates);
        SetLine(InpMagic, dates[0]);
    }

    // If current bar is older then InpForward, compute PCA vectors, otherwise, use previously calculated

    if (position > InpForward - 1)
    {
        CAlglib::PCABuildBasis(iMatrices, iFrame, iOrder, result, iG.iVars, iVectors);
        
        // PCA does't pay attention to resulting sign of the vector
        // Thus, to prevent false sign changes, calculate most probable sign, based on previous values
        
        double plus = 0;
        double minus = 0;
        
        for (int k = 0; k < iOrder; k++) 
        {
            plus += MathAbs(iVectors[k][InpVector] + iG.iSigns[k]);
            minus += MathAbs(iVectors[k][InpVector] - iG.iSigns[k]);
        }

        sign = Inverse(iVectors, iG.iSigns, iOrder) || minus > plus ? -1 : 1;
    }
    else
    {
        // If this is first PCA value, think that sign is correct and leave it as is
    
        sign = 1;
    }

    // Update PCA vectors with correct sign

    for (int k = 0; k < iOrder; k++) 
    {
        iVectors[k].Set(InpVector, iVectors[k][InpVector] * sign);
        synthetics += iCharts[k].mPoints[last].mPoint * iVectors[k][InpVector];
        iG.iSigns[k] = iVectors[k][InpVector];
    }

    // Show vector values for each currency on the chart, until OOS period

    if (position > InpForward - 1)
    {
        ShowVectorCustom(iSeries, iVectors, InpVector, iOrder, InpMagic);
    }

    // Show either synthetics or list of separate, normalized and smoothed by MA, currency pairs

    if (InpSynthetics)
    {
        iG.S0[position] = EMA(synthetics, iG.S0[position + 1], InpMaPeriod);
    }
    else
    {
        if (iLines > 0) iG.S0[position] = EMA(iCharts[0].mPoints[last].mPoint, iG.S0[position + 1], InpMaPeriod);
        if (iLines > 1) iG.S1[position] = EMA(iCharts[1].mPoints[last].mPoint, iG.S1[position + 1], InpMaPeriod);
        if (iLines > 2) iG.S2[position] = EMA(iCharts[2].mPoints[last].mPoint, iG.S2[position + 1], InpMaPeriod);
        if (iLines > 3) iG.S3[position] = EMA(iCharts[3].mPoints[last].mPoint, iG.S3[position + 1], InpMaPeriod);
        if (iLines > 4) iG.S4[position] = EMA(iCharts[4].mPoints[last].mPoint, iG.S4[position + 1], InpMaPeriod);
        if (iLines > 5) iG.S5[position] = EMA(iCharts[5].mPoints[last].mPoint, iG.S5[position + 1], InpMaPeriod);
        if (iLines > 6) iG.S6[position] = EMA(iCharts[6].mPoints[last].mPoint, iG.S6[position + 1], InpMaPeriod);
        if (iLines > 7) iG.S7[position] = EMA(iCharts[7].mPoints[last].mPoint, iG.S7[position + 1], InpMaPeriod);
        if (iLines > 8) iG.S8[position] = EMA(iCharts[8].mPoints[last].mPoint, iG.S8[position + 1], InpMaPeriod);
        if (iLines > 9) iG.S9[position] = EMA(iCharts[9].mPoints[last].mPoint, iG.S9[position + 1], InpMaPeriod);
    }

    // Set global values that can be used by EA

    for (int k = 0; k < iOrder; k++)
    {
        GlobalVariableSet(InpMagic + ":" + "V" + ":" + iSeries[k].mSymbol.mName, iVectors[k][InpVector]);
        GlobalVariableSet(InpMagic + ":" + "S" + ":" + iSeries[k].mSymbol.mName, iSeries[k].mPoints[last].mPoint / SymbolInfoDouble(iSeries[k].mSymbol.mName, SYMBOL_POINT));
    }
    
    return 1;
}

/**
 * Display vector values (coefficients) at the top right corner
 */
void ShowVectorCustom(SSeries& series[], CMatrixDouble& vectors, const int index, const int order, const string name)
{
    double max = 0;    
    datetime times[];
    int X = 10, Y = 5, size = 8, offset = int(ChartGetDouble(0, CHART_SHIFT_SIZE) * ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) / 100);

    // Choose min and max to scale appropriately

    for (int k = 0; k < order; k++) 
    {
        max = MathAbs(vectors[k][index]) > max ? MathAbs(vectors[k][index]) : max;
    }

    int maxPixel = offset - 155;

    // Draw each vector

    for (int k = 0; k < order; k++) 
    {
        string alignment = (vectors[k][index] >= 0 ? " " : "") + DoubleToString(vectors[k][index], _Digits);
        string label = StringFormat("%s  | %s", series[k].mSymbol.mName, alignment);
        int level = int(MathCeil(MathAbs(vectors[k][index]) * maxPixel / (max == 0 ? 1 : max)));

        ShowItem(name, OBJ_LABEL, name + series[k].mSymbol.mName + "A", label, X, Y, size, clrBlack);
        ShowItem(name, OBJ_RECTANGLE_LABEL, name + series[k].mSymbol.mName + "B", series[k].mSymbol.mName, X + level + 140, Y + 2, size, vectors[k][index] > 0 ? clrDeepSkyBlue : clrTomato, level, 10);
        
        Y += size * 2;
    }
}

/**
 * Check if sign accidentally changed and needs to be inverted after previous PCA calculation
 */
bool Inverse(CMatrixDouble &vectors, double &signs[], int order)
{
    for (int k = 0; k < order; k++)
    {
        bool C1 = MathMax(vectors[k][InpVector], 0) > 0 && MathMax(signs[k], 0) > 0;
        bool C2 = MathMin(vectors[k][InpVector], 0) < 0 && MathMin(signs[k], 0) < 0;
    
        if (C1 || C2)
        {
            return false;
        }
    }

    return true;
}

Comments