Skip to main content

Embeddable DSL for trading quant models, research factors, and backtesting.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kafal-1.0.0.tar.gz (47.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kafal-1.0.0-py3-none-any.whl (48.1 kB view details)

Uploaded Python 3

File details

Details for the file kafal-1.0.0.tar.gz.

File metadata

  • Download URL: kafal-1.0.0.tar.gz
  • Upload date:
  • Size: 47.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for kafal-1.0.0.tar.gz
Algorithm Hash digest
SHA256 a4b45af1adac4c3c39a5113c8a691a018f5fdb133fdbd603ad7fb348934aeaf6
MD5 619e16c58bf2c74aa6320010d17eb927
BLAKE2b-256 cf9282ad895e7715f3baf32ba40c7661c76189afe428fcda575210a2919ddee4

See more details on using hashes here.

File details

Details for the file kafal-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: kafal-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 48.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for kafal-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 21c6d67deabb88521c9526d981992b2b76bf345976624ebe6959ed24ca7222dc
MD5 52711e54bdcec871f032cc0c49fd88e1
BLAKE2b-256 350255cb4286c20c95f869dd7f85411cbf54e221207446658fc07eab39f340fa

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page