(deps: CalcDeps)
| 13 | import { runScript, type CalcDeps } from '@/domain/analysis/calc-v2/index' |
| 14 | |
| 15 | export function createQuantTools(deps: CalcDeps) { |
| 16 | return { |
| 17 | searchBars: tool({ |
| 18 | description: `Find K-line sources for a symbol — returns barIds to paste into calculateQuant's bars(...). |
| 19 | |
| 20 | Federates connected brokers (alpaca-paper, binance-readonly, …) AND vendors (fmp, yfinance). |
| 21 | Candidates come back freshest-first. Each carries: |
| 22 | - barId: use directly, e.g. bars("<barId>", "1d", count=250). |
| 23 | · broker barIds ("accountId|symbol") need NO asset= in bars(). |
| 24 | · vendor barIds ("provider|symbol") need asset="equity|crypto|currency|commodity". |
| 25 | - source: "uta" (broker) | "vendor" |
| 26 | - barCapability: "realtime" | "delayed" | "iex" | "subscription". |
| 27 | Source preference: a broker you actually trade (realtime, and the chart matches your fills) > |
| 28 | a paid vendor (fmp, …) > yfinance. yfinance is a FREE FALLBACK only — its end-of-day bars can |
| 29 | lag a day or two, so don't use it for anything time-sensitive or to chart a live position when |
| 30 | a broker source exists. The same asset appears from multiple sources (redundancy is expected); |
| 31 | default to the freshest broker candidate.`, |
| 32 | inputSchema: z.object({ |
| 33 | query: z.string().describe('Symbol or keyword, e.g. "AAPL", "BTC", "bitcoin"'), |
| 34 | limit: z.number().int().positive().optional().describe('Max candidates (default 20)'), |
| 35 | }).meta({ examples: [{ query: 'AAPL' }] }), |
| 36 | execute: async ({ query, limit }) => { |
| 37 | const candidates = await deps.barService.searchBarSources(query, limit != null ? { limit } : undefined) |
| 38 | return { candidates, count: candidates.length } |
| 39 | }, |
| 40 | }), |
| 41 | |
| 42 | calculateQuant: tool({ |
| 43 | description: `Run a technical-analysis script over K-lines from explicit sources (barId-keyed). |
| 44 | Get barIds from \`searchBars\` first (or \`searchContracts\` for broker-only). |
| 45 | |
| 46 | A script is one or more \`name = bars(...)\` bindings followed by a final result expression: |
| 47 | |
| 48 | s = bars("alpaca-paper|AAPL", "1d", count=250) |
| 49 | sma(s.close, 50) - sma(s.close, 200) |
| 50 | |
| 51 | bars(barId, interval, count=, asOf=, start=, end=, asset=): |
| 52 | - barId: "{source}|{symbol}" from searchBars (broker, e.g. "alpaca-paper|AAPL", |
| 53 | "binance-readonly|BTC/USDT") or a vendor ("yfinance|AAPL", "fmp|AAPL"). Prefer a broker |
| 54 | barId for anything you trade or anything time-sensitive — yfinance is a delayed free |
| 55 | fallback (EOD bars can lag a day or two). |
| 56 | - interval: "1m" "5m" "15m" "30m" "1h" "4h" "1d" "1w". |
| 57 | - count=N: number of most-recent bars (the natural window for indicators). |
| 58 | - asset=: REQUIRED for vendor barIds — "equity" | "crypto" | "currency" | "commodity". |
| 59 | (Broker barIds infer it.) |
| 60 | Series columns: s.open / s.high / s.low / s.close / s.volume |
| 61 | Functions: sma(series, n), ema(series, n), stdev(series), max/min/sum/average/median(series), |
| 62 | rsi(series, n=14), bbands(series, n, std), macd(series, fast, slow, signal), |
| 63 | atr(high, low, close, n), rvol(volume, n=20), obv(close, volume), |
| 64 | mfi(high, low, close, volume, n=14), vwap(high, low, close, volume), |
| 65 | roc(series, n) [% change over n], zscore(series, n?) [how extended vs trailing window], |
| 66 | slope(series, n) [linreg trend, signed/rankable], correlation(seriesA, seriesB) [−1..1], |
| 67 | highest(series, n), lowest(series, n). |
| 68 | Indicators return the LATEST value directly (a scalar) — do NOT index them. |
| 69 | Only raw columns are series: index them with s.close[-1] (latest), s.close[-n] (n-back). |
| 70 | Arithmetic: + - * /. |
| 71 | |
| 72 | Panels — batch many computations in ONE call (avoids calling this tool N times). |
no test coverage detected