Skip to content

Open source · MIT · Rustis_largest(sma(close, 2), 3)

…is a complete trading strategy: “hold the 3 names with the highest 2-day average close.” lemon is the little language it’s written in; yuzu is the Rust engine that backtests it. Below, the real engine — compiled to WebAssembly — runs it against three years of real market data, right here in your browser. No server, no signup.

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. 1 · Install the CLI (macOS / Linux)
    curl -fsSL https://citrusquant.com/install.sh | sh
  2. 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. 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.

yuzu-core on crates.iolemon-lang on crates.ioCI statusMIT license

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.

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 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: fmp
is_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 .lemon file can’t touch the network on its own; data is fetched only when you pass –sync with 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 →

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-lsp language 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.

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):

Terminal window
curl -fsSL https://citrusquant.com/install.sh | sh

A 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: fmp
is_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:

Terminal window
export FMP_API_KEY=# your key; the file declares the vendor
lemon momentum.lemon --sync

It 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.

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 →