Problem Description
1. Input Data Each currency pair 𝐶𝑖 for 𝑖=1,2,...,28 has:

Volatility 𝑣𝑖 computed as:
v_i = StdDev(p_i(t), lookback)

where:

𝑝𝑖(𝑡) : Price series of pair 𝐶𝑖, lookback: Window size for the standard deviation.
Adjacency Matrix 𝐴 to model relationships:

A_ij = exp(-((v_i - v_j)^2) / (2 * sigma^2)) for i != j
A_ii = 0

where:

𝑠𝑖𝑔𝑚𝑎: Kernel bandwidth parameter.
2. GNN Propagation Each node embedding 𝐻𝑖(𝑙) at layer 𝑙 is updated using:

H_i^(l+1) = ReLU(Sum_{j=1}^28 A_ij * H_j^(l) * W^(l))

where:

𝐴𝑖𝑗 : Adjacency matrix element,
𝑊(𝑙) : Trainable weight matrix for layer
𝑙,𝑅𝑒𝐿𝑈(𝑥) : Max(0, x).
3. Dimensionality Reduction After 𝐿 GNN layers, embeddings 𝐻𝑖 are reduced in dimensionality:

Kernel PCA Compute the kernel matrix:

K_ij = exp(-(||H_i - H_j||^2) / (2 * sigma^2))

Perform eigenvalue decomposition:

K = V * Lambda * V^T
Project data onto top-k eigenvectors:

Z_i = Sum_{j=1}^k Lambda_j * V_j

Autoencoder PCA Encoder:

Z_i = H_i * W_enc

Decoder:

H_i_hat = Z_i * W_dec

Minimize reconstruction loss:

L = Sum_{i=1}^28 ||H_i - H_i_hat||^2

4. Trading Signals
Using the reduced features 𝑍𝑖, generate trading signals 𝑠𝑖 :

s_i = 1 if Z_i1 > mu_Z + k * sigma_Z
-1 if Z_i1 < mu_Z - k * sigma_Z
0 otherwise

where:

𝑚𝑢𝑍 : Mean of 𝑍𝑖1,
𝑠𝑖𝑔𝑚𝑎𝑍 : StdDev of 𝑍𝑖1,
𝑘 : Threshold multiplier.

Puzzle Questions
Graph Construction:

How does the kernel bandwidth 𝑠𝑖𝑔𝑚𝑎 affect the sparsity of 𝐴𝑖𝑗?

A_ij -> sparse as |v_i - v_j| increases relative to sigma.

Dimensionality Reduction:

What happens if the eigenvalues 𝐿𝑎𝑚𝑏𝑑 𝑎𝑗 decay too quickly in Kernel PCA?

Z_i -> dominated by 1st few components, losing information.

Signal Sensitivity:

How does 𝑘 affect the number of Buy (𝑠𝑖=1) or Sell (𝑠𝑖=−1) trades?

Large k -> fewer trades, high confidence.
Small k -> more trades, lower confidence.

Performance: Evaluate trading performance using:

Sharpe_Ratio = Mean(Returns) / StdDev(Returns)

Code
#define PAIRS 28
#define CHUNK_SIZE 5
#define LATENT_DIM 3

// Currency pairs
string CurrencyPairs[PAIRS] = {
    "EURUSD", "GBPUSD", "USDJPY", "GBPJPY", "USDCAD", "EURAUD", "EURJPY",
    "AUDCAD", "AUDJPY", "AUDNZD", "AUDUSD", "CADJPY", "EURCAD", "EURCHF",
    "EURGBP", "EURNZD", "GBPCAD", "GBPCHF", "NZDCAD", "NZDJPY", "NZDUSD",
    "USDCHF", "CHFJPY", "AUDCHF", "GBPNZD", "NZDCHF", "CADCHF", "GBPAUD"
};

// Variables
vars volatilities[PAIRS];
vars adjacencyMatrices[PAIRS][PAIRS];
vars nodeEmbeddings[PAIRS][5];
vars kernelMatrix[PAIRS][PAIRS];
vars eigenvalues[PAIRS];
vars eigenvectors[PAIRS][PAIRS];
vars reducedMatrix[PAIRS][3];
vars cumulativeCovariance[5][5];
vars encoderWeights[5][LATENT_DIM];
vars decoderWeights[LATENT_DIM][5];
vars signals[PAIRS];
double kernelSigma = 0.5;
double thresholdMultiplier = 1.0;
double learningRate = 0.01;
int lookback = 50;
int totalSamples = 0;

// Step 1: Calculate volatilities
function calculateVolatilities() {
    for (int i = 0; i < PAIRS; i++) {
        volatilities[i] = StdDev(series(price(CurrencyPairs[i])), lookback);
    }
}

