尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗A small system that looks for multi-leg FX arbitrage (triangular and longer loops such as CHF → USD → AUD → CHF) across 13 currencies (78 pairs), using bid/ask quotes and fees. It includes:
Not financial advice. This is a research and learning project. It does not place trades. Profit figures use top-of-book quotes only and ignore latency, slippage, financing, and execution risk. Cross-rate arbitrage among major pairs is quickly closed by professional market makers, so most real opportunities will be smaller than the costs.
Desktop. Each card is one loop. The amount box recalculates every step as you type. The flow panel on the right draws the selected loop.
Flow panel. Hover or tap a leg to see its numbers: price, gross and net amounts, fee, and whether the quoted depth covers the amount.
Mobile. The layout stacks to a single column with no horizontal scrolling.
-log(rate). A loop whose rates multiply to more than 1 is a negative cycle, which Bellman-Ford finds.The currency universe is USD, EUR, GBP, JPY, CHF, AUD, CAD, NZD, NOK, SEK, SGD, ZAR, and INR: 13 currencies and 78 pairs. Because the detector only uses the best bid and ask for each pair, it is fast: a scan takes about 130 µs for 78 pairs in local tests.
| Currency | Name | Currency | Name |
|---|---|---|---|
| USD | US dollar | SGD | Singapore dollar |
| EUR | Euro | ZAR | South African rand |
| GBP | Pound sterling | INR | Indian rupee |
| JPY | Japanese yen | CAD | Canadian dollar |
| CHF | Swiss franc | NOK | Norwegian krone |
| AUD | Australian dollar | SEK | Swedish krona |
| NZD | New Zealand dollar |
The simulated feed uses the starting USD values in START_VALUE in fx_arb/synth.py. To add a currency, add one entry there, add its option to the dropdown in index.html, and check that the OANDA account offers its pairs. Pairs the account doesn't offer are skipped.
fx_arb/ Python package
detector.py graph building, cycle search, cycle pricing
quotes.py Quote type and CSV read/write
replay.py replay recorded quotes; reports new opportunities
synth.py synthetic quote generator with injected mispricings
oanda.py OANDA v20 pricing adapter (live feed)
tests/ pytest suite
api/scan.py Vercel Python function: GET /api/scan[?currency=EUR]
index.html single-page UI (no build step, no dependencies)
docs/ screenshots used in this README
Requirements: Python 3.10+ and, for the tests, pytest.
# Run the unit tests
python3 -m pip install pytest
python3 -m pytest fx_arb/tests
# Generate synthetic quotes with a few injected mispricings
python3 -m fx_arb.synth quotes.csv --ticks 5000 --seed 1
# Replay them through the detector
python3 -m fx_arb.replay quotes.csv --fee-bps 0.25 --min-bps 1
CSV columns: ts,base,quote,bid,ask,bid_size,ask_size. Prices are quote per unit of base (EURUSD = 1.10). Sizes are in base units. ts is epoch seconds.
The detector also runs directly from Python:
from fx_arb.detector import ArbDetector
from fx_arb.quotes import Quote
book = {"EURUSD": Quote(ts, "EUR", "USD", 1.1000, 1.1002, 1e6, 1e6), ...}
for opp in ArbDetector(fee_bps=0.5, min_profit_bps=0.5).scan(book, now=ts):
print(opp.path, round(opp.profit_bps, 2), round(opp.max_notional))
The API and the page are served by the same function, so the simplest local check is a small server that serves index.html and calls api/scan.py. Without OANDA credentials it returns simulated quotes, clearly labelled as such.
To use live prices, see the next section.
The live feed uses OANDA's v20 REST API with a free practice account. Set these environment variables:
| Variable | Required | Meaning |
|---|---|---|
OANDA_TOKEN | for live | Personal access token from the OANDA practice account |
OANDA_ACCOUNT_ID | for live | Practice account ID |
OANDA_ENV | no | practice (default) or live |
FEE_BPS | no | Fee per leg in basis points (default 0.5) |
MAX_AGE_S | no | Ignore quotes older than this many seconds (default 5) |
Without OANDA_TOKEN and OANDA_ACCOUNT_ID, the function falls back to simulated data. The page labels which mode it is in.
The live feed asks OANDA which instruments the account offers and keeps the pairs among our 13 currencies. Pairs the account doesn't offer, such as some INR crosses, are skipped automatically. That list is cached for an hour.
Assumptions to verify against a live response:
max_notional./v3/accounts/{id}/instruments) is read as a list of objects with name and type fields, with type equal to CURRENCY. This is from OANDA's documentation, not a live response. If the live scan returns no pairs, check this first.The repo is set up for Vercel's Python runtime with no build step and no third-party Python dependencies.
npx vercel login
npx vercel deploy --prod
Add the OANDA variables under the project's environment settings (or with npx vercel env add OANDA_TOKEN production), then redeploy.
max_notional is the largest starting amount that fits the best quoted size on every leg. It does not model order-book depth beyond the top level.No license has been chosen yet. Until one is added, all rights are reserved by the author.