Coverage for book/marimo/notebooks/Experiment2.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 2: Improved CTA strategy with volatility scaling.
21This module enhances the basic trend-following strategy by incorporating
22volatility scaling to adjust position sizes based on market conditions.
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 2.0""")
49 return
52@app.function
53def f(price: "pl.Expr", fast: int = 32, slow: int = 96, volatility: int = 32) -> "pl.Expr":
54 """Return the volatility-scaled EWM crossover signal."""
55 return (
56 price.ewm_mean(com=fast, min_samples=300) - price.ewm_mean(com=slow, min_samples=300)
57 ).sign() / price.pct_change().ewm_std(com=volatility, min_samples=300)
60@app.cell
61def _():
62 fast = mo.ui.slider(4, 192, step=4, value=32, label="Fast Moving Average")
63 slow = mo.ui.slider(4, 192, step=4, value=96, label="Slow Moving Average")
64 vola = mo.ui.slider(4, 192, step=4, value=32, label="Volatility")
66 mo.vstack([fast, slow, vola])
68 return fast, slow, vola
71@app.cell
72def _(fast, slow, vola):
73 signals = prices_only.select(
74 f(pl.all(), fast=fast.value, slow=slow.value, volatility=vola.value).fill_null(0.0) * 1e5
75 )
76 portfolio = Portfolio.from_cash_position(prices=prices, cash_position=signals, aum=1e8)
77 return (portfolio,)
80@app.cell
81def _(portfolio):
82 print(portfolio.stats.sharpe())
85@app.cell(hide_code=True)
86def _():
87 mo.md(
88 r"""
89 * This is a **univariate** trading system, we map the (real) price of an asset to its (cash)position
90 * Only 3 **free parameters** used here.
91 * Scaling the bet-size by volatility has improved the situation.
92 """
93 )
94 return
97@app.cell(hide_code=True)
98def _():
99 mo.md(
100 r"""
101 Results do not look terrible but...
102 * No concept of risk integrated
104 Often hedge funds outsource the risk management to some board or committee
105 and develop machinery for more systematic **parameter-hacking**.
106 """
107 )
108 return
111@app.cell
112def _(portfolio):
113 portfolio.plots.snapshot()
114 return
117if __name__ == "__main__":
118 app.run()