Strategy: Optimal Execution of EUR/USD Orders with Market Microstructure Models
Overview This strategy employs stochastic filtering and impulse control within a Markov-modulated limit order book (LOB). It is designed for high-frequency trading (HFT) and algorithmic execution, optimizing order placement under incomplete information about market liquidity.
We assume that the EUR/USD price evolution follows a Hawkes process, where order flows influence future price movements. Liquidity is treated as a hidden Markov process, which the strategy estimates in real-time.
1. Market Model Assumptions
Price Dynamics: The EUR/USD price follows an Ornstein-Uhlenbeck process with transient and permanent market impacts.
Liquidity Modeling: Hidden Markov-modulated liquidity states influence bid-ask spreads and market depth.
Market Impact: A concave power-law function describes execution price impact, limiting adverse price movement from large orders.
Execution Constraints: The strategy optimizes execution by deciding when and how much to trade, minimizing cost and slippage.
2. Strategy Components
2.1. Signal Generation (Regime Detection)
Use a Hidden Markov Model (HMM) to classify the current market regime:
Regime 1 (High Liquidity): Low market impact, high order book depth.
Regime 2 (Low Liquidity): High market impact, low order book depth.
Compute the posterior probability of the market being in Regime 1 using Bayesian inference.
2.2. Trade Execution (Impulse Control Optimization)
The strategy solves an optimal stopping problem:
If the probability of Regime 1 is high, execute a larger trade to take advantage of liquidity.
If the probability of Regime 2 increases, reduce trade size to minimize market impact.
Execution follows an adaptive liquidation model, using the Kushner-Stratonovich equations to update liquidity estimates.
3. Execution Algorithm
Step 1: Estimate Market Liquidity Using Filtering
Observe order arrivals and price changes.
Compute the expected liquidity state using a stochastic filter.
If liquidity is high (Regime 1), prepare to execute larger orders.
Step 2: Compute Optimal Trade Size
Define an inventory risk function:
ð½(ð‘¡,ð‘‹ð‘¡) = ð¸[∑ð‘˜ð¶(ð‘†ðœð‘˜+ð·ðœð‘˜,Δð‘‹ðœð‘˜)]
where:
ð‘‹ð‘¡ is inventory,
ð¶ is transaction cost,
ð‘†ðœð‘˜ and ð·ðœð‘˜ are price components.
Solve for the optimal order size using a Bellman recursion:
ð‘‰(ð‘¡,ð‘‹ð‘¡)=maxâ¡Î”ð‘‹ð‘¡ð¸[ð¶(ð‘†ð‘¡+ð·ð‘¡,Δð‘‹ð‘¡)+ð‘‰(ð‘¡+1,ð‘‹ð‘¡âˆ’Δð‘‹ð‘¡)]
Step 3: Execute Trades Based on Liquidity Regime
Regime 1 (High Liquidity):
Execute larger trades (low price impact).
Use limit orders when spread is narrow.
Regime 2 (Low Liquidity):
Reduce order size to avoid price impact.
Use market orders only when necessary.
Step 4: Monitor Market Impact and Adjust Strategy
Compute the trade execution performance metric:
𑃠slippage =Executed Price − Mid Price
​
Adjust trade sizes dynamically.
#include <default.c>
#define LOOKBACK 5
#define FEATURES 5
#define X_MAX 100
// Define Perceptron weights and biases manually
var PerceptronWeights[3] = {0.5, -0.3, 0.2};
var PerceptronBias = 0.1;
// Sigmoid activation function
var sigmoid(var x) {
return 1.0 / (1.0 + exp(-x));
}
// Custom Perceptron function to replace `adviseLong()`
var perceptronPredict(vars Features, int size) {
var weightedSum = PerceptronBias;
int i;
for (i = 0; i < size; i++) {
weightedSum += PerceptronWeights[i] * Features[i];
}
return sigmoid(weightedSum) * 100.0;
}
// Compute dynamic liquidity probability
var computePiT() {
vars atr_series = series(ATR(20), LOOKBACK);
vars spread_series = series(Spread, LOOKBACK);
vars price_series = series(priceClose(), LOOKBACK);
vars stddev_series = series(0, LOOKBACK);
stddev_series[0] = StdDev(price_series, 20);
vars Features = series(0, 15);
int i;
for (i = 0; i < LOOKBACK; i++) {
Features[i] = atr_series[i] / priceClose();
Features[i + LOOKBACK] = stddev_series[i] / priceClose();
Features[i + 2 * LOOKBACK] = spread_series[i] / 0.001;
}
return perceptronPredict(Features, 15) / 100.0;
}
// Compute dynamic threshold for last 5 candles
void computeThresholdSeries(vars threshold_series) {
vars atr_series = series(ATR(20), LOOKBACK);
vars pi_series = series(computePiT(), LOOKBACK);
int i;
for (i = 0; i < LOOKBACK; i++) {
threshold_series[i] = 40 + (atr_series[i] * 100) - (pi_series[i] * 10);
threshold_series[i] = clamp(threshold_series[i], 30, 70);
}
}
function run() {
set(PARAMETERS);
StartDate = 20231231;
EndDate = 2025;
NumWFOCycles = 10;
BarPeriod = 1;
LookBack = 150;
Capital = 1000;
vars X = series(priceClose());
vars X_diff = series(priceClose(1) - priceClose());
var pi_t = computePiT();
vars threshold_series = series(0, LOOKBACK);
computeThresholdSeries(threshold_series);
vars DTREE_Features = series(0, FEATURES);
int i;
for (i = 0; i < LOOKBACK; i++) {
DTREE_Features[i] = threshold_series[i];
}
var trade_threshold = perceptronPredict(DTREE_Features, FEATURES);
trade_threshold = clamp(trade_threshold, 30, 70);
vars TradeFeatures = series(0, 3);
TradeFeatures[0] = X[0];
TradeFeatures[1] = X_diff[0];
TradeFeatures[2] = pi_t;
var long_prob = perceptronPredict(TradeFeatures, 3);
var short_prob = perceptronPredict(TradeFeatures, 3) - 10;
int trade_flag = ifelse(long_prob > trade_threshold || short_prob > trade_threshold, 1, 0);
var trade_size = ifelse(is(PARAMETERS), perceptronPredict(TradeFeatures, 3) / 100.0 * X_MAX, 10);
string order_type = ifelse(long_prob > trade_threshold, "Market", "Limit");
if (trade_flag) {
if (long_prob > short_prob) {
if (strstr(order_type, "Market")) {
enterLong(trade_size);
} else {
enterLong(trade_size);
Entry = priceClose() * 1.001;
}
} else {
if (strstr(order_type, "Market")) {
enterShort(trade_size);
} else {
enterShort(trade_size);
Entry = priceClose() * 0.999;
}
}
}
printf("\nTrade Decision: %s", ifelse(trade_flag, "Execute", "Hold"));
printf("\nTrade Size: %.2f units", trade_size);
printf("\nOrder Type: %s", order_type);
printf("\nPredicted Liquidity Probability (pi_t): %.2f", pi_t);
printf("\nDynamic Threshold (DTREE Replaced): %.2f", trade_threshold);
}