Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
거래 신호가 다음 움직임을 예측하는지 테스트하고, 실시간 Yahoo Finance 데이터로 정보계수·감쇠·분위 분석을 수행합니다
Test whether a trading signal actually predicts the next move. Real quant factor research — information coefficient, decay and quintile analysis — on live Yahoo Finance data.
금융TypeScript · CSSGitHub @hoshsmith244-ops온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A quant factor research tool. Pick a stock, pick a factor, and find out whether
it actually predicts the next move — measured the way a quant fund measures it,
using real market data.
An introduction to quant research, built as a runnable Next.js app.
Then open.
Node runtime. This machine had no Node installation, so a portable build
(Node 24.21.0) was extracted to %LOCALAPPDATA%\nodejs-portable and added to
your user PATH. A new terminal picks it up automatically. If a shell still
cannot find node, run dev.cmd in this folder — it pins the path first.README 전문 보기 ↗A quant factor research tool. Pick a stock, pick a factor, and find out whether it actually predicts the next move — measured the way a quant fund measures it, using real market data.
An introduction to quant research, built as a runnable Next.js app.
npm run dev
Then open http://localhost:3000.
Node runtime. This machine had no Node installation, so a portable build (Node 24.21.0) was extracted to
%LOCALAPPDATA%\nodejs-portableand added to your userPATH. A new terminal picks it up automatically. If a shell still cannot findnode, rundev.cmdin this folder — it pins the path first.
| Command | What it does |
|---|---|
npm run dev | Dev server on port 3000 |
npm run build | Production build |
npm run start | Serve the production build |
npm run test | Bar hygiene, pre-close window, sync merge, edge model, trade record and options-maths suites |
npm run check | typecheck + lint + test + build |
npm run deploy | Pre-deploy gate, then commit and push (Git Bash) |
| Route | Purpose |
|---|---|
/ | Landing page |
/dashboard | The tool. Opens in Simple mode; Advanced holds the statistics |
/screener | Every strategy against every ticker, filtered by strategy, company and risk |
/watchlist | Saved ticker + strategy pairs, scanned in one pass |
/notifications | Entry and exit changes across the watchlist |
/settings | Appearance — accent, direction colours, background, grid |
/guide | How to use it, and how to read every statistic |
/membership | Account area and tiers (UI only — see below) |
/api/history | Daily OHLCV for one ticker (GET) |
Prices come from Yahoo Finance via yahoo-finance2
— the same endpoints the Python yfinance package reads, in TypeScript so the
app stays on one runtime and deploys to Vercel without a Python function.
Any ticker Yahoo carries works: AAPL, BRK-B, BTC-USD, ^GSPC, VOD.L,
EURUSD=X. Three years of daily bars, fetched once per symbol and cached at the
edge for an hour.
src/lib/market.ts is server-only (yahoo-finance2 needs node:module and
cannot be bundled for the browser). Anything the client needs — the ticker list,
shared types — lives in src/lib/symbols.ts.
The hero panel on / is not a screenshot. It runs the real engine on AAPL at
request time and prints the current signal, dated. The page is prerendered with
revalidate = 3600, so it refreshes hourly and costs one Yahoo call an hour no
matter how much traffic it gets. If the provider is unreachable at build or
revalidate time it falls back to describing the tool rather than showing numbers
that were never true.
The terminal also shows the extended-hours price and recent headlines, because a signal computed on the daily close cannot see an earnings release at 16:05. When the after-hours move exceeds 2%, the signal is flagged as possibly out of date.
None of it touches the maths, for two reasons:
Headlines are labelled as context, not cause. A story published near a price move is a hypothesis; the interface says so rather than asserting a reason. Building a real news factor would need point-in-time historical news with timestamps and sentiment — a different data provider, not Yahoo.
/screener answers the question the terminal cannot: which ticker should I
even be looking at? It runs all five price factors against the universe at
their default settings and lets you filter the results three ways:
| Group | Filters |
|---|---|
| The strategy | verdict, factor, signal now, minimum trades, beat holding out of sample, survives the stress test, hide strategies that still lost money |
| The company | sector, industry, market cap, P/E, dividend, profitable or not, revenue growing or shrinking |
| Risk and tradeability | liquidity (daily dollar volume), volatility, beta, short interest |
Simple mode shows four of these; the rest live behind More filters in
advanced mode. Every bucket carries its ticker count (Mid · $2–10B (46)) so an
empty slice announces itself rather than silently returning nothing.
Liquidity is the one that matters most and the one a conventional screener never has. Every backtest here charges a flat 10 bps round trip, which is only honest on a name that actually trades; below roughly $10M a day the real spread is wider than the modelled cost and the edge shown is partly fiction.
Analyst ratings and price targets are deliberately absent. They are opinions, not measurements, and nothing else on this page is an opinion.
It is precomputed, not live. The scan is a thousand backtests and takes
about three minutes — far too slow per request, and pointless, because daily
bars only move once a day. scripts/build-screen.mts writes
public/screen.json (399 KB, ~62 KB gzipped) and the page fetches that static
file. No Yahoo traffic, no function timeout, nothing added to the JS bundle.
Rebuild it after the close:
node scripts/build-screen.mts
Two rules the script enforces, both of which a conventional screener breaks:
Because scanning a thousand combinations is exactly the multiple-testing trap the guide warns about, two defences are on by default: results must survive a raised cost (25 bps) and a parameter shift of ±20%, and the page shows how many results chance alone would produce next to how many actually appeared.
/settings has five controls: accent colour, direction colours, background,
grid weight, and an optional lattice texture. Each is a CSS custom-property
override keyed off a data-* attribute on <html> (src/lib/appearance.ts →
globals.css), so adding a palette is a few lines of CSS and no component
changes at all.
Direction colours earn their place. About one man in twelve cannot reliably separate red from green — on a page where that distinction is the information — so a blue/orange palette is offered. "Red up" is the East Asian convention, not a gimmick.
Settings are applied before first paint by a small inline script in the root
layout, so a non-default theme never flashes the default. That follows the
pattern in next/dist/docs/01-app/02-guides/preventing-flash-before-hydration.md:
an InlineScript helper that renders as a real script on the server and inert
text/plain on the client, plus suppressHydrationWarning on <html>.
Naming wart, on purpose.
--color-amberis the accent slot, not a promise of the colour amber —text-amberrenders cyan when cyan is selected. Renaming it would touch nearly every component for no user-visible gain.
The screener rebuild runs as a GitHub Action on weekdays at 21:30 UTC. It
commits public/screen.json and public/iv-cutoffs.json (the day's
cross-sectional IV rankings behind the Options tab's rich/cheap badge), which
triggers a deploy. Scheduled runs on GitHub are routinely late, which does not
matter here — the rebuild only has to happen some time after the bell. It
refuses to commit a result that fails a sanity check, because a run where every
fetch failed would still produce valid JSON.
Notifications are in-app only: the bell, and desktop notifications while a tab is open. Nothing is emailed and no server job contacts users.
Every strategy reads the daily close, so by the time a signal exists the price it refers to is gone. The only way to take the trade the backtest measured is to decide shortly before the bell and send a market-on-close order.
That window is bounded at both ends, and the app enforces both:
| US equities | What happens | |
|---|---|---|
| Window opens | 3:40pm | Watchlist alerts fire; the terminal shows a live countdown |
| On-close cutoff | 3:50pm | Exchange stops accepting MOC/LOC orders — alerts stop too |
| Close | 4:00pm | The bar settles; entries and exits are recorded for the record |
Earlier in the session a provisional reading is a guess about hours of trading still to come, so it is shown but explicitly marked too early, and no alert is sent. That restraint is the feature — alerting all day would train people to act on noise.
Stand-downs. If a price moves back inside the window and the rule stops triggering, the earlier alert is withdrawn. An alert that is never taken back walks the user into a trade the strategy does not want, which is worse than never alerting.
Times come from each venue's own trading calendar
(currentTradingPeriod.regular.end), not an assumed 16:00 — so London's 4:30pm
close puts the window at 4:10–4:20pm and half-day holidays shift automatically.
Crypto reports a 24-hour session and is flagged alwaysOpen, where none of this
applies.
The logic is time-dependent and alive for twenty minutes a day, so it is covered by a test rather than by looking at the running app:
node scripts/check-window.mts
These are exchange cutoffs. Brokers often impose earlier ones and some retail brokers do not offer on-close orders at all, which the UI says rather than assuming.
/watchlist scans every ticker you save in one pass and reports which are
signalling now, how long they have been in that state, and whether the signal
has any evidence behind it.
Each row carries its own strategy, not just a ticker. That is the point: a factor that works on one name usually does not work on another, so a watchlist of bare tickers would throw away the conclusion the research surface exists to reach. The Watch button in the terminal saves whatever you are currently looking at, strategy included, and clicking a row opens it back up with that preset loaded.
Fetches are pooled six at a time and each ticker is edge-cached for an hour; measured at roughly 210ms to fetch eight cold tickers and 18ms per ticker to analyse.
The terminal opens in Simple mode, which answers the question a newer trader actually has: is this strategy holding this stock right now, when would it have bought, how have its past trades gone, and is there any evidence it works. One plain-English verdict, a price chart with entry and exit points marked, and a table of every trade.
Advanced mode exposes the research surface underneath — information coefficient, significance, decay, quintile ladder, equity curve — plus the lookback and forward-horizon controls. The choice is remembered per browser.
src/lib/alpha.ts is the engine. A factor turns price history into one
score per day; the engine then measures that score against forward returns —
returns it could not have seen.
Five factors ship: momentum, short-term reversal, risk-adjusted momentum, distance from trend, and volume thrust.
That is the correct answer most of the time, and reading it is the point. A negative IC means the factor is inverted on that asset — high scores precede weaker returns — which is still information, and the interface says so rather than dressing it up. Momentum on AAPL over the current window has an IC around −0.14 with a t-stat near −3.4: a real effect, pointing the opposite way to the textbook thesis.
/membership offers an optional sign-in that carries your watchlist,
appearance settings and notification history between devices. It is off
unless configured, and the app is fully usable without it — with no Supabase
project set up, the panel says so and nothing else changes.
There is no password. Sign-in is a one-time link emailed to you (Supabase magic link), so the app never asks for, handles or stores one.
Enable it in about ten minutes:
supabase/schema.sql → Runanon public key into
NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY<origin>/auth/callback as a
redirect URL, for every origin you use (localhost AND production)Deploying it. The CSP is assembled at BUILD time from
NEXT_PUBLIC_SUPABASE_URL, so the variables must exist in the host before the
build runs, and setting them without redeploying produces a "Failed to fetch"
on sign-in with nothing in the console. The magic-link redirect needs no
configuration — it uses the browser's own origin, so localhost, preview
deployments and production each send links back to themselves.
Step 2 is not optional. It creates the table and the row-level security
policies, and those policies are the only thing making the anon key safe to ship
in a browser bundle. Never use the service_role key here — it bypasses RLS.
Conflicts: collections merge, preferences take the newer side. Watchlists
are unioned by symbol:factor, so adding a ticker on your laptop can never
delete one added on your phone; notification history is unioned by event id with
read-state sticky. Only singular preferences (theme, mode, cost setting) follow
last-write-wins.
Once signed in, changes push automatically in the background (a change check
every few seconds, plus one when the tab is hidden or closed). Sync now on the
account panel does a full pull-merge-push and is only needed to pick up changes
made on another device.
The merge rules are the only code here that can silently destroy something you made, so they are covered by a test rather than by inspection:
node scripts/check-sync.mts
What deliberately does not sync is the per-device scan bookkeeping in
qp:signal-state and qp:pending-alerts. Those record what a given browser has
already seen; copying them across would either suppress an alert the second
device should raise or replay one already dealt with. A new device records its
baselines silently, which is the correct behaviour.
/membership renders the account area and tiers, but nothing is wired.
There is no authentication, no database, no billing, and the sign-in form does
not submit anywhere. The terminal is fully usable without an account.
A terminal-leaning interface: near-black surfaces (#07090D), visible hairline
grid, square corners, amber (#FFB020) as the primary accent rather than blue,
and JetBrains Mono as the default face — prose passages opt back into Geist via
.prose-face. Every figure is tabular so columns align digit-for-digit. Tokens
live in src/app/globals.css under @theme.
Next.js 16 (App Router, Turbopack) · React 19 · TypeScript · Tailwind CSS v4 · Recharts · Lucide icons · yahoo-finance2.
Educational research tool. Hypothetical results exclude costs and slippage, and past behaviour does not predict future returns. Nothing here is investment advice.