Skip to content

About

C++ for FX & Rates Automation

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

Multi-Asset Signal Engine for FX & Rates Research

A header-only C++17 framework that generates a daily multi-asset signal dashboard suitable for an FX & Rates Research desk, feeding directly into the morning note workflow.

Files

File Purpose
signal_engine.hpp Header-only library: stats, linalg (Jacobi PCA), 8 signal modules, DailyReport builder
main.cpp Demo driver with synthetic GBM/Vasicek data
Makefile Build/run/clean targets

Build & run

make run                 # compiles signal_engine and runs it
# or manually:
g++ -std=c++17 -O2 -Wall -Wextra main.cpp -o signal_engine
./signal_engine          # writes daily_report.txt and prints to stdout

No external dependencies (no Eigen, no Boost). Compiles in under a second.

Plugging in real data

Replace the synthetic generators in main.cpp with a CSV/Bloomberg loader. A TimeSeries is just {ticker, dates, values}, so a minimal CSV reader is ~20 lines. Typical pipelines:

  • Bloomberg BLPAPI/BPIPE pull, dump to parquet/CSV nightly, ingest at 7am SGT.
  • Refinitiv Eikon DataStream API.
  • Internal market-data warehouse (ETL).

Yields go in as percent (e.g. 4.30 for 4.30%); FX spot as quoted; vol indices (VIX) and credit spreads as level. Feed yield changes (or differences) into PCA, not levels, so PC1 represents parallel shifts.

What each module does and how to read it

A. Cross-asset risk regime

Composite z-score of DXY, VIX, HY OAS, UST10Y over a 60D window. Risk-off proxies: stronger USD, higher VIX, wider HY, lower yields (flight to quality). Output is a single number bucketed into RISK ON / NEUTRAL / RISK OFF.

Read it as: the macro top-of-page summary. If composite < -0.5, lead the note with risk-off framing (defensive FX = JPY, CHF; receive duration; fade carry trades).

B. FX vol-adjusted carry

For each pair, computes (rate_base - rate_quote) / annualized_realized_vol. This is essentially the carry-trade information ratio. Sign convention: pair ABCDEF long = long ABC, short DEF. Long the pair earns positive carry when rate_base > rate_quote.

Read it as: an IR. >0.5 is a structurally attractive carry; <-0.5 means the carry is paying you to be the other side. The [LONG] / [SHORT] tag fires at |IR|>0.5. Cross-reference with the risk-regime call: high-carry positions de-rate hard in risk-off.

C. Cross-sectional momentum (63D)

Demeans 3-month log returns across the FX universe and converts to peer z-scores. Top/bottom flagged at |z|>0.7.

Read it as: trend persistence relative to peers. A pair with peer-z > 1.5 is the dominant trend; below -1.5 is the weakest. Standard quant-overlay signal that pairs well with carry (the textbook AQR-style FX strategy).

D. Mean reversion (20D z-score)

Flags spot at |z| > 2σ vs its own 20D rolling distribution. Empty section when nothing extreme, which is the common case.

Read it as: short-term tactical fades. A USDJPY at 20D z = +2.3 is a candidate for tactical short; if risk-on regime is on the same side, the signal is amplified.

E. Yield-curve shape

For each input curve, computes 2s10s, 5s30s slopes (bp) and the 2s5s10s butterfly (bp). Positive butterfly means the 5y is rich relative to the 2y/10y wings.

Read it as: standard rates desk tabular: slope direction (steepener / flattener candidates) and inversion flag. The butterfly tells you about belly relative-value (positive fly = belly cheap relative to barbell, candidate to receive 5y vs pay 2y/10y).

F. Rates PCA (level / slope / curvature)

Eigendecomposition of the historical curve covariance. Loadings are the eigenvectors; the latest factor scores are the projection of today's deviation onto each PC.

Read it as:

  • PC1 loadings should be roughly equal-positive across tenors (this is the "level" factor; it represents parallel shifts and explains 80-90% of variance in normal markets).
  • PC2 flips sign across the curve (front-end positive, long-end negative or vice versa); this is the "slope" factor.
  • PC3 has positive belly and negative wings; this is the "curvature" factor.
  • Cumulative variance explained should be in the high 90s by PC3. If PC1 alone is <70%, the curve is in a non-trivial regime (idiosyncratic moves dominating).
  • Latest factor scores tell you whether the curve is currently far from average in level (PC1 score), slope (PC2), or curvature (PC3) terms. Big PC2 score with small PC1 score = pure steepening/flattening move.

This is exactly the framework used to construct PCA-neutral RV trades on a rates desk: enter a butterfly that's long PC3 but neutral on PC1 and PC2.

G. Realized vol regime

Ratio of 20D realized vol to 252D realized vol. Ratio > 1.5 = vol expansion (de-risk); ratio < 0.7 = vol compression (potential breakout coming, options cheap).

Read it as: a positioning hint. Vol-expansion regimes argue against carry and for defensive positioning. Vol compression often precedes a regime change — useful for flagging "cheap optionality" in the morning note.

H. Cross-asset beta breakdown

For each (asset, driver) pair (e.g. USDJPY-UST10Y, AUDUSD-DXY), compares 252D rolling correlation to 20D rolling correlation. |Δ| > 0.4 flags a regime shift.

