Coverage for book/marimo/notebooks/Experiment1.py: 100%
42 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-09 08:57 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-09 08:57 +0000
1# /// script
2# requires-python = ">=3.12"
3# dependencies = [
4# "marimo==0.24.0",
5# "numpy==2.4.6",
6# "plotly==6.9.0",
7# "polars==1.44.1",
8# "jquantstats==0.11.0"
9# ]
10#
11# [tool.ty.environment]
12# # ``from preamble import ...`` resolves at runtime via the sys.path.insert in the
13# # setup cell below. ty analyses a PEP 723 script in isolation from the project, so
14# # pyproject.toml's [tool.ty.environment] never reaches this file and the path has
15# # to be declared here. Preserve this table if marimo rewrites the header.
16# extra-paths = ["."]
17# ///
19"""Experiment 1: Basic CTA strategy implementation using moving averages.
21This module demonstrates a simple trend-following strategy using exponential
22moving averages with different lookback periods.
23"""
25import marimo
27__generated_with = "0.23.1"
28app = marimo.App()
30with app.setup:
31 import sys
32 from pathlib import Path
34 import marimo as mo
35 import polars as pl
36 from jquantstats import Portfolio
38 sys.path.insert(0, str(Path(__file__).parent))
40 from preamble import date_col, load_prices
42 prices = load_prices(__file__)
43 prices_only = prices.drop(date_col)
46@app.cell(hide_code=True)
47def _():
48 mo.md(r"""# CTA 1.0""")
49 return
52@app.function
53def f(price: "pl.Expr", fast: int = 32, slow: int = 96) -> "pl.Expr":
54 """Return the sign of the fast-minus-slow EWM crossover."""
55 return (price.ewm_mean(com=fast, min_samples=100) - price.ewm_mean(com=slow, min_samples=100)).sign()
58@app.cell
59def _():
60 fast = mo.ui.slider(4, 192, step=4, value=32, label="Fast moving average")
61 slow = mo.ui.slider(4, 192, step=4, value=96, label="Slow moving average")
63 mo.vstack([fast, slow])
65 return fast, slow
68@app.cell
69def _(fast, slow):
70 signals = prices_only.select(f(pl.all(), fast=fast.value, slow=slow.value).fill_null(0.0) * 5e6)
71 portfolio = Portfolio.from_cash_position(prices=prices, cash_position=signals, aum=1e8)
72 return (portfolio,)
75@app.cell
76def _(portfolio):
77 print(portfolio.stats.sharpe())
80@app.cell(hide_code=True)
81def _():
82 mo.md(
83 r"""
84 Results do not look terrible but...
85 * No concept of risk integrated.
86 * The size of each bet is constant regardless of the underlying asset.
87 * The system lost its mojo in 2009 and has never really recovered.
88 * The sign function is very expensive to trade as position changes are too extreme.
89 """
90 )
91 return
94@app.cell(hide_code=True)
95def _():
96 mo.md(
97 r"""
98 Such fundamental flaws are not addressed by **parameter-hacking**
99 or **pimp-my-trading-system** steps (remove the worst performing assets,
100 insane quantity of stop-loss limits, ...)
101 """
102 )
103 return
106@app.cell
107def _(portfolio):
108 fig = portfolio.plots.snapshot()
109 fig
110 return
113if __name__ == "__main__":
114 app.run()