Quickstart

Get a full backtest running in under 20 lines. No API key required.

# pip install cobweb-py[all]

import cobweb_py as cw

# 1. Grab SPY data
df = cw.from_yfinance("SPY", "2020-01-01", "2024-12-31")

# 2. Connect (free, no key needed)
sim = cw.CobwebSim("https://web-production-83f3e.up.railway.app")

# 3. Enrich to get SMA-50, then momentum: long when price > SMA-50
rows = sim.enrich_rows(df, feature_ids=[12])
signals = [1.0 if (r.get("sma_50") and r["Close"] > r["sma_50"]) else 0.0 for r in rows]
signals[:50] = [0.0] * 50

# 4. Backtest with realistic friction
bt = sim.backtest(
    rows, signals=signals,
    config=cw.BacktestConfig(exec_horizon="swing", initial_cash=100_000),
)

# 5. Results
for label, val in cw.format_metrics(bt).items():
    print(f"{label}: {val}")
cw.print_signal(bt)
cw.plots.save_equity_plot(bt, out_html="equity.html")
cw.plots.save_metrics_table(bt, out_html="metrics.html")

Installation

# Base SDK (requests + pandas)
pip install cobweb-py

# With Yahoo Finance helper
pip install cobweb-py[yahoo]

# With Plotly visualization
pip install cobweb-py[viz]

# Everything (yfinance + plotly)
pip install cobweb-py[all]

Requires Python 3.9+.

CobwebSim Client

The main class for interacting with the API.

sim = CobwebSim(
    base_url="https://web-production-83f3e.up.railway.app",
    api_key="YOUR_KEY",   # optional if auth is disabled
    timeout=60,
)

sim.health()

Check API status. Returns {"status": "ok", "version": "0.1.0"}.

cw.from_yfinance(ticker, start, end)

Download OHLCV data from Yahoo Finance and return a clean DataFrame. Requires pip install cobweb-py[yahoo].

df = cw.from_yfinance("AAPL", "2020-01-01", "2024-12-31")

sim.enrich(data, feature_ids)

Compute technical features from OHLCV data. Returns the full response dict. Alias: sim.features().

feats = sim.enrich("stock.csv", feature_ids=[1, 11, 36])
rows = feats["rows"]              # OHLCV + ret_1d, sma_20, rsi_14
cols = feats["feature_columns"]   # ['ret_1d', 'sma_20', 'rsi_14']

sim.enrich_rows(data, feature_ids)

Shorthand that returns just the enriched rows list directly.

rows = sim.enrich_rows(df, feature_ids=[1, 11, 36])  # list[dict] directly

Both accept: CSV path, pandas DataFrame, list[dict], or {"rows": [...]}.

sim.backtest(data, signals=, config=)

Run a full backtest with execution modeling.

bt = sim.backtest(
    data,
    signals=[0.0]*50 + [1.0]*200,   # or "long" / "short" / "flat"
    config=BacktestConfig(exec_horizon="swing"),
    compute_features=True,
    feature_ids=[70, 71],
    benchmark=benchmark_data,         # optional
)

bt["metrics"]        # Sharpe, Sortino, drawdown, VaR, ...
bt["trades"]         # fill prices, costs, TCA breakdown
bt["equity_curve"]   # per-bar equity, cash, position
bt["current_signal"] # "buy" / "sell" / "hold"

sim.plots(data, backtest_result, plot_ids=)

Generate chart payloads from backtest results.

pl = sim.plots(data, bt, plot_ids=[1, 5, 10])
pl["payloads"]  # dict of plot data keyed by name

BacktestConfig

All fields are optional with sensible defaults.

ParameterDefaultDescription
initial_cash10,000Starting capital
exec_horizon"swing"intraday | swing | longterm
asset_type"equities"equities | crypto
fee_bps1.0Broker fee (basis points)
half_spread_bps2.0Bid-ask half-spread
base_slippage_bps1.0Fixed slippage component
impact_coeff1.0Market impact multiplier
max_leverage1.0Maximum leverage allowed
allow_marginfalseAllow margin/short positions
rebalance_mode"on_signal_change"on_signal_change | every_bar | calendar
max_participationNoneMax fraction of bar volume per trade (overrides horizon default)
rebalance_every_n_bars0Bar interval for calendar rebalance mode
buy_threshold0.10Exposure threshold to trigger buy
sell_threshold-0.10Exposure threshold to trigger sell
min_trade_units0.0Minimum trade size in units
max_order_age_bars0Max bars a pending order stays alive (0 = unlimited)
risk_free_annual0.0Risk-free rate for Sharpe/Sortino calculation
periods_per_year252Trading days per year for annualization

