Page 1 of 2
Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:21 pm
by Fairman
Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Three currency pairs are linked. If their prices drift out of line, traders can exploit the mismatch, and doing so pulls the prices back together.
Consider EUR/USD, GBP/USD, and EUR/GBP. The three rates should satisfy a relationship: EUR/GBP equals EUR/USD divided by GBP/USD. Suppose EUR/USD is 1.1000, GBP/USD is 1.2500, and EUR/GBP is quoted at 0.8850 instead of the implied 0.8800.
A trader could sell euros for pounds at 0.8850, which is a better rate than implied, then convert pounds to dollars and dollars back to euros through the other pairs, ending with more euros than started. This is triangular arbitrage.
In practice, such opportunities last for fractions of a second and are captured by algorithms. The act of trading closes the gap. For retail traders, the opportunities have disappeared before you could click.
Transaction costs also matter. The profit from a mismatch must exceed the spreads on all three legs, so only discrepancies larger than the costs are exploitable.
The relevance for you is understanding that cross rates are tightly linked to dollar pairs, and any large move in one leg must show up in the others.
This also explains why correlation among pairs is not accidental. It arises from this mathematical structure.
Practical step: calculate the implied EUR/GBP from live quotes and compare with the direct quote.
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:43 pm
by PTScalper
Fairman wrote: Mon Oct 05, 2026 4:21 pm
Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Three currency pairs are linked. If their prices drift out of line, traders can exploit the mismatch, and doing so pulls the prices back together.
Consider EUR/USD, GBP/USD, and EUR/GBP. The three rates should satisfy a relationship: EUR/GBP equals EUR/USD divided by GBP/USD. Suppose EUR/USD is 1.1000, GBP/USD is 1.2500, and EUR/GBP is quoted at 0.8850 instead of the implied 0.8800.
A trader could sell euros for pounds at 0.8850, which is a better rate than implied, then convert pounds to dollars and dollars back to euros through the other pairs, ending with more euros than started. This is triangular arbitrage.
In practice, such opportunities last for fractions of a second and are captured by algorithms. The act of trading closes the gap. For retail traders, the opportunities have disappeared before you could click.
Transaction costs also matter. The profit from a mismatch must exceed the spreads on all three legs, so only discrepancies larger than the costs are exploitable.
The relevance for you is understanding that cross rates are tightly linked to dollar pairs, and any large move in one leg must show up in the others.
This also explains why correlation among pairs is not accidental. It arises from this mathematical structure.
Practical step: calculate the implied EUR/GBP from live quotes and compare with the direct quote.
Hi Fairman,
This is an excellent breakdown of how structural efficiency is maintained in the FX market. While the high-frequency trading (HFT) reality eliminates triangular arbitrage as a viable retail strategy, understanding the underlying math remains a critical edge for discretionary trading.
Here are three practical ways retail and institutional traders apply this mathematical structure beyond chasing microsecond pricing inefficiencies:
Managing Correlated Risk Exposure: Many retail traders unknowingly double or wipe out their exposure because they ignore cross-rate implications. If you are simultaneously long EUR/USD and short GBP/USD, you have essentially constructed a synthetic long position on EUR/GBP. Understanding this mathematical lock prevents traders from taking on unintended, concentrated risk across multiple correlated pairs.
The Illusion of News-Event Arbitrage: During high-impact macroeconomic releases (e.g., US CPI or Non-Farm Payrolls), price feeds can briefly desync, creating what looks like massive triangular arbitrage opportunities on a retail screen. However, this is a latency illusion. Institutional liquidity providers immediately widen their spreads to protect against this exact vulnerability. Factoring in widened spreads and execution slippage, the theoretical profit instantly becomes a net loss.
Synthetic Order Routing by Market Makers: Your final point about calculating the implied quote versus the direct quote is exactly how many retail brokers operate. Rather than matching buyers and sellers on minor cross pairs (like CAD/CHF or NZD/JPY) where direct liquidity is thin, brokers often synthesize the rate dynamically using the highly liquid USD major legs. The "direct quote" on your platform is often just the algorithmically derived product of the two USD pairs plus a retail markup.
For anyone looking to test the practical step mentioned in the original post, plotting the spread differential between the synthetic calculation and the direct broker feed on a tick chart is a great way to visualize your broker's internal pricing efficiency and true execution costs.
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:43 pm
by PTScalper
Here is the TradingView Pine Script (v5) to visualize this exact arbitrage mathematical structure. It calculates the synthetic (implied) rate of EUR/GBP using EUR/USD and GBP/USD, compares it to your broker’s direct EUR/GBP feed, and plots the gap in points (ticks).
Code: Select all
//@version=5
indicator("Synthetic Cross vs Actual Spread", shorttitle="Tri-Arb Gap", overlay=false)
// 1. Define the two USD leg inputs
leg1_sym = input.symbol("FX_IDC:EURUSD", title="Base Leg (EUR/USD)")
leg2_sym = input.symbol("FX_IDC:GBPUSD", title="Quote Leg (GBP/USD)")
// 2. Fetch the closing prices of the two legs from the same timeframe
leg1_price = request.security(leg1_sym, timeframe.period, close)
leg2_price = request.security(leg2_sym, timeframe.period, close)
// 3. Calculate the implied/synthetic rate (EUR/USD divided by GBP/USD = EUR/GBP)
synthetic_rate = leg1_price / leg2_price
// 4. Get the actual chart price (this script should be applied to the EUR/GBP chart)
actual_rate = close
// 5. Calculate the discrepancy in broker points/ticks
// Using syminfo.mintick ensures the calculation scales correctly for the asset
pricing_gap = (actual_rate - synthetic_rate) / syminfo.mintick
// 6. Plot the gap as a histogram
plot(pricing_gap, title="Pricing Gap (Points)", color=pricing_gap > 0 ? color.teal : color.maroon, style=plot.style_columns)
hline(0, title="Perfect Efficiency", color=color.gray, linestyle=hline.style_dotted)
// Optional: Plot the raw synthetic price as a line on the subchart for reference
plot(synthetic_rate, title="Synthetic Rate (Hidden by Default)", color=color.blue, display=display.none)
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:44 pm
by PTScalper
How to use this on TradingView:
1.) Open a chart for EUR/GBP (drop down to a 1-minute or tick chart to see the micro-fluctuations).
2.) Open the Pine Editor tab at the bottom of the screen.
3.) Paste the code above, click Save, and then Add to Chart.
What you are looking at:
The Zero Line: Represents perfect mathematical efficiency (the direct broker quote exactly matches the division of the two USD legs).
The Histogram Bars: Show how many points (pipettes) the broker's direct quote deviates from the mathematically implied rate.
The Reality Check: You will notice the bars constantly oscillating around zero. In highly liquid sessions, the gap stays near zero. During major news events (like FOMC or NFP), you will see these spikes widen dramatically as liquidity providers widen their spreads to prevent institutional HFTs from exploiting this exact equation.
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:44 pm
by PTScalper
To adapt the script for a pair like EUR/JPY, the mathematical relationship changes because the US Dollar is the quote currency in the first leg (EUR/USD) but the base currency in the second leg (USD/JPY).
Instead of dividing, you multiply them: EUR/USD × USD/JPY = EUR/JPY.
Here is the modified Pine Script:
Code: Select all
//@version=5
indicator("Synthetic Cross vs Actual Spread (Multiplier)", shorttitle="Tri-Arb Gap (Mult)", overlay=false)
// 1. Define the two USD leg inputs (Defaulted for EUR/JPY)
leg1_sym = input.symbol("FX_IDC:EURUSD", title="Base Leg (EUR/USD)")
leg2_sym = input.symbol("FX_IDC:USDJPY", title="Quote Leg (USD/JPY)")
// 2. Fetch the closing prices of the two legs from the same timeframe
leg1_price = request.security(leg1_sym, timeframe.period, close)
leg2_price = request.security(leg2_sym, timeframe.period, close)
// 3. Calculate the implied/synthetic rate via multiplication
// (EUR/USD * USD/JPY = EUR/JPY)
synthetic_rate = leg1_price * leg2_price
// 4. Get the actual chart price (this script MUST be applied to the EUR/JPY chart)
actual_rate = close
// 5. Calculate the discrepancy in broker points/ticks
// syminfo.mintick automatically handles JPY's different decimal structure (2 or 3 decimals)
pricing_gap = (actual_rate - synthetic_rate) / syminfo.mintick
// 6. Plot the gap as a histogram
plot(pricing_gap, title="Pricing Gap (Points)", color=pricing_gap > 0 ? color.teal : color.maroon, style=plot.style_columns)
hline(0, title="Perfect Efficiency", color=color.gray, linestyle=hline.style_dotted)
// Optional: Plot the raw synthetic price as a line on the subchart for reference
plot(synthetic_rate, title="Synthetic Rate (Hidden)", color=color.blue, display=display.none)
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:45 pm
by PTScalper
What changed:
The Inputs: The second leg is now USDJPY instead of GBPUSD.
The Operator: The synthetic_rate calculation now uses * instead of /.
Why syminfo.mintick is critical here
Standard pairs like EUR/USD are priced to 4 or 5 decimal places (e.g., 1.10500), while JPY pairs are priced to 2 or 3 (e.g., 150.250). By dividing the raw price difference by syminfo.mintick, the script automatically detects that it is on a JPY chart and scales the gap into standardized points, saving you from having to write custom decimal logic.
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:46 pm
by PTScalper
Here is the universal Pine Script. It introduces an input.string() function with an options parameter to create a dropdown menu, along with a conditional statement to switch the mathematical logic on the fly.
Code: Select all
//@version=5
indicator("Universal Synthetic Cross Gap", shorttitle="Uni-Arb Gap", overlay=false)
// 1. Inputs for the two legs
leg1_sym = input.symbol("FX_IDC:EURUSD", title="Leg 1 (e.g., EUR/USD)")
leg2_sym = input.symbol("FX_IDC:GBPUSD", title="Leg 2 (e.g., GBP/USD)")
// 2. Dropdown for the calculation method
calc_mode = input.string("Divide (Leg1 / Leg2)", title="Calculation Method",
options=["Divide (Leg1 / Leg2)", "Multiply (Leg1 * Leg2)"])
// 3. Fetch the closing prices of the two legs
leg1_price = request.security(leg1_sym, timeframe.period, close)
leg2_price = request.security(leg2_sym, timeframe.period, close)
// 4. Calculate the synthetic rate based on the dropdown selection
// If "Divide" is selected, it divides. Otherwise, it multiplies.
synthetic_rate = calc_mode == "Divide (Leg1 / Leg2)" ? (leg1_price / leg2_price) : (leg1_price * leg2_price)
// 5. Calculate discrepancy in broker points/ticks
pricing_gap = (close - synthetic_rate) / syminfo.mintick
// 6. Plot the gap as a histogram
plot(pricing_gap, title="Pricing Gap (Points)", color=pricing_gap > 0 ? color.teal : color.maroon, style=plot.style_columns)
hline(0, title="Perfect Efficiency", color=color.gray, linestyle=hline.style_dotted)
// Optional: Plot the raw synthetic price as a line on the subchart for reference
plot(synthetic_rate, title="Synthetic Rate (Hidden)", color=color.blue, display=display.none)
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:46 pm
by PTScalper
How the universal logic works:
The Dropdown: The options array inside input.string creates the UI dropdown in your script's settings gear.
The Conditional Operator (?
: The formula for synthetic_rate acts as an if/then statement. It checks if the dropdown is set to "Divide". If yes, it executes leg1 / leg2. If no, it defaults to leg1 * leg2.
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:48 pm
by PTScalper
To add alerts, we need to introduce an input for your threshold limit, use the absolute value of the gap (so it triggers on both positive and negative extremes), and add TradingView's alertcondition() function.
I've also added visual threshold lines and a background highlight so you can instantly see when the condition is met on the chart.
Code: Select all
//@version=5
indicator("Universal Synthetic Cross Gap w/ Alerts", shorttitle="Uni-Arb Alerts", overlay=false)
// 1. Inputs for the two legs & calculation method
leg1_sym = input.symbol("FX_IDC:EURUSD", title="Leg 1 (e.g., EUR/USD)")
leg2_sym = input.symbol("FX_IDC:GBPUSD", title="Leg 2 (e.g., GBP/USD)")
calc_mode = input.string("Divide (Leg1 / Leg2)", title="Calculation Method",
options=["Divide (Leg1 / Leg2)", "Multiply (Leg1 * Leg2)"])
// 2. Alert Threshold Input
alert_threshold = input.int(15, title="Alert Threshold (Points)", minval=1)
// 3. Fetch the closing prices of the two legs
leg1_price = request.security(leg1_sym, timeframe.period, close)
leg2_price = request.security(leg2_sym, timeframe.period, close)
// 4. Calculate the synthetic rate based on the dropdown selection
synthetic_rate = calc_mode == "Divide (Leg1 / Leg2)" ? (leg1_price / leg2_price) : (leg1_price * leg2_price)
// 5. Calculate discrepancy in broker points/ticks
pricing_gap = (close - synthetic_rate) / syminfo.mintick
// 6. Plot the gap as a histogram
plot(pricing_gap, title="Pricing Gap (Points)", color=pricing_gap > 0 ? color.teal : color.maroon, style=plot.style_columns)
// 7. Plot the Zero Line and Threshold Lines
hline(0, title="Perfect Efficiency", color=color.gray, linestyle=hline.style_dotted)
hline(alert_threshold, title="Upper Alert Limit", color=color.red, linestyle=hline.style_dashed)
hline(-alert_threshold, title="Lower Alert Limit", color=color.red, linestyle=hline.style_dashed)
// 8. Define the Alert Trigger
// math.abs() converts negative gaps to positive numbers so we only need one threshold check
is_alert_triggered = math.abs(pricing_gap) >= alert_threshold
// 9. Alert Condition for TradingView UI
alertcondition(is_alert_triggered, title="Gap Threshold Exceeded", message="The synthetic pricing gap has exceeded your defined threshold.")
// 10. Visual Cue: Highlight the background red when the alert condition is met
bgcolor(is_alert_triggered ? color.new(color.red, 90) : na, title="Alert Background Highlight")
Re: Triangular Arbitrage: Why Prices Across Pairs Stay Consistent
Posted: Mon Oct 05, 2026 4:49 pm
by PTScalper
What changed:
alert_threshold: Adds a setting to the gear menu where you can define how many points the gap must reach to trigger the alert (defaults to 15).
math.abs(): The gap will often swing negative. By wrapping the pricing_gap in math.abs(), a gap of -20 becomes 20, ensuring it correctly triggers your 15 point threshold.
hline(): Draws dashed red lines across your histogram so you can visually see where your threshold is set.
bgcolor(): Flashes a light red background on the chart exactly when the threshold is breached, making historical backtesting visually intuitive.
How to activate the alert in TradingView:
Adding alertcondition() to the code allows TradingView to see the alert, but you still have to turn it on.
1.) Add the updated script to your chart.
2.) Press Alt + A (Windows) or Option + A (Mac) to open the Create Alert menu.
3.) Change the Condition dropdown from the price to your script ("Uni-Arb Alerts").
4.) Ensure the secondary dropdown is set to Gap Threshold Exceeded.
5.) Choose your trigger frequency (e.g., "Once Per Bar Close") and click Create.