Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A small, dependency-free Python module for recording trade executions and measuring what they cost: commissions, fees, and slippage against a benchmark price.
The benchmark can be whatever you measure against: arrival mid, decision price, VWAP, and so on.
The package also tracks tokenised FX: it quotes EURC/USDC on Base from Uniswap v3, Uniswap v4, Aerodrome and the 0x aggregator. It then compares each quote with the EUR/USD reference mid and stores the results in SQLite.
Each cycle prints the following for evREADME 전문 보기 ↗A small, dependency-free Python module for recording trade executions and measuring what they cost: commissions, fees, and slippage against a benchmark price.
For each execution:
| Metric | Definition |
|---|---|
explicit_cost | commission + fees |
slippage_cost | (fill price − benchmark price) × quantity, signed so a worse fill is a positive cost for both buys and sells |
total_cost | explicit cost + slippage |
*_bps | the cost as basis points of benchmark notional |
The benchmark can be whatever you measure against: arrival mid, decision price, VWAP, and so on.
pip install -e ".[dev]"
from execution_cost_tracker import Execution, ExecutionCostTracker, Side
tracker = ExecutionCostTracker()
tracker.record(Execution("AAPL", Side.BUY, 100, price=190.10, benchmark_price=190.00, commission=1.00, venue="BrokerA"))
tracker.record(Execution("AAPL", Side.SELL, 50, price=191.00, benchmark_price=191.20, commission=0.50, venue="BrokerB"))
summary = tracker.summary()
print(summary.total_cost, summary.total_cost_bps)
# Break costs down by symbol, venue, side, or any function of an execution
for venue, s in tracker.summary_by("venue").items():
print(venue, round(s.total_cost_bps, 2))
# Narrow down before summarising
aapl_buys = tracker.filter(symbol="AAPL", side="buy")
# Save and reload
tracker.to_csv("executions.csv")
tracker = ExecutionCostTracker.from_csv("executions.csv")
The package also tracks tokenised FX: it quotes EURC/USDC on Base from Uniswap v3, Uniswap v4, Aerodrome and the 0x aggregator, plus Coinbase's EURC-USDC order book. It then compares each quote with the EUR/USD reference mid and stores the results in SQLite.
pip install -e ".[dev]"
export BASE_RPC_URL=https://... # optional; defaults to the public Base RPC
export ZEROX_API_KEY=... # optional; needed for the 0x venue
export COINBASE_API_KEY=... COINBASE_API_SECRET=... # optional; Coinbase book (public endpoint otherwise)
python -m execution_cost_tracker.run --sizes 1000,10000,100000
Each cycle prints the following for every venue and size:
Failed venues are listed with their errors and never stop the cycle. Everything is written to fxtracker.db; pass --no-store to skip writing.
The reference price is the latest EUR/USD price received from TradingView (see "Market mid from TradingView" below). It's used to size buy quotes, hide outliers, and centre the chart. Over a weekend that's Friday's last price. Until TradingView has sent anything, the app estimates a mid from the venues' own prices.
app.py at the repo root is the entrypoint Vercel looks for. It loads the FastAPI app in src/execution_cost_tracker/api.py:
| Endpoint | What it does |
|---|---|
GET / | The front-end page (public/index.html) |
GET /api/health | Liveness check |
GET /api/config | Chains, tokens, pairs, venues and limits the front end builds its form from |
GET /api/pairs | One entry per pair and chain, with token details |
POST /api/route | What the page uses: prices a conversion end to end, from a start stablecoin and chain to an end stablecoin and chain, through every venue. Not stored; same rate limits and cache. |
POST /api/quote | Single-chain quote for one pair, not stored. Rate limited and briefly cached; see below. |
POST /api/tradingview/webhook | TradingView alert pushes the live EUR/USD price. Secret in the message; TradingView's IPs only. |
GET /api/cron | Scheduled run: quotes every pair, venue and size, then stores everything. Requires Authorization: Bearer <CRON_SECRET>. |
An example POST /api/route body (every field is optional):
{"from_token": "USDC", "from_chain": "tempo", "to_token": "EURC", "to_chain": "solana",
"amount": 50000, "venues": ["coinbase", "uniswap_v3", "aerodrome"]}
See "Routes" below for what comes back. An example POST /api/quote body (every field is optional):
{"pair": "EURC/USDC", "notional": 25000, "side": "both",
"source_chain": "base", "destination_chain": "base",
"venues": ["uniswap_v3", "aerodrome", "zerox"]}
side is buy, sell or both. destination_chain defaults to source_chain. Different source and destination chains are rejected for now, because cross-chain routes aren't built yet. venues defaults to every venue that works on the chain.
FastAPI also serves interactive docs at /docs, where you can try requests in the browser.
To run it locally:
pip install -e ".[dev]"
uvicorn app:app --reload # then open http://127.0.0.1:8000 (page) or /docs
config.yamlconfig.yaml at the repo root lists the chains, tokens (address and decimals) and pairs. It also sets the size limits, guardrails and the sizes the scheduled run quotes. The API only accepts what's listed there, and the front end's dropdowns are built from it. To add a token or pair on Base, edit the file and push; no code changes are needed. The app checks the file on startup and refuses malformed addresses, unknown tokens or chains, and duplicate pairs.
The "vs mkt mid (bps)" column compares each venue's price with the live EUR/USD mid while the interbank FX market is open, from Monday 05:00 Sydney to Friday 17:00 New York. Outside those hours, or if the latest price is more than 3 minutes old, it shows N/A, and hovering over a cell shows why. Positive means worse than mid.
TradingView has no API for pulling prices out of an account. Instead, TradingView pushes the price to this app about once a minute through a webhook. There are two ways to set it up. Both start with:
TRADINGVIEW_WEBHOOK_SECRET with a long random string, for example from python3 -c "import secrets; print(secrets.token_hex(32))". Then redeploy.Option A (recommended): true mid from bid and ask, using the Pine script. TradingView alert messages have no {{bid}} or {{ask}} placeholders. If you type them, TradingView sends them unfilled and the app rejects the message with an explanation. Pine scripts can read bid and ask, but only on the 1-tick chart, so tradingview/fx_tracker_bid_ask.pine builds the message itself:
tradingview/fx_tracker_bid_ask.pine, Save, then Add to chart on EUR/USD.bid and ask. On any other chart (1 second, 1 minute, …) bid and ask are empty, so it sends the chart's latest price instead. That's the same as Option B, but via the script. Tick charts aren't on every TradingView plan.https://YOUR-APP.vercel.app/api/tradingview/webhook. Leave the Message box alone; the script supplies the message.The script sends at most once a minute (adjustable in its settings). The page headline then shows "market mid from TradingView, average of bid … and ask …".
Option B (simpler): the chart price. On a 1-minute EUR/USD chart, create an alert with Condition = EURUSD Greater Than 0, Trigger = Once Per Bar Close, Webhook URL as above, and this Message (with your secret):
{"secret": "YOUR_SECRET", "ticker": "{{ticker}}", "price": {{close}}, "time": "{{timenow}}"}
{{close}} is the chart's latest price. Depending on the feed, that may be a mid or a bid; see "how to check" below.
Check it's working: during market hours, wait a minute or two and run a quote. TradingView's alert log has a "Webhook status" column if anything fails.
Notes:
config.yaml) and checks the secret in the message. Wrong secret → 401; wrong address → 403."price" or "bid" plus "ask"; with bid and ask it stores their average as the mid and keeps both for display.market_mid in config.yaml: how old a price can be, which TradingView ticker maps to which pair, and the IP allowlist.The coinbase venue prices a trade by walking Coinbase's EURC-USDC order book, as if you sent a market order:
If the book's depth can't fill the size, the venue reports a failure saying how much it could fill. The page's route tooltip shows how many levels were used; the API's meta also has the top-of-book price, the worst level hit and the slippage from top of book. The pure walk lives in orderbook.py.
Settings under venue_params.coinbase for the pair in config.yaml:
product_id: the Coinbase product (EURC-USDC). If Coinbase rejects it, try EURC-USD.depth_limit: how many levels to request (default 500). Raise it if large sizes run out of depth.taker_fee_bps: your Coinbase taker fee, deducted from what you receive. It's 0 by default, so prices are before fees.With COINBASE_API_KEY and COINBASE_API_SECRET set, the book comes through Coinbase's SDK (coinbase-advanced-py). Without them it uses Coinbase's public product-book endpoint. A secret pasted on one line with literal \n is accepted.
The page asks for the stablecoin you start with and its chain, and the stablecoin you want and the chain it should arrive on. Chains: Ethereum, Solana, Base, Arbitrum, Polygon and Tempo. For every venue that trades the pair, routes.py builds and prices a route in three steps:
What each bridge step does:
eth_gasPrice), valued with Coinbase's ETH-USD, POL-USD and SOL-USD prices.Coinbase rules, all in config.yaml under routing.coinbase:
bridge_hubs (Base, Arbitrum, Ethereum).tempo to networks.USDC to deposit and withdraw directly.withdrawal_fees. Deposits are free.Taker fee: venue_params.coinbase.taker_fee_bps is 0.45 bps. That's Coinbase Exchange's published stable-pair taker rate for standard users (EURC-USDC is a stable pair). Coinbase only shows the Advanced Trade retail stable-pair rate on your logged-in fee page (Advanced, then Fees, then Stablecoin pairs). Put your rate there. EURC-USDC is currently limit_only on Coinbase, so a real taker sends a marketable limit order. The cost is the same as the order-book walk.
Known gaps (the page says so per route instead of guessing):
tokens under routing.bridges.routing.gas_units). Uniswap, 0x and Relay give their own figures, which are used instead.Keys, set in Vercel's environment variables:
RELAY_API_KEY (from dashboard.relay.link): Relay requires one from 2 October 2026.LAYERZERO_API_KEY: needed for LayerZero quotes. Without it, LayerZero shows "set LAYERZERO_API_KEY" and the other bridges are used.ACROSS_API_KEY: optional.ETHEREUM_RPC_URL, ARBITRUM_RPC_URL, POLYGON_RPC_URL, TEMPO_RPC_URL and BASE_RPC_URL. Without them, the public RPCs listed in config.yaml are used.Quotes priced more than 2% above or below the latest market price are left out of the page's table, chart and "best" picks, and listed under a "hidden" note instead. They usually come from pools too thin for the size requested. The check runs fresh on every request, so a venue reappears as soon as its price is back within 2%. The API returns hidden quotes in excluded, with offset_pct showing how far off they were. Change the threshold with quality.max_distance_from_mid_pct in config.yaml. The scheduled run still stores every quote, so the history includes outliers.
/api/quoteBecause the page is public, each quote request costs RPC calls and 0x API calls:
limits.min_notional / limits.max_notional in config.yaml. The MAX_NOTIONAL env var overrides the maximum.guardrails.per_client_per_minute (per visitor IP) and guardrails.global_per_minute. Over the limit, the API returns HTTP 429 with a Retry-After header.guardrails.cache_seconds gets the previous answer without new RPC calls, and doesn't count against the limits. Failed results aren't cached.The rate limits and cache live in memory, so on Vercel they apply per running instance. They stop casual abuse. For hard limits, add a rate-limit rule in Vercel's Firewall or a shared store such as Redis.
To deploy:
main then redeploys.BASE_RPC_URL: your Base RPC provider URLZEROX_API_KEY: from dashboard.0x.orgRELAY_API_KEY, LAYERZERO_API_KEY (and optionally ACROSS_API_KEY): bridge quotes for routes (see "Routes")ETHEREUM_RPC_URL, ARBITRUM_RPC_URL, POLYGON_RPC_URL, TEMPO_RPC_URL: optional provider RPCs for gas prices and Ethereum quotesCOINBASE_API_KEY and COINBASE_API_SECRET: optional, a Coinbase Developer Platform key with view access (see "Coinbase order book")CRON_SECRET: a random string of 16+ charactersTURSO_DATABASE_URL and TURSO_AUTH_TOKEN: from a Turso databaseMAX_NOTIONAL: optional cap on quote size (default 1,000,000)TRADINGVIEW_WEBHOOK_SECRET: shared secret for the TradingView market-mid webhook (see below)vercel.json schedules /api/cron daily at 12:00 UTC. That is the most often the Hobby plan allows. On Pro, change the schedule (for example */15 * * * *) and push.Storage uses Turso when TURSO_DATABASE_URL is set and a local fxtracker.db otherwise. On Vercel it refuses to fall back to a local file, because files there don't persist.
pytest