Skip to main content

kafal

kafal is a compact scripting layer for building trading indicators, strategies, and research factors on top of pandas OHLCV data.
It sits between raw Python and chart / execution UIs:

  • DSL for signals, risk, and execution.
  • TA + quant primitives (TA‑Lib wrappers, rolling stats, z‑scores, etc.) wired straight into your script.
  • A configurable strategy engine with realistic fills, intrabar simulation, and portfolio aggregation.

Write strategy logic fast, run it anywhere Python runs.

Status: alpha / experimental – API is stabilising; small breaking changes may still happen before 1.0.


60‑second Quickstart

Install:

pip install kafal

1. Indicator + crossover strategy in one script

import pandas as pd
from kafal.core.interpreter import KafalInterpreter

Create a toy OHLCV DataFrame:

dates = pd.date_range("2025-01-01", periods=200, freq="1H")
close = pd.Series(100 + 0.1 * range(200), index=dates)

df = pd.DataFrame(
    {
        "open": close.shift(1).fillna(close.iloc),
        "high": close + 1.0,
        "low": close - 1.0,
        "close": close,
        "volume": 1_000,
    },
    index=dates,
)

Kafal script (as a string):

code = """
// Simple EMA crossover strategy

len_fast = 10
len_slow = 30

fast = ema(close, len_fast)
slow = ema(close, len_slow)

plot(close) - title="Close"
plot(fast)  - title="Fast EMA"  color=rgba(0,255,0,0.9)
plot(slow)  - title="Slow EMA"  color=rgba(255,0,0,0.9)

long_cond  = crossover(fast, slow)
short_cond = crossunder(fast, slow)

// Modern trading API (preferred)
trade.long(long_cond,  size=1.0)
trade.short(short_cond, size=1.0)
"""

Run it:

intr = KafalInterpreter()
result = intr.run(code, df, mode="strategy")

plots    = result["plots"]
strategy = result["strategy"]
equity   = strategy["equity"]
trades   = strategy["trades"]
stats    = strategy["stats"]
  • Feed plots into your chart layer.
  • Use equity / trades / stats for a basic backtest view.

2. Strategy with inputs and the modern trade.* API

indicator_code = """
define name="EMA Cross (Inputs)" overlay=true category="trend"

input fast_len:int 10  min=2  max=50  step=1  title="Fast EMA"
input slow_len:int 30  min=5  max=200 step=1  title="Slow EMA"

fast = ema(close, fast_len)
slow = ema(close, slow_len)

plot(fast) - color=rgba(0,255,0,0.9) title="Fast"
plot(slow) - color=rgba(255,0,0,0.9) title="Slow"

long_cond  = crossover(fast, slow)
short_cond = crossunder(fast, slow)

trade.long(long_cond,  size=1.0)
trade.short(short_cond, size=1.0)
"""
intr = KafalInterpreter()
intr.set_inputs({"fast_len": 12, "slow_len": 34})

result = intr.run(indicator_code, df, mode="strategy")

strategy      = result["strategy"]
stats         = strategy["stats"]
inputs_schema = result["inputs_schema"]
  • inputs_schema tells your UI which sliders / controls to render.
  • set_inputs lets your app push UI values back into the script.

For more copy‑pasteable scripts, see:

  • docs/Quickstart.md
  • docs/Indicator_cookbook.md
  • docs/Strategy_cookbook.md

Public API surface (host‑facing)

Core class

from kafal.core.interpreter import KafalInterpreter

Typical construction:

intr = KafalInterpreter(
    host_api=None,           # dict of data callbacks (see Host API)
    max_request_calls=50,    # safety cap for cross-symbol requests
    initial_equity=100_000.0,
    commission_perc=0.0,
    slippage=0.0,
    pyramiding=1,
    execution_mode="backtest",   # "backtest" | "realistic"
    symbol_configs=None,         # per-symbol dealing config
    intrabar_model="none",       # "none" | "random_walk"
    intrabar_steps=0,            # synthetic ticks per bar if intrabar_model != "none"
)