// Step 2: Construct adjacency matrix for GNN
function constructAdjacencyMatrix() {
    for (int i = 0; i < PAIRS; i++) {
        for (int j = 0; j < PAIRS; j++) {
            if (i != j) {
                adjacencyMatrices[i][j] = exp(-pow(volatilities[i] - volatilities[j], 2) / (2 * pow(kernelSigma, 2)));
            } else {
                adjacencyMatrices[i][j] = 0;
            }
        }
    }
}

// Step 3: Propagate embeddings for GNN
function propagateEmbeddings() {
    vars newEmbeddings[PAIRS][5];
    for (int i = 0; i < PAIRS; i++) {
        for (int k = 0; k < 5; k++) {
            newEmbeddings[i][k] = 0;
            for (int j = 0; j < PAIRS; j++) {
                newEmbeddings[i][k] += adjacencyMatrices[i][j] * nodeEmbeddings[j][k];
            }
        }
    }
    for (int i = 0; i < PAIRS; i++) {
        for (int k = 0; k < 5; k++) {
            nodeEmbeddings[i][k] = ReLU(newEmbeddings[i][k]);
        }
    }
}

// Step 4: Incremental PCA
function updateIncrementalPCA(vars embeddings[PAIRS][5]) {
    for (int i = 0; i < PAIRS; i += CHUNK_SIZE) {
        for (int a = 0; a < 5; a++) {
            for (int b = 0; b < 5; b++) {
                cumulativeCovariance[a][b] = 0;
                for (int j = i; j < i + CHUNK_SIZE && j < PAIRS; j++) {
                    cumulativeCovariance[a][b] += embeddings[j][a] * embeddings[j][b];
                }
            }
        }
        totalSamples += CHUNK_SIZE;
    }
    for (int a = 0; a < 5; a++) {
        for (int b = 0; b < 5; b++) {
            cumulativeCovariance[a][b] /= totalSamples;
        }
    }
}

// Step 5: Autoencoder PCA
function initializeAutoencoder() {
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < LATENT_DIM; j++) {
            encoderWeights[i][j] = random() * 0.1;
            decoderWeights[j][i] = random() * 0.1;
        }
    }
}

function forwardPass(vars input[5], vars latent[LATENT_DIM], vars output[5]) {
    for (int j = 0; j < LATENT_DIM; j++) {
        latent[j] = 0;
        for (int i = 0; i < 5; i++) {
            latent[j] += input[i] * encoderWeights[i][j];
        }
    }
    for (int i = 0; i < 5; i++) {
        output[i] = 0;
        for (int j = 0; j < LATENT_DIM; j++) {
            output[i] += latent[j] * decoderWeights[j][i];
        }
    }
}

function backpropagate(vars input[5], vars output[5], vars latent[LATENT_DIM]) {
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < LATENT_DIM; j++) {
            double gradEncoder = (input[i] - output[i]) * decoderWeights[j][i];
            double gradDecoder = (input[i] - output[i]) * latent[j];
            encoderWeights[i][j] += learningRate * gradEncoder;
            decoderWeights[j][i] += learningRate * gradDecoder;
        }
    }
}

function trainAutoencoder(vars embeddings[PAIRS][5]) {
    for (int epoch = 0; epoch < 50; epoch++) {
        for (int i = 0; i < PAIRS; i++) {
            vars latent[LATENT_DIM];
            vars output[5];
            forwardPass(embeddings[i], latent, output);
            backpropagate(embeddings[i], output, latent);
        }
    }
}

// Step 6: Generate trading signals
function generateSignals() {
    for (int i = 0; i < PAIRS; i++) {
        double mean = 0, stddev = 0;
        for (int j = 0; j < 3; j++) {
            mean += reducedMatrix[i][j];
            stddev += pow(reducedMatrix[i][j] - mean, 2);
        }
        stddev = sqrt(stddev / 3);
        double threshold = mean + thresholdMultiplier * stddev;
        if (reducedMatrix[i][0] > threshold) signals[i] = 1;
        else if (reducedMatrix[i][0] < -threshold) signals[i] = -1;
        else signals[i] = 0;
    }
}

// Step 7: Execute trades
function executeTrades() {
    for (int i = 0; i < PAIRS; i++) {
        if (signals[i] > 0) enterLong(CurrencyPairs[i]);
        else if (signals[i] < 0) enterShort(CurrencyPairs[i]);
    }
}

// Main function
function run() {
    set(PLOTNOW);
    calculateVolatilities();
    constructAdjacencyMatrix();
    propagateEmbeddings();
    updateIncrementalPCA(nodeEmbeddings);
    trainAutoencoder(nodeEmbeddings);
    generateSignals();
    executeTrades();
}

Last edited by TipmyPip; 01/07/25 11:18.