Optimal Arbitrage with Stochastic Correlation in Currency Pairs
You are tasked with developing an arbitrage strategy between three independent currency pairs:
𝐴/𝐵, 𝐵/𝐶, and 𝐴/𝐶, where:

The price of each currency pair follows a geometric Brownian motion:

dS_i = μ_i * S_i * dt + σ_i * S_i * dW_i, for i ∈ {1, 2, 3}

μ_i: Drift rate of the 𝑖-th pair.
σ_i: Volatility of the 𝑖-th pair.
W_i: Independent Wiener processes for each pair.
A stochastic correlation ρ(t) governs the dependency between the pairs' volatilities:

dρ(t) = κ * (θ - ρ(t)) * dt + η * √(1 - ρ(t)^2) * dZ_t
κ: Mean-reversion speed of ρ(t).
θ: Long-term mean of ρ(t).
η: Volatility of ρ(t).
Z_t: Another Wiener process, independent of W_i.

Objective
Derive a dynamic arbitrage strategy that:

Exploits triangular arbitrage opportunities between the three currency pairs.
Incorporates stochastic correlation ρ(t) to optimize trading decisions.
The profit at each step is calculated as:

Profit = log(S_AB * S_BC / S_AC)

When Profit > Δ, execute arbitrage trades.
Δ: Dynamic threshold dependent on ρ(t).

Formulate the optimal stopping problem:

Define the time Ï„ to execute trades based on maximizing expected profit:

V(S, ρ) = sup_τ E [ ∫_0^τ Profit(S, ρ) * e^(-r * t) dt ]

r: Risk-free discount rate.


Code
#define PAIRS 28 // Number of 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"
};

vars ProfitMatrix[PAIRS][PAIRS];  // Profitability matrix
int Previous[PAIRS];             // Tracks the previous node in the path
vars PathProfit[PAIRS];          // Cumulative profit along the path
vars DynamicDeltaThreshold;      // Dynamic threshold for trade execution

var thresholdZ = 2;              // Z-score threshold
var volatilityThreshold = 1;     // Relative volatility threshold
int lookback = 50;               // Lookback period for mean and volatility

// Function to calculate profitability matrix
function calculateProfitMatrix() {
    for (int i = 0; i < PAIRS; i++) {
        for (int j = 0; j < PAIRS; j++) {
            if (i != j) {
                // Calculate price ratio and mean reversion metrics
                var priceRatio = price(CurrencyPairs[i]) / price(CurrencyPairs[j]);
                var mean = SMA(series(price(CurrencyPairs[i])), lookback);
                var zScore = (price(CurrencyPairs[i]) - mean) / StdDev(series(price(CurrencyPairs[i])), lookback);
                var volatility = StdDev(series(price(CurrencyPairs[i])), lookback);
                var relativeVolatility = volatility / SMA(series(volatility), lookback);

                // Filter trades based on Z-score and volatility
                if (abs(zScore) > thresholdZ && relativeVolatility > volatilityThreshold) {
                    ProfitMatrix[i][j] = log(priceRatio) - (DynamicDeltaThreshold / volatility);
                } else {
                    ProfitMatrix[i][j] = -INF; // Disqualify trade
                }
            } else {
                ProfitMatrix[i][j] = 0; // No self-loops
            }
        }
    }
}

// Calculate the dynamic delta threshold with risk-adjustment
function calculateDynamicDeltaThreshold() {
    var totalRiskAdjustedProfit = 0;
    int count = 0;

    for (int i = 0; i < PAIRS; i++) {
        for (int j = 0; j < PAIRS; j++) {
            if (i != j && ProfitMatrix[i][j] > -INF) {
                var volatility = StdDev(series(price(CurrencyPairs[i])), lookback);
                var riskAdjustedProfit = ProfitMatrix[i][j] / volatility;
                totalRiskAdjustedProfit += riskAdjustedProfit;
                count++;
            }
        }
    }

    if (count > 0) {
        var baselineThreshold = totalRiskAdjustedProfit / count; // Average risk-adjusted profit
        var performanceFactor = Equity / MaxEquity; // Performance feedback
        DynamicDeltaThreshold = baselineThreshold * performanceFactor; // Adjust threshold dynamically
    } else {
        DynamicDeltaThreshold = 0.001; // Default fallback
    }
}

// Bellman-Ford algorithm to find paths with the highest cumulative profit
function bellmanFord(int start) {
    for (int i = 0; i < PAIRS; i++) {
        PathProfit[i] = -INF; // Negative infinity for unvisited nodes
        Previous[i] = -1;     // No predecessor
    }
    PathProfit[start] = 0; // Profit starts at 0 from the source

    for (int k = 0; k < PAIRS - 1; k++) {
        for (int i = 0; i < PAIRS; i++) {
            for (int j = 0; j < PAIRS; j++) {
                if (ProfitMatrix[i][j] != -INF && PathProfit[i] + ProfitMatrix[i][j] > PathProfit[j]) {
                    PathProfit[j] = PathProfit[i] + ProfitMatrix[i][j]; // Update cumulative profit
                    Previous[j] = i; // Track the path
                }
            }
        }
    }
}

// Execute trades along the highest cumulative profit path
function executePath(int start, int end) {
    int current = end;

    while (current != -1) {
        int prev = Previous[current];
        if (prev == -1) break;

        if (ProfitMatrix[prev][current] > 0) {
            enterLong(CurrencyPairs[prev]);
            enterShort(CurrencyPairs[current]);
        } else {
            enterShort(CurrencyPairs[prev]);
            enterLong(CurrencyPairs[current]);
        }

        current = prev;
    }
}

// Continuously execute trades while conditions exist
function executeContinuousTrades(int start) {
    while (1) {
        calculateProfitMatrix();          // Update the graph with live data
        calculateDynamicDeltaThreshold(); // Adjust the threshold dynamically
        bellmanFord(start);               // Recalculate paths with the highest cumulative profit

        for (int i = 0; i < PAIRS; i++) {
            for (int j = 0; j < PAIRS; j++) {
                if (i != j && ProfitMatrix[i][j] > DynamicDeltaThreshold) {
                    enterLong(CurrencyPairs[i]);
                    enterShort(CurrencyPairs[j]);
                    printf("Executing Trade: Long %s, Short %s\n", CurrencyPairs[i], CurrencyPairs[j]);
                }
            }
        }

        // Add a condition to exit the loop for backtesting or analysis
        if (isBacktest() && getBacktestPeriod() == "End") {
            break;
        }
    }
}

// Main trading function
function run() {
    set(PLOTNOW);
    int startPair = 0; // Start from the first currency pair

    executeContinuousTrades(startPair);
}

Last edited by TipmyPip; 12/25/24 15:58.