Architecture
wickworks is one Python process: a FastAPI app with an MCP server mounted inside it. Nothing is stored and nothing is called over the network. A request is a pure function of its body.
┌──────────────┐ ┌───────────────────────┐ ┌──────────────────────┐
│ your app │───▶│ POST / or MCP │───▶│ ComputeRequest │
│ (any lang) │ │ compute │ │ validation │
└──────────────┘ └───────────────────────┘ └──────────┬───────────┘
│
▼
┌──────────────┐ ┌───────────────────────┐ ┌──────────────────────┐
│ camelCase │◀───│ registry functions │◀───│ pre-flight: types, │
│ JSON │ │ pandas-ta, SMC pass │ │ params, min bars │
└──────────────┘ └───────────────────────┘ └──────────────────────┘
Request path
- Entry.
server.pyservesPOST /;mcp_server.pyserves the MCPcomputetool. Both validate the body with the same Pydantic model,ComputeRequestinschemas.py, and checkMAX_BARSand thatbarsandindicatorsare not empty. REST validates first (FastAPI does it before the handler runs); MCP checks the counts first. - Pre-flight.
compute_dataframeincompute.pyturns each entry ofindicatorsinto a type and parameters, rejects unknown types, checks the parameters (validation.py), and works out each one’s minimum bars (requirements.py). It collects every shortfall before failing, so one error lists them all. Nothing is computed until every entry passes. - Compute. Each entry runs its function from
INDICATORSinregistry.py. Series indicators callpandas-taon the bar columns directly. SMC objects and summaries read from the shared analysis pass. - Serialize. The result goes through
NumpyEncoder, so numpy and pandas scalars become plain JSON, and every key is rewritten to camelCase.NaNand infinity were already turned intonull.
REST runs the computation in FastAPI’s thread pool; MCP runs it with asyncio.to_thread. Either way a slow request does not block others in the same worker.
The shared analysis pass
Order blocks, FVGs, BOS/CHoCH, swing levels, S/R levels, the recent range and the six summaries all come from one analysis of the full history. A per-request Context object runs it the first time an output needs it and keeps the result for the rest of the request.
The pass first adds a fixed set of columns to the bar table (indicators/): EMA 21, SMA 50/100/200, ATR 14, RSI 14, MACD, Bollinger, MFI, Stochastic, ADX, VWAP, Donchian, volume ratios and OBV, the smartmoneyconcepts swing, order block, FVG and BOS/CHoCH columns, and a 7-bar swing structure. smc.py then builds the objects and summaries from those columns.
liquidity, previousHighLow, sessions and retracements take parameters, so they call smartmoneyconcepts separately instead of using the pass.
smc_fast
smartmoneyconcepts spends most of its time in Python loops. smc_fast.py replaces the library’s ob, fvg, bos_choch and swing_highs_lows with numba-compiled versions when the indicators package is imported. It is a port of the MIT-licensed upstream code (see THIRD_PARTY.md), and parity tests prove it returns the same values. Setting DISABLE_SMC_FAST turns the replacement off.
Determinism
The same bars and indicators always produce the same response bytes. There is no randomness, clock or state in the computation, and the container runs TZ=UTC. The test suite checks determinism, that adding new bars does not change past values of causal indicators, and that requesting two outputs together gives the same values as requesting each alone.
Because of that, wickworks scales by adding replicas behind any load balancer, with no affinity.
Why camelCase and null
Every consumer, whatever its language, gets one naming style and valid JSON. Python’s json module would otherwise write NaN tokens that strict parsers reject. Series keep full float precision so very small prices survive; summaries round to six decimal places.
Terms
| Term | Meaning |
|---|---|
| Output key | A key of indicators in the request, and the matching key in the response |
| Type | The indicator a spec runs: the key itself, or the spec’s type field |
| Series | One value per bar, aligned with bars |
| Warmup | The leading bars of a series that are null because the indicator needs more history |
| Analysis pass | The single SMC and summary computation shared by a request |