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
plotsinto your chart layer. - Use
equity/trades/statsfor 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_schematells your UI which sliders / controls to render.set_inputslets your app push UI values back into the script.
For more copy‑pasteable scripts, see:
docs/Quickstart.mddocs/Indicator_cookbook.mddocs/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 (returnsSeries).host_api["request_ohlcv"]/"requestohlcv"→ v2 OHLCV API (returnsDataFrame).
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 viamax_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 ofinput.*fields.result["meta"]– user metadata fromdefinelines.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
-
Install & import
pip install kafal
from kafal.core.interpreter import KafalInterpreter
-
Provide data
- Build a pandas
DataFramewithopen,high,low,close,volume. - Optionally implement
request_ohlcv/request_securityto support multi‑symbol or multi‑timeframe scripts.
- Build a pandas
-
Expose scripting
- Let users upload
.kffiles or edit scripts in a text area. - Read into
code: str, then callintr.run(code, df, host_api=..., mode=...).
- Let users upload
-
Render outputs
- Map
plots,fills,shapes,hlines,bgcolors,tablesto your chart library. - Use
result["strategy"]["equity"],["pnl"],["trades"],["stats"]for backtest panels. - Use
result["features"]for factor research and ranking dashboards.
- Map
-
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.
- Mirror patterns from
Errors & troubleshooting
Kafal surfaces structured runtime errors to make debugging easier.
-
All runtime errors are prefixed with
Kafal:and carry a stableKAFAL-*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]
- Example:
-
Codes are stable and searchable:
KAFAL-EXEC-*– interpreter configuration issues (e.g. badexecution_modeorrun(mode=...)).KAFAL-EXPR-*– DSL expression / sandbox issues (e.g. blocked.attr, lambdas, invalid operators).KAFAL-HOST-*– host integration issues (e.g. missing or wrong‑typerequest_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.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kafal-1.0.4.tar.gz | 51.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kafal-1.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 105.1 kB
Release files / kafal-1.0.4.tar.gz
| Download URL | kafal-1.0.4.tar.gz |
|---|---|
| Size | 51.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f6d8f80b62fc40133c8efa706480e42d42af4913f26d0305254aa7506c65732
|
|
BLAKE2b-256 checksum How to use checksums |
aa432ab3dab96504b7c928f0e747271f80ac7c0c9aad75d2ca94aa7e1e86606f
|
| 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.4-py3-none-any.whl
| Download URL | kafal-1.0.4-py3-none-any.whl |
|---|---|
| Size | 53.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b81d9ef67b23c696a0f81bf60286702bbfdbb0e7d7e966f44a804bde3cafb272
|
|
BLAKE2b-256 checksum How to use checksums |
3b395b1028d6a69c6de5d7f8578cd72ce21fc3840e7755db2ae3b4658864913f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|