LIVE·
fetching live quotes from Yahoo Finance…
--:--:--UTC
Learn/Bots/Sharpe Optimizer
Risk & Sizingid · sharpe

Sharpe Optimizer

Risk-adjusted return. Higher = better per unit of pain.

In plain English

Sharpe is your return divided by how bumpy the ride was. Above 1 is good, above 2 is rare, above 3 is suspicious. Sortino is the same idea but only counts the bumps that hurt (downside vol). The simplest 'is this strategy any good?' number that exists.

No jargon. Just what this bot does.
The longer version

Sharpe is return divided by how bumpy the ride was. >1 is good. >2 is rare. >3 is suspicious. Sortino is the same idea but only counts the bumps that hurt (downside vol). The classic risk-adjusted-return summary — every fund manager reports it, every retail trader should know what their own Sharpe is.

The math
formula
Sharpe = √252 · mean / σ ; Sortino uses downside σ
parameters
lookbackLookback barsrange 30 → 504default · 252
rfRisk-free / dayrange 0 → 0.001default · 0.0001
Live demo

Real Sharpe Optimizer bot, running on real Yahoo data when the symbol is available. Drag the params — the bot re-runs instantly.

symbolloading…
loading AMZN bars…
Source code · public

This is the actual code the bot runs — not a re-explanation, not a simplified version. Whatever ships here is what executes when you press Run All in the workbench. Read it, copy it, fork it, build a better one.

lib/quant/bots.ts·lines 695740
TypeScript · MIT-licensed
const sharpeBot: BotDef = {
  id: "sharpe",
  name: "Sharpe Optimizer",
  category: "risk",
  glyph: "★",
  tagline: "Risk-adjusted return. Higher = better per unit of pain.",
  formula: "Sharpe = √252 · mean / σ ; Sortino uses downside σ",
  params: [
    { key: "lookback", label: "Lookback bars", kind: "number", default: 252, min: 30, max: 504, step: 1 },
    { key: "rf", label: "Risk-free / day", kind: "number", default: 0.0001, min: 0, max: 0.001, step: 0.0001 },
  ],
  run: (ctx, p): BotResult => {
    const lb = Math.round(num(p, "lookback", 252));
    const rf = num(p, "rf", 0.0001);
    const px = closes(ctx.candles).slice(-lb);
    const r = returns(px);
    const sh = sharpe(r, rf);
    const so = sortino(r, rf);
    const eq: number[] = [1];
    for (let i = 0; i < r.length; i++) eq.push(eq[eq.length - 1] * (1 + r[i]));
    const dd = maxDrawdown(eq).dd;
    const totalRet = eq[eq.length - 1] - 1;
    return {
      signals: [],
      metrics: [
        { key: "sharpe", label: "Sharpe", value: fmtNum(sh, 2), tone: sh > 1 ? "bull" : sh > 0 ? "neutral" : "bear" },
        { key: "sortino", label: "Sortino", value: fmtNum(so, 2), tone: so > 1 ? "bull" : "neutral" },
        { key: "ret", label: `${px.length}d return`, value: fmtPct(totalRet), tone: totalRet > 0 ? "bull" : "bear" },
        { key: "dd", label: "Max DD", value: fmtPct(dd), tone: dd < -0.2 ? "bear" : dd < -0.1 ? "warn" : "neutral" },
      ],
      summary: `Sharpe ${sh.toFixed(2)} · Sortino ${so.toFixed(2)} · Max DD ${(dd * 100).toFixed(1)}% over ${px.length} bars.`,
      beginner:
        "Sharpe is return divided by how bumpy the ride was. >1 is good. >2 is rare. >3 is suspicious. Sortino is the same idea but only counts the bumps that hurt (downside).",
      verdict: {
        side: sh > 1 ? "buy" : sh < 0 ? "warn" : "hold",
        text: sh > 1
          ? "Quality risk-adjusted return — buy-and-hold is paying."
          : sh > 0
          ? "Positive but mediocre. Look for better setups."
          : "Negative Sharpe — paying for pain. Cash > this asset right now.",
        confidence: Math.min(1, Math.abs(sh) / 3),
      },
      equity: eq,
    };
  },
};
what each piece means
  • id — unique key the workbench uses to find the bot.
  • params — the sliders + inputs you see on the cell.
  • run(ctx, p) — the function that gets called with candles + your params and returns the verdict.
  • verdict — the BUY / SELL / HOLD pill at the top of the cell.
  • metrics — the small stat boxes shown in the cell body.
use this code yourself
  1. Copy the whole block above.
  2. On /quant, click + Import your bot in the bot library.
  3. Paste, hit save. It hot-loads into your workspace.
  4. Edit any param defaults or logic to your taste — it's now yours.
Specialty · when it shines, when it fails
✓ Shines when
  • ·Comparing strategies on apples-to-apples basis. A 30% return with 60% vol (Sharpe 0.5) is worse than a 15% return with 10% vol (Sharpe 1.5).
  • ·Long-horizon evaluation. Sharpe needs ≥30 monthly observations or ≥250 daily to be reliable.
  • ·Position sizing across uncorrelated edges. Equal-Sharpe weighting is provably near-optimal for portfolio construction.
✗ Fails when
  • ·Skewed distributions. Sharpe punishes upside vol; if your strategy has rare big wins, Sharpe undersells it.
  • ·Short windows. Sharpe over a 3-month sample has wildly variable estimates.
  • ·Comparing across asset classes. Sharpe of a bond strategy vs a crypto strategy isn't directly meaningful.
How to read its verdict

BUY when Sharpe > 1 (quality risk-adjusted return). HOLD between 0 and 1 (positive but mediocre). WARN when Sharpe < 0 (negative edge — paying for pain). Sortino > Sharpe means your returns are positively skewed (good); Sortino < Sharpe is rare and concerning.

FAQ
What's a 'good' Sharpe?+
Long-only equities historically: ~0.4. A skilled discretionary trader: 0.6-1.0. Top quant funds in their flagship strategies: 1.5-2.5 net of costs. Anything above 3 sustained over years is either alpha that won't persist, or fraud.
Should I optimise for Sharpe or absolute return?+
Sharpe — almost always. Higher Sharpe = more leverage you can apply safely = higher absolute return at any given risk. The only exception is when you can't lever (cash account), in which case absolute return and Sharpe align.