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