Open source · MIT · Rustis_largest(sma(close, 2), 3)
Ready. Runs in your browser on real data: 10 US large-caps, 2014–2017.
Run this on your own data
The demo runs on 10 names, 2014–2017. The same strategy is a file you run on your universe — same engine, no browser ceiling.
- 1 · Install the CLI (macOS / Linux)
curl -fsSL https://citrusquant.com/install.sh | sh - 2 · Save as
strategy.lemon#! universe: 20180101..20241231 #! symbols: AAPL, MSFT, NVDA, AMZN, GOOGL, META, JPM, XOM #! config: { "fee_ratio": 0.001 } #! data-source: fmp # Hold whichever names have the highest 2-day average close. # Edit this and press Run (or Ctrl/Cmd+Enter). is_largest(sma(close, 2), 3) - 3 · Run it (bring your own key)
export FMP_API_KEY=… lemon strategy.lemon --sync
The #! lines are front-matter — the window, universe, fee, and the vendor --sync may fetch from; a .lemonfile makes no network request on its own. Full walkthrough →
Real data, honestly sourced. Daily bars for 10 US large-caps (AAPL, NVDA, XOM, …), Nov 2014 – Nov 2017, from a CC0 public-domain dataset; P/E derived from SEC EDGAR filings and visible only from each 10-K’s filing date — no look-ahead. Why data ending in 2017? It’s the newest daily dataset that’s genuinely free to redistribute — the fine print.
Try an idea
Section titled “Try an idea”Each chip loads into the editor above and re-runs — watch how the metrics move. The language has ~60 built-in ops: moving averages, MACD / ADX / Bollinger, cross-sectional ranks, industry neutralization, volatility targeting, stateful entry/exit rotation. The whole spec fits on one page.
How it’s built
Section titled “How it’s built”A pure function
The core is I/O-free: (strategy spec, data panels) → Report.
No database, no network, no hidden state — which is what makes it trivially
embeddable, and easy to test.
One core, two targets
The same crate compiles native (batch backtests parallelized with Rayon) and to WASM. This page and the published crates run the identical engine — there is no JavaScript reimplementation to drift.
A DSL, not a framework
lemon parses to a JSON expression tree; the engine evaluates it over price/fundamental panels. Even this page’s syntax highlighting comes from the engine’s own lexer, compiled to WASM.
Bring your own data
The engine ships no market data and never phones home. Feed it panels from any source you’re licensed to use — see the data guide.
A strategy is a file
Section titled “A strategy is a file”A complete strategy — “hold the 30 cheapest names in the S&P 500, point-in-time” — is a single text file, not a class to subclass or a notebook whose result depends on cell-execution order:
#! universe: 20180101..20241231#! index: sp500#! config: { "fee_ratio": 0.001 }#! data-source: fmpis_largest(rank(-pe), 30)- Diffable & reviewable. It’s text — commit it, open a PR on it, paste it into an issue. A strategy change is a line change.
- Reproducible bit-for-bit. The file carries its own
window, universe, and fees, so a colleague running it on the same data gets
the same
Report— native or WASM. - No surprise I/O. A
.lemonfile can’t touch the network on its own; data is fetched only when you pass–syncwith your key. You share a strategy, never a credential or a side effect.
The alternatives make a strategy code: backtrader a ~200-line Python class, vectorbt a notebook with hidden state, Lean a whole C# project. lemon makes it data — which is why the same file runs in your terminal, in CI, and in the browser above. Run one on your data →
Write it with real tooling
Section titled “Write it with real tooling”Highlighting, hover, completion, and lint all come from the engine’s own lexer and parser — one source of truth, so your editor never drifts from the language:
- In this page. The playground editor above highlights, completes, shows op signatures on hover, and lints as you type — the engine compiled to WASM.
- In your editor. The
lemon-lsplanguage server brings the same hover, completion, and diagnostics to any LSP-capable editor. - In VS Code. The lemon extension wires it up — TextMate highlighting with no server, and the language server auto-downloads.
Get it
Section titled “Get it”Use the tool, or embed the engine. The lemon CLI runs a .lemon file
end-to-end from your terminal; the language bindings drop the same engine into
your Rust, Python, or JS/WASM code.
Install the lemon binary with one line — macOS / Linux (on Windows, grab
the release asset):
curl -fsSL https://citrusquant.com/install.sh | shA strategy is a single file. Save this as momentum.lemon:
#! universe: 20180101..20241231#! symbols: AAPL, MSFT, NVDA, AMZN, GOOGL, META, JPM, XOM#! config: { "fee_ratio": 0.001 }#! data-source: fmpis_largest(pct_change(close, 63), 3)Run it. --sync fetches the declared names’ daily bars from the vendor the
file names — bring your own $FMP_API_KEY; nothing is fetched without it:
export FMP_API_KEY=… # your key; the file declares the vendorlemon momentum.lemon --syncIt prints the full Report as JSON — the same metrics, equity curve, and
trades the playground draws, from the same engine. The front-matter keys
(universe, symbols, index, config, data-source, …) are in the
lemon reference.
cargo add yuzu-core lemon-langlet spec = lemon::parse("is_largest(sma(close, 2), 3)")?;let report = yuzu_core::run_backtest(&spec, &ctx, "close", &cfg)?;Or clone and run the self-contained example — the best 40 lines to read first:
git clone https://github.com/citrusquant/citrusquantcargo run -p yuzu-core --example basic_backtestpip install yuzu-backtestimport yuzu
report = yuzu.run_backtest( "close > sma(close, 20)", # lemon source, straight in panels={"close": df_close}, # pandas/polars DataFrame (dates × symbols) config={"fee_ratio": 0.001},)report["metrics"]["sharpe"]Same Rust core underneath (abi3 wheels, Python ≥ 3.9) — see crates/yuzu-py.
npm install @citrusquant/yuzu-wasm @citrusquant/lemon-wasmThe wasm-pack artifacts this very page runs on
(@citrusquant/yuzu-wasm,
@citrusquant/lemon-wasm).
They work in browsers and Cloudflare Workers — see the API reference.
What it is — and isn’t
Section titled “What it is — and isn’t”It is
- A daily-bar, portfolio-level backtest engine for cross-sectional and trend strategies
- A small, readable strategy language with a complete one-page spec
- Deterministic and I/O-free — same input, same
Report, native or WASM - MIT-licensed: crates, PyPI wheels, npm WASM packages
It isn’t
- Not a data vendor — you bring panels you’re licensed to use
- Not tick or order-book simulation — bars in, portfolio NAV out
- Not a broker or execution layer — it stops at the report
- Not investment advice — it computes; you decide
Evaluating against something else? See how it compares to backtrader, vectorbt, and Lean →