Coverage for book/marimo/notebooks/Experiment2.py: 100%

42 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-31 10:05 +0000

1# /// script 

2# requires-python = ">=3.12" 

3# dependencies = [ 

4# "marimo==0.23.15", 

5# "numpy==2.4.6", 

6# "plotly==6.9.0", 

7# "polars==1.43.1", 

8# "jquantstats==0.9.7" 

9# ] 

10# /// 

11 

12"""Experiment 2: Improved CTA strategy with volatility scaling. 

13 

14This module enhances the basic trend-following strategy by incorporating 

15volatility scaling to adjust position sizes based on market conditions. 

16""" 

17 

18import marimo 

19 

20__generated_with = "0.23.1" 

21app = marimo.App() 

22 

23with app.setup: 

24 import sys 

25 from pathlib import Path 

26 

27 import marimo as mo 

28 import polars as pl 

29 from jquantstats import Portfolio 

30 

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

32 

33 from preamble import date_col, load_prices 

34 

35 prices = load_prices(__file__) 

36 prices_only = prices.drop(date_col) 

37 

38 

39@app.cell(hide_code=True) 

40def _(): 

41 mo.md(r"""# CTA 2.0""") 

42 return 

43 

44 

45@app.function 

46def f(price: "pl.Expr", fast: int = 32, slow: int = 96, volatility: int = 32) -> "pl.Expr": 

47 """Return the volatility-scaled EWM crossover signal.""" 

48 return ( 

49 price.ewm_mean(com=fast, min_samples=300) - price.ewm_mean(com=slow, min_samples=300) 

50 ).sign() / price.pct_change().ewm_std(com=volatility, min_samples=300) 

51 

52 

53@app.cell 

54def _(): 

55 fast = mo.ui.slider(4, 192, step=4, value=32, label="Fast Moving Average") 

56 slow = mo.ui.slider(4, 192, step=4, value=96, label="Slow Moving Average") 

57 vola = mo.ui.slider(4, 192, step=4, value=32, label="Volatility") 

58 

59 mo.vstack([fast, slow, vola]) 

60 

61 return fast, slow, vola 

62 

63 

64@app.cell 

65def _(fast, slow, vola): 

66 signals = prices_only.select( 

67 f(pl.all(), fast=fast.value, slow=slow.value, volatility=vola.value).fill_null(0.0) * 1e5 

68 ) 

69 portfolio = Portfolio.from_cash_position(prices=prices, cash_position=signals, aum=1e8) 

70 return (portfolio,) 

71 

72 

73@app.cell 

74def _(portfolio): 

75 print(portfolio.stats.sharpe()) 

76 

77 

78@app.cell(hide_code=True) 

79def _(): 

80 mo.md( 

81 r""" 

82 * This is a **univariate** trading system, we map the (real) price of an asset to its (cash)position 

83 * Only 3 **free parameters** used here. 

84 * Scaling the bet-size by volatility has improved the situation. 

85 """ 

86 ) 

87 return 

88 

89 

90@app.cell(hide_code=True) 

91def _(): 

92 mo.md( 

93 r""" 

94 Results do not look terrible but... 

95 * No concept of risk integrated 

96 

97 Often hedge funds outsource the risk management to some board or committee 

98 and develop machinery for more systematic **parameter-hacking**. 

99 """ 

100 ) 

101 return 

102 

103 

104@app.cell 

105def _(portfolio): 

106 portfolio.plots.snapshot() 

107 return 

108 

109 

110if __name__ == "__main__": 

111 app.run()