Read it as: the early-warning system for "the usual relationship is breaking down." The classic call-out: USDJPY normally tracks UST10Y; when it decouples (BoJ intervention, Fed-BoJ divergence), that's the headline of the note. Add more pairs as relevant: AUDUSD-iron ore, EURUSD-Bund-UST 10y spread, USDCAD-WTI, KRW/TWD-SOX.

Mapping signals to the morning-note structure

A typical FX & Rates morning note has sections like Markets Overview, FX Views, Rates Views, Watch List. The engine outputs map as follows:

  • Markets Overview → risk-regime composite (Section A) + curve snapshot (E)
  • FX Views → carry rankings (B) + cross-sectional momentum (C) + extremes (D)
  • Rates Views → curve slopes/butterflies (E) + PCA factor scores (F)
  • Watch List → vol-regime flags (G) + beta breakdowns (H)

A junior strategist can ingest the saved daily_report.txt, lift the commentary fields verbatim into bullet points, and spend their time on the discretionary overlay (catalysts, flow, technicals) rather than on number crunching.

Amendments

The four upgrades below are the priority changes to take this from a demo to something a research desk would actually run.

1. SGD NEER deviation from MAS slope

Singapore runs monetary policy via the NEER, not rates, so the NEER vs the MAS policy band is the closest thing to a domestic monetary-policy signal. Build a synthetic NEER from the published basket weights (or BBG SNEER Index), fit the implied centre/slope from MAS's last statement (currently a ~1.5%/yr appreciation slope, recentred at the prevailing rate as of the April MPS), and report today's deviation in basis points off the midpoint plus a [STRONG SIDE / MID / WEAK SIDE] flag. Output ties directly into USDSGD, SGD-cross carry, and SGS-curve calls — and is the single most Asia-research-relevant signal the engine can carry.

2. CNH-CNY basis as a PBoC stance proxy

CNH spot − CNY fix (or onshore-offshore forward-points basis) is one of the cleanest reads on PBoC FX policy stance: a persistently negative basis (CNH stronger than the fix) usually means the PBoC is leaning against depreciation via the fix; a widening positive basis flags tolerance for weakness or active outflow pressure. Add a cnh_cny_basis module that ingests both daily, computes the 20D z-score and a 5D moving average, and emits [PBOC LEAN STRONG / NEUTRAL / TOLERATING WEAKNESS]. This becomes a leading input for USDCNH carry (Section B), USDCNH momentum (Section C), and any AxJ pair that beta-trades CNH (KRW, TWD, MYR).

3. Replace realized vol with implied vol (Bloomberg)

The current realized_vol() is backward-looking and noisy. Replace the carry denominator and the vol-regime module with the front-month ATM implied vol pulled from BBG (USDJPYV1M BGN Curncy, etc.) so:

  • Carry-IR becomes the textbook implied-vol-adjusted carry — directly comparable to what option-overlay desks actually trade.
  • The vol-regime ratio becomes 1M IV / 1Y IV (term-structure slope), and a 1M IV / 1M RV ratio gives a vol risk-premium read.

The interface stays the same: feed an additional TimeSeries of implied-vol points per pair, swap the denominator in fx_carry(), and gate the vol-regime module on whichever series is provided.

4. Backtest harness with per-signal rolling Sharpe

Wrap each signal in a thin Strategy interface (signal_t evaluate(date) → position) and run a daily P&L loop over the past 12M of history. Track per-signal:

  • Rolling 60D / 252D Sharpe
  • Hit rate and average win/loss
  • Drawdown vs peer signals

Then in the daily report add a new Section I — Signal Health that prints each signal's current rolling Sharpe with a [WORKING / NEUTRAL / BROKEN] flag (e.g. >0.8, |·|≤0.5, <-0.3 over 60D). The morning-note reader should be able to glance at Section I and know which signals to lead with and which to caveat. Downweighting in the composite (Section A) is a follow-on once the harness is stable.

Other suggested extensions

  • Add more drivers: USDCAD vs WTI, USDZAR vs gold, KRW/TWD vs SOX, USDIDR vs Indo CDS.
  • Term-premium proxy: ACM-style decomposition needs a Kalman filter; simpler proxy is 10Y_yield - average_3M_OIS_path_5y.
  • Output to JSON/CSV alongside text so a downstream Python notebook or web dashboard can consume it.

Caveats

  • All correlations and z-scores are sensitive to window choice. The defaults (20D / 60D / 252D) are reasonable for daily research but should be tuned.
  • The Jacobi PCA implementation is fine for small (<20 tenors) curves but isn't optimized; for production swap in Eigen's SelfAdjointEigenSolver.
  • Sample data is GBM/Vasicek, so don't read structural meaning into the synthetic output. Real data will produce sharper signals.
  • The signal weights in the risk-regime composite are equal; in production these should be calibrated against historical risk-off episodes (e.g. Mar-2020, Sep-2022, Aug-2024).

Notes

  1. Why C++: speed matters when you're running 10+ signals across 30+ assets every morning at 6am. Python is fine for prototyping but a compiled engine is what production research desks deploy.
  2. Why vol-adjusted carry not raw carry: comparing IDR carry (high vol) to JPY carry (low vol) on a raw basis is meaningless; you want the IR.
  3. Why PCA on yield changes not levels: levels are non-stationary; you need stationary inputs for the covariance to be interpretable.
  4. The butterfly is a curvature trade: sign convention matters (2*y5 - y2 - y10 is the standard PCA-curvature direction).
  5. Beta breakdown as risk signal: when the historical relationship breaks, the explanation is usually the next research piece.

About

C++ for FX & Rates Automation

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages