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

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# /// 

18 

19"""Experiment 1: Basic CTA strategy implementation using moving averages. 

20 

21This module demonstrates a simple trend-following strategy using exponential 

22moving averages with different lookback periods. 

23""" 

24 

25import marimo 

26 

27__generated_with = "0.23.1" 

28app = marimo.App() 

29 

30with app.setup: 

31 import sys 

32 from pathlib import Path 

33 

34 import marimo as mo 

35 import polars as pl 

36 from jquantstats import Portfolio 

37 

38 sys.path.insert(0, str(Path(__file__).parent)) 

39 

40 from preamble import date_col, load_prices 

41 

42 prices = load_prices(__file__) 

43 prices_only = prices.drop(date_col) 

44 

45 

46@app.cell(hide_code=True) 

47def _(): 

48 mo.md(r"""# CTA 1.0""") 

49 return 

50 

51 

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() 

56 

57 

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") 

62 

63 mo.vstack([fast, slow]) 

64 

65 return fast, slow 

66 

67 

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,) 

73 

74 

75@app.cell 

76def _(portfolio): 

77 print(portfolio.stats.sharpe()) 

78 

79 

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 

92 

93 

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 

104 

105 

106@app.cell 

107def _(portfolio): 

108 fig = portfolio.plots.snapshot() 

109 fig 

110 return 

111 

112 

113if __name__ == "__main__": 

114 app.run()