Scoring

Build composite scores from enriched feature data.

from cobweb_py import score_by_id, score, auto_score, auto_score_by_id

# Weighted score by feature IDs (most common)
scores = score_by_id(enriched_rows, {36: 0.3, 11: 0.3, 1: 0.4})

# Weighted score by column names
scores = score(enriched_rows, {"rsi_14": 0.3, "sma_signal": 0.3, "ret_1d": 0.4})

# Default formula (RSI + SMA signal + returns)
scores = auto_score(enriched_rows)       # by column names
scores = auto_score_by_id(enriched_rows)  # by feature IDs

MA features (IDs 10-15) are auto-converted to (Close - MA) / MA signal form. RSI is scaled to 0-1. Other features are z-scored.

Feature / Plot Discovery

from cobweb_py import show_features, show_plots, show_categories
from cobweb_py import list_features, list_plots

show_features()              # print all 71 features
show_features("rsi")         # filter by category (fuzzy match)
show_plots("trades")         # filter plots by category
show_categories()            # list all valid category names

# Programmatic access (returns list of dicts):
feats = list_features("vol")  # [{"id": 38, "name": "vol_20", "category": "volatility"}, ...]
plots = list_plots()           # all 27 plots

Signals

Convert scores into discrete trading positions.

from cobweb_py import to_signals

signals = to_signals(scores, entry_th=0.20, exit_th=0.05, use_shorts=False)
# Returns: list of 0.0 (flat) and 1.0 (long)
# With use_shorts=True: -1.0 (short), 0.0, 1.0

Pipeline

Enrich once, iterate many times with different weights and parameters.

from cobweb_py import Pipeline

pipe = Pipeline("https://web-production-83f3e.up.railway.app", "stock.csv",
    benchmark="spy.csv",         # optional benchmark data
    feature_ids=list(range(1, 72)),  # default: all 71
)
result = pipe.run(weights={36: 0.3, 11: 0.3, 1: 0.4})

# Re-run with different params (no re-enrichment):
result2 = pipe.run(weights={36: 0.5, 1: 0.5}, entry_th=0.30)

# Access Plotly figures:
for name, fig in result["figures"].items():
    fig.show()

# Result keys: metrics, signal, trades, scores, signals, equity_curve, backtest, figures

Plots (SDK)

All plot functions are available via the cw.plots namespace:

import cobweb_py as cw

cw.plots.save_equity_plot(bt, out_html="equity.html")
cw.plots.save_metrics_table(bt, out_html="metrics.html")
FunctionOutput
save_equity_plot(bt)Equity curve + position units
save_metrics_table(bt)Styled metrics summary
save_trades_table(bt)Trade log with TCA breakdown
save_score_plot(rows, scores)Score time series
save_price_and_score_plot(rows, scores)Price + score overlay
save_features_table(rows)OHLCV + features (HTML + CSV)
save_api_payloads_to_html(payloads)Any API plot → HTML file
payloads_to_figures(payloads)API plots → Plotly figures
payload_to_df(payload)Single plot payload → pandas DataFrame
save_api_payloads_to_dfs(payloads)All payloads → dict of DataFrames

payload_to_figure, payloads_to_figures, and payload_to_df are also available as top-level imports: cw.payload_to_df(p).

Utilities

import cobweb_py as cw

# Download data from Yahoo Finance
df = cw.from_yfinance("AAPL", "2020-01-01", "2024-12-31")

# Normalize timestamps for the API
data = cw.fix_timestamps(rows, dayfirst=True)

# Load CSV with auto-detection
data = cw.load_csv("stock.csv")

# Align two datasets by timestamp
asset, bench = cw.align(asset_data, benchmark_data)

# Get current signal from backtest
cw.print_signal(bt)   # Signal: BUY   |  exposure=+0.87  |  strength=0.72
s = cw.get_signal(bt)  # {"signal": "buy", "exposure": 0.87, "strength": 0.72}

# Convert signal float to label
cw.signal_label(1.0)   # "BUY"
cw.signal_label(-1.0)  # "SELL"
cw.signal_label(0.0)   # "HOLD"

# Format backtest metrics with labels
cw.format_metrics(bt)  # {"Total Return": "23.45%", "Sharpe Ratio": "1.32", ...}

# Convert a plot payload to a pandas DataFrame
df = cw.payload_to_df(payload)

# Save a lightweight HTML table (auto-creates directories)
cw.save_table(rows, "out/table.html", title="My Table")

# Fetch a single plot (auto-handles benchmark/regime/correlation deps)
pl = cw.get_plot(sim, data, bt, 5)              # by ID
pl = cw.get_plot(sim, data, bt, "sharpe")       # by fuzzy name

# Fetch a plot and return it directly as a DataFrame (one-liner)
df = cw.get_plot_df(sim, data, bt, 3, benchmark_rows=bench)

# Fetch and save all plots as HTML
cw.save_all_plots(sim, data, bt, range(1, 28), out_dir="out/plots")

API Overview

Base URL: https://web-production-83f3e.up.railway.app

All endpoints accept JSON. No API key required (free tier).

MethodEndpointDescription
GET/healthHealth check
POST/featuresCompute features from OHLCV
POST/backtestRun backtest simulation
POST/plotsGenerate chart payloads

Full interactive docs: Swagger UI

POST /features

Compute technical features from OHLCV candle data (minimum 20 rows).

# curl example
curl -X POST https://web-production-83f3e.up.railway.app/features \
  -H "Content-Type: application/json" \
  -d '{
    "data": {"rows": [{"open":150,"high":155,"low":149,"close":153,"volume":1000000}, ...]},
    "feature_ids": [1, 11, 36]
  }'

POST /backtest

Run a backtest with signals and execution modeling. Signal count must match row count.

curl -X POST https://web-production-83f3e.up.railway.app/backtest \
  -H "Content-Type: application/json" \
  -d '{
    "data": {"rows": [...]},
    "signals": [0, 0, 1, 1, 1, 0, -1, ...],
    "config": {"exec_horizon": "swing", "initial_cash": 100000}
  }'

POST /plots

Generate chart payloads from OHLCV data and a backtest result.

curl -X POST https://web-production-83f3e.up.railway.app/plots \
  -H "Content-Type: application/json" \
  -d '{
    "data": {"rows": [...]},
    "backtest_result": { ...bt response... },
    "plot_ids": [1, 5, 10]
  }'

71 Features

IDsCategoryExamples
1–9Returns / Momentumret_1d, logret_1d, ret_5d, ret_20d, ret_60d, ret_252d, acceleration, percentile
10–29Trend / Moving Averagessma_10/20/50/200, ema_12/26, slopes, distances, spreads
30–35MACDmacd, signal, histogram, slopes, z-score
36–37RSIrsi_14, rsi_pct_252
38–44Volatility / ATRvol_20, vol_60, atr_14, vol-of-vol, percentiles
45–51Bollinger Bandsmid/upper/lower, bandwidth, %B, distances
52–54Mean Reversionzscore_20, deviation from mean
55–56Stochastic%K and %D (14-period)
57–62Volume / Rangeadv_20, vol_shock, vol_z, range ratio
63–67Riskdrawdown, skew, kurtosis
68–69Beta / Correlationbeta_252, corr_252
70–71Regimetrend_regime, vol_regime

Full list in code: from cobweb_py import show_features; show_features()

27 Plots

IDsCategoryPlots
1–15PerformanceEquity (linear/log), vs benchmark, daily returns, rolling Sharpe/Sortino/vol/beta/alpha, drawdowns, VaR/CVaR, tail risk
16–19TradesReturn distribution, win/loss overlay, expectancy, duration vs return
20–22RegimePerformance by vol regime, trend regime, bull vs bear
23DiagnosticsFeature correlation heatmap
24–27Volume / ExecutionVolume, volume shock, participation, transaction costs

Execution Model

Every trade passes through a realistic execution simulator:

Three presets adjust all parameters automatically:

HorizonMax Participation (eq)Impact CoeffUse Case
intraday2%1.25Sub-daily bars, tight constraints
swing8%1.0Daily bars, typical strategies
longterm15%0.8Weekly rebalance, portfolio allocation