Modes

result = intr.run(code, df, mode="chart")     # visuals + optional strategy
result = intr.run(code, df, mode="strategy")  # same payload, host treats as strategy
result = intr.run(code, df, mode="research")  # focus on features; visuals optional

Host API (data access)

All external data is explicit and host‑controlled, via a small host API.

  • request_security(symbol, timeframe, field_or_expr, gaps="off" | "ffill" | "on")
  • host_api["request_security"] / "requestsecurity" → v1 per‑field API (returns Series).
  • host_api["request_ohlcv"] / "requestohlcv" → v2 OHLCV API (returns DataFrame).

Scripts never open files, hit the network, or touch OS APIs.

Minimal example:

import pandas as pd
from kafal.core.host_api import HostAPI
from kafal.core.interpreter import KafalInterpreter

def request_ohlcv(symbol: str, timeframe: str) -> pd.DataFrame:
    # You decide where data comes from (DB, gateway, CSV, etc.)
    df = load_ohlcv_from_somewhere(symbol, timeframe)
    # Must be a DataFrame with at least: open, high, low, close, volume
    return df

host_api = {"request_ohlcv": request_ohlcv}
intr = KafalInterpreter(host_api=host_api)

result = intr.run(code, df, mode="chart")

Inside the script:

// Uses host_api["request_ohlcv"] under the hood
other_close = request_security("NIFTY", "1D", "close")
spread      = close - other_close
plot(spread) - title="Spread vs NIFTY"

Sandbox & safety model

Kafal scripts are untrusted by default. The interpreter enforces:

  • No filesystem, network, or OS access.
  • __builtins__ cleared during eval.
  • AST validator that blocks attribute access, imports, lambdas, comprehensions, etc.
  • Loop bounds capped by MAX_FOR_RANGE.
  • request_* calls limited via max_request_calls.

Host‑side, you only expose controlled callbacks via host_api; everything else is locked down by the sandbox.


What run() returns

result = intr.run(code, df, ...) yields a dict with these main fields:

  • Visual layer

    • result["plots"]
    • result["shapes"]
    • result["hlines"]
    • result["bgcolors"]
    • result["fills"]
    • result["tables"]
    • result["alerts"]
  • Strategy payload

    strat    = result["strategy"]
    equity   = strat["equity"]    # Series
    pnl      = strat["pnl"]       # Series
    position = strat["position"]  # Series
    trades   = strat["trades"]    # List[dict]
    stats    = strat["stats"]     # Dict[str, Any]
    
  • Meta / research

    • result["inputs_schema"] – description of input.* fields.
    • result["meta"] – user metadata from define lines.
    • result["features"] – named research features (in research mode).
    • result["mode"] – echo of the mode you passed.

These keys are stable for the 0.1 line; new keys may be added in future versions.


DSL snapshot (what you can write)

Series and TA

mid    = hl2(high, low)
trend  = ema(close, 50)
vol_z  = zscore(volume, 50)

plot(mid)   - color=rgba(255,255,0,0.9) title="HL2"
plot(trend) - color=rgba(0,200,255,0.9) title="EMA 50"
plot(vol_z) - color=rgba(255,165,0,0.9) title="Vol zscore"

Control flow and arrays

sumval = 0
for i in 0..10 {
    if i > 5 {
        sumval = sumval + 1
    }
}

arr = array()
for i in 0..5 {
    array_push(arr, i)
}

plot(sumval) - title="Counter"

Functions and pipes

fn atr_band(src, len, mult) = src + atr(len) * mult

upper   = atr_band(close, 14,  2.0)
lower   = atr_band(close, 14, -2.0)
signal  = close | rsi(14) | sma(9)

plot(upper)  - title="ATR Upper"
plot(lower)  - title="ATR Lower"
plot(signal) - title="RSI SMA"

For a deeper walkthrough, see docs/Quickstart.md plus the indicator & strategy cookbooks.


Examples in this repo

You already have runnable examples wired to the interpreter and portfolio engine:

  • examples/run_research_example.py – single‑symbol research mode.
  • examples/run_universe_research.py – multi‑symbol research / factor panel.
  • examples/run_strategy_example.py – single‑symbol strategy backtest.

Run them:

python examples/run_strategy_example.py
python examples/run_research_example.py
python examples/run_universe_research.py

These act as “host skeletons” you can copy into your own app.


Embedding Kafal in your app

  1. Install & import

    pip install kafal
    
    from kafal.core.interpreter import KafalInterpreter
    
  2. Provide data

    • Build a pandas DataFrame with open, high, low, close, volume.
    • Optionally implement request_ohlcv / request_security to support multi‑symbol or multi‑timeframe scripts.
  3. Expose scripting

    • Let users upload .kf files or edit scripts in a text area.
    • Read into code: str, then call intr.run(code, df, host_api=..., mode=...).
  4. Render outputs

    • Map plots, fills, shapes, hlines, bgcolors, tables to your chart library.
    • Use result["strategy"]["equity"], ["pnl"], ["trades"], ["stats"] for backtest panels.
    • Use result["features"] for factor research and ranking dashboards.
  5. Lock in behaviour with tests

    • Mirror patterns from kafal/tests/test_kafal.py:
      • host API error behaviour you depend on.
      • "backtest" vs "realistic" execution.
      • presence and types of keys in run() output.
      • portfolio aggregation in PortfolioState.

Errors & troubleshooting

Kafal surfaces structured runtime errors to make debugging easier.

  • All runtime errors are prefixed with Kafal: and carry a stable KAFAL-* code at the end.

    • Example: Kafal: execution_mode must be 'backtest' or 'realistic'. [KAFAL-EXEC-MODE]
    • Example: Kafal: host_api['request_ohlcv'] must return a pandas DataFrame. [KAFAL-HOST-OHLCV-TYPE]
  • Codes are stable and searchable:

    • KAFAL-EXEC-* – interpreter configuration issues (e.g. bad execution_mode or run(mode=...)).
    • KAFAL-EXPR-* – DSL expression / sandbox issues (e.g. blocked .attr, lambdas, invalid operators).
    • KAFAL-HOST-* – host integration issues (e.g. missing or wrong‑type request_security / request_ohlcv).

If you see a Kafal error:

  • Read the human message; it always includes what went wrong and usually how to fix.
  • Use the KAFAL-* code in your logs, tests, or docs search to find the relevant contract or section.

Metadata

Release files for kafal 1.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kafal 1.0.3
File Size Uploaded
kafal-1.0.3.tar.gz 51.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for kafal 1.0.3
File Interpreter ABI Platform
kafal-1.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 105.1 kB

Release files / kafal-1.0.3.tar.gz

Download URL kafal-1.0.3.tar.gz
Size 51.9 kB
Tags Source
SHA-256 checksum
How to use checksums
62f774ab90dd7d4443e14537fec5ec1fb6779b16b70c6af66ffbab3a86cd0f1c
BLAKE2b-256 checksum
How to use checksums
3d563701451f271701dfa114d14dd191c07aa308439c57639faf6f5a4ed07b68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release files / kafal-1.0.3-py3-none-any.whl

Download URL kafal-1.0.3-py3-none-any.whl
Size 53.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2419a11e7cfc89c7d2dbb9ad94c4f54118370d2f6b3b44e50884b7ce00be1c2a
BLAKE2b-256 checksum
How to use checksums
e45995306e6d43be07beb50e4a6e20080cd44d88384a846537684a9dcb802860
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.0

Release history Release notifications | RSS feed

1.0.5

2 release files

1.0.4

2 release files

This release

1.0.3 This release

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page