Artifacts
Save Python charts, reports, tables, and model output in a script job's artifact directory.
Python scripts often produce more than scalar metrics. Use ctx.artifact_path(name) to save charts, CSV files, model output, or reports in a directory owned by the current run.
def on_finish(ctx, history):
path = ctx.artifact_path("performance.png")
plt.savefig(path, dpi=160, bbox_inches="tight")
plt.close()
return {"meta": {"chart": path}}The method returns an absolute path as a string and creates missing parent directories:
csv_path = ctx.artifact_path("tables/trades.csv")
frame.to_csv(csv_path, index=False)Storage location
Artifacts are stored under:
~/.market-lab/artifacts/<job-id>/Live runs use the script job ID. Backtests use a generated run-specific directory so one backtest does not overwrite another.
On Unix systems, Market Lab creates each job artifact directory with owner-only 0700 permissions.
Path safety
The name must remain inside the job directory. Absolute paths and parent traversal are rejected:
ctx.artifact_path("../outside.csv") # rejected
ctx.artifact_path("/tmp/report.csv") # rejectedUse a relative name such as report.csv or charts/equity.png.
Plot runtime PnL
ctx.pnl() returns the PnL points recorded during the run. This makes the same runtime series available for post-analysis in on_finish:
def on_finish(ctx, history):
import matplotlib.pyplot as plt
from datetime import datetime, timezone
import matplotlib.dates as mdates
points = ctx.pnl()
if not points:
return
times = [
datetime.fromtimestamp(point["t"] / 1000, tz=timezone.utc)
for point in points
]
pnl = [point["pnl"] for point in points]
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(times, pnl, linewidth=1.5, label="Net PnL")
ax.axhline(0, color="gray", linewidth=1, linestyle="--")
locator = mdates.AutoDateLocator()
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(locator))
path = ctx.artifact_path("pnl.png")
fig.savefig(path, dpi=160)
print(f"Saved chart to: {path}")
plt.close(fig)Use ctx.pnl(0) for the latest point or ctx.pnl(1) for the previous point. An unavailable index returns None.
Complete pandas and matplotlib example
import matplotlib.pyplot as plt
import pandas as pd
SOURCE = "btc@candles@binancef:timeframe=60"
def on_finish(ctx, history):
frame = pd.DataFrame(history.source(SOURCE))
if frame.empty:
return
frame["sma_20"] = frame["c"].rolling(20).mean()
csv_path = ctx.artifact_path("tables/candles.csv")
frame.to_csv(csv_path, index=False)
axis = frame.plot(x="t", y=["c", "sma_20"], title="BTC close and SMA")
axis.figure.savefig(
ctx.artifact_path("charts/sma.png"),
dpi=160,
bbox_inches="tight",
)
plt.close(axis.figure)
return {
"meta": {
"candles": csv_path,
"chart": ctx.artifact_path("charts/sma.png"),
}
}on_finish has a longer runtime limit than event hooks so final reporting can do more work. It still must complete within 60 seconds.
Retention responsibility
Market Lab does not automatically delete artifact directories and does not impose an artifact disk quota. Monitor ~/.market-lab/artifacts and remove old run directories according to your own retention policy.
ctx.artifact_path is a safe default, not a filesystem sandbox. Trusted Python code can still write elsewhere using normal Python APIs and the operating-system permissions of the daemon user.