Skip to content

Repository files navigation

fx-basis — G10 cross-currency basis & FX factor studies

This repo contains two cross-sectional G10 FX studies that share one from-scratch research stack (data → signal → dollar-neutral portfolio → variance targeting → costs → date-clustered inference → diagnostics), built src/basis/:

  1. Cross-currency basis modelthis README, below. The 3M OIS-based basis, a non-circular cross-sectional fair value, and the L/S residual strategy. See also investigations/beta_sign.md (why the cross-sectional slope is positive, opposite Du-Tepper-Verdelhan).
  2. FX factor model (carry / value / momentum) — reuses the basis package's portfolio/risk/cost/inference apparatus unchanged on a new signal layer (src/basis/premium/). Writeup: reports/fx_factors.md.

Both are honest risk-premium-harvesting studies, not alpha/arbitrage. Reproduce either via pip install -e . then python -m basis.pipeline / python -m basis.premium.pipeline (needs LSEG Workspace for the first data pull; see below). pytest runs 33 tests.


Cross-Sectional G10 Cross-Currency Basis Model

Constructs the 3M cross-currency basis for G10 currencies from first principles (covered interest parity, OIS legs), specifies a cross-sectional fair value, trades the residual as a dollar-neutral long/short portfolio, and reports defensible inference with an honest account of the strategy's negative skew.

Key caveat (read first). This harvests a known funding-liquidity risk premium (Du–Tepper–Verdelhan). It is not alpha discovery. The deliverable is correct construction, a non-circular fair value, defensible inference, and an honest account of the skew — not a high-Sharpe trading signal. After realistic costs the strategy is a modest-Sharpe (~1.2), heavily negatively-skewed, high-leverage carry harvest.

Universe & data

Clean-6: EUR, JPY, GBP, CHF, AUD, CAD vs USD, daily. Sample 2009-05-06 → present (OIS-limited; this covers the 2011-12 euro crisis and March-2020, but not the Sept-2008 Lehman peak — GBP/CHF/AUD/CAD/EUR OIS series start mid-2009).

All data is pulled from LSEG Refinitiv Workspace via its desktop API proxy. The official lseg.data / eikon Python libraries hang on this machine, so we use a thin direct-HTTP UDF client (src/basis/data/lseg_client.py). Forward points and OIS are not available free with this history — they require the terminal. RICs (field TR.MIDPRICE, daily):

Series RICs
Spot EUR= JPY= GBP= CHF= AUD= CAD=
3M FX forward points EUR3M= JPY3M= GBP3M= CHF3M= AUD3M= CAD3M=
3M OIS USD3MOIS=, EUREON3M=, JPY3MOIS=, GBP3MOIS=, CHF3MOIS=, AUD3MOIS=, CAD3MOIS=
Exogenous vol state (Step 7) .MOVE (TR.CLOSEPRICE)

Sign convention (basis)

Everything in USD per 1 unit of foreign currency. The 3M basis x_i is the wedge added to the foreign OIS leg that equates synthetic USD funding via the FX swap with direct USD OIS (DTV convention):

(1 + r_US·τ) = (F/S)·(1 + (r_i + x_i)·τ)
x_i = (1/τ)·[ (S/F)·(1 + r_US·τ) − 1 ] − r_i

x_i < 0 ⇒ synthetic dollars are expensive (the dominant post-GFC G10 case). The forward F is the market-observed outright (spot + market forward points); it is never backed out of OIS — doing so would make the basis identically zero. Required check (test): EUR & JPY bases are persistently negative and widen in 2011-12 and March-2020. ✔ (see tests/test_construction.py).

Method & headline results

  1. Construction — CIP from first principles. EUR mean basis −30bp (98.6% of days negative), JPY −42bp; both spike in 2011-12 and to −150/−247bp in Mar-2020.
  2. Cross-sectional fair value — at each date b_{i,t} = α_t + β·(r_i−r_US) + ε_{i,t}. α_t (date FE) absorbs the common dollar factor; ε is the signal. This is not the circular own-rate time-series regression. β is POSITIVE (Fama-MacBeth +0.15, t=17.7; pooled date-FE +0.11, t=85.6): across the developed G6 the basis is a near-monotone increasing function of the rate differential (between-currency corr +0.99), high-yield AUD at a ~0 basis, low-yield JPY/CHF/EUR most negative. Low-yield currencies do carry the most negative bases (a level fact), but that is the ordering, not the cause of the slope — the slope survives dropping the safe havens and holds in every sub-period including DTV's own 2010-16 window (see investigations/beta_sign.md). This is the opposite sign to DTV's "higher-yield → more negative" phrasing, which corresponds to signing the differential (r_US−r_i); the residual signal is invariant to this choice.
  3. Portfolio — weekly, dollar-neutral, weight ∝ −ε. P&L decomposed into carry (Sharpe ~10, smooth, ~74% of return) and convergence (Sharpe 0.62, bears the tail). Honest answer to "is it just carry?": largely yes.
  4. Variance targeting (Barroso–Santa-Clara, on the strategy's own trailing vol, no regime timing) — at equal vol, Sharpe 2.26 → 2.65, kurtosis 45 → 23. Hitting 10% vol needs ~310x average leverage (basis arb is a near-arbitrage).
  5. Costs — bid-ask on FX-swap + OIS legs, wider for AUD/CAD, widened in high-vol periods (named parameters in src/basis/costs.py). Costs eat ~45% of gross; Sharpe 2.29 → 1.26 net.
  6. Inference — net long-short premium Newey-West t = 6.7 (gross 11.6), SEs robust to the 3M overlap; ~6 currencies/cross-section, N_eff ≈ 815. Inference rests on limited cross-sectional dispersion and a carry-dominated premium — do not over-read the t-stat. Purged+embargoed CV splitter provided.
  7. Skew diagnostic (descriptive, not a timer) — net premium bucketed by lagged MOVE terciles: skew +0.5 (calm) → −3.4 (stress), mean return halving in stress. Worst drawdowns: Jan-2015 (CHF de-peg) and March-2020.

Modeling judgment calls (flagged)

  • EUR OIS = EUREON3M= (EONIA basis; the only EUR OIS reaching 2009). It sits a constant ~8.5bp above €STR OIS — a constant level offset on the EUR basis, no dynamic distortion.
  • Day-count τ = 91/360 (EUR/CHF/USD) or 91/365 (JPY/GBP/AUD/CAD). Exact settlement-date accrual is a parameterized second-order refinement.
  • Basis MTM duration for the convergence P&L ≈ tenor (0.25y).
  • β positive in our (r_i−r_US) convention — reported as-is, not forced negative.

Reproduce

python -m venv .venv && source .venv/bin/activate
pip install -e .                       # installs the `basis` package + deps
# Set the LSEG app key (Eikon Data API type, from Workspace App Key Generator):
#   create lseg-data.config.json (see template) or export LSEG_APP_KEY=...
pytest                                 # 27 tests incl. the basis sign sanity checks
python -m basis.pipeline               # pulls+caches data, prints all numbers, writes figures/
jupyter notebook notebooks/results.ipynb   # end-to-end notebook

Data is pulled once and cached under data/. Requires LSEG Workspace running and logged in (desktop proxy on localhost:9000). All figures land in figures/.

Layout

src/basis/
  data/lseg_client.py   direct UDF client      construction.py   Step 1 (CIP, sign conv.)
  data/loader.py        RICs, fetch, cache      regression.py     Step 2 (FM + panel FE)
  portfolio.py          Step 3 (L/S, P&L decomp) risk.py          Step 4 (variance targeting)
  costs.py              Step 5 (bid-ask)         inference.py      Step 6 (NW t, purged CV)
  diagnostics.py        Step 7 (skew)            pipeline.py       end-to-end orchestrator
tests/                  27 tests                 notebooks/results.ipynb

About

G10 cross-currency basis + FX carry/value/momentum factor model: a from-scratch research stack (CIP/OIS basis, Fama-MacBeth cross-sectional fair value, dollar-neutral portfolios, variance targeting, realistic costs, Newey-West inference, honest tail/skew diagnostics).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages