galaaz 2.1.8 → 2.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/Rakefile +20 -2
  4. data/bin/check_gemfile_lock_version +46 -0
  5. data/bin/release_bump +26 -0
  6. data/blogs/README.md +4 -0
  7. data/blogs/galaaz_2_0/galaaz_2_0.Rmd +385 -0
  8. data/blogs/galaaz_2_0/galaaz_2_0.md +409 -0
  9. data/blogs/galaaz_2_0/galaaz_2_0.tex +756 -0
  10. data/blogs/galaaz_2_0/images/galaaz-header.png +0 -0
  11. data/blogs/galaaz_2_0/images/galaaz-lockup-stacked.png +0 -0
  12. data/blogs/galaaz_ggplot/galaaz_ggplot.Rmd +14 -1
  13. data/blogs/galaaz_ggplot/galaaz_ggplot.md +123 -103
  14. data/blogs/galaaz_ggplot/galaaz_ggplot.tex +60 -23
  15. data/blogs/galaaz_ggplot/images/galaaz-lockup-stacked.png +0 -0
  16. data/blogs/gknit/gknit.Rmd +16 -1
  17. data/blogs/gknit/gknit.md +13 -1
  18. data/blogs/gknit/gknit.tex +64 -23
  19. data/blogs/gknit/gknit_files/figure-html/bubble-1.png +0 -0
  20. data/blogs/gknit/gknit_files/figure-html/diverging_bar.png +0 -0
  21. data/blogs/gknit/gknit_files/figure-latex/bubble-1.png +0 -0
  22. data/blogs/gknit/images/galaaz-lockup-stacked.png +0 -0
  23. data/blogs/manual/images/galaaz-lockup-stacked.png +0 -0
  24. data/blogs/manual/manual.Rmd +32 -11
  25. data/blogs/manual/manual.md +30 -23
  26. data/blogs/manual/manual.tex +88 -66
  27. data/blogs/manual/manual_files/figure-html/bubble-1.png +0 -0
  28. data/blogs/manual/manual_files/figure-latex/bubble-1.png +0 -0
  29. data/blogs/nse_dplyr/images/galaaz-lockup-stacked.png +0 -0
  30. data/blogs/nse_dplyr/nse_dplyr.Rmd +14 -1
  31. data/blogs/nse_dplyr/nse_dplyr.md +697 -649
  32. data/blogs/nse_dplyr/nse_dplyr.tex +61 -24
  33. data/blogs/oh_my/images/galaaz-lockup-stacked.png +0 -0
  34. data/blogs/oh_my/oh_my.Rmd +14 -1
  35. data/blogs/oh_my/oh_my.md +36 -26
  36. data/blogs/oh_my/oh_my.tex +95 -58
  37. data/blogs/r_on_rails_ledger/images/00_portfolio_page.png +0 -0
  38. data/blogs/r_on_rails_ledger/images/01_results_panel.png +0 -0
  39. data/blogs/r_on_rails_ledger/images/02_density_tail_risk.png +0 -0
  40. data/blogs/r_on_rails_ledger/images/03_mc_cone.png +0 -0
  41. data/blogs/r_on_rails_ledger/images/04_rolling_var.png +0 -0
  42. data/blogs/r_on_rails_ledger/images/galaaz-lockup-stacked.png +0 -0
  43. data/blogs/r_on_rails_ledger/r_on_rails_ledger.Rmd +354 -0
  44. data/blogs/r_on_rails_ledger/r_on_rails_ledger.md +365 -0
  45. data/blogs/r_on_rails_ledger/r_on_rails_ledger.tex +670 -0
  46. data/blogs/ruby_plot/images/galaaz-lockup-stacked.png +0 -0
  47. data/blogs/ruby_plot/ruby_plot.Rmd +14 -1
  48. data/blogs/ruby_plot/ruby_plot.md +11 -1
  49. data/blogs/ruby_plot/ruby_plot.tex +60 -23
  50. data/blogs/ruby_plot/ruby_plot_files/figure-html/facets_with_jitter.png +0 -0
  51. data/blogs/ruby_plot/ruby_plot_files/figure-html/final_violin_plot.png +0 -0
  52. data/blogs/ruby_plot/ruby_plot_files/figure-html/violin_with_jitter.png +0 -0
  53. data/blogs/ruby_plot/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  54. data/blogs/ruby_plot/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  55. data/blogs/ruby_plot/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  56. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/facets_with_jitter.png +0 -0
  57. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/final_violin_plot.png +0 -0
  58. data/blogs/ruby_plot/ruby_plot_files/ruby_plot_files/figure-latex/violin_with_jitter.png +0 -0
  59. data/lib/galaaz/cli.rb +47 -3
  60. data/logos/icon-font/README.md +27 -0
  61. data/logos/icon-font/build_font.py +130 -0
  62. data/logos/icon-font/galaaz-mark.svg +34 -0
  63. data/script/omarchy/README.md +8 -1
  64. data/script/omarchy/fonts/galaaz.ttf +0 -0
  65. data/script/omarchy/install-galaaz.sh +8 -1
  66. data/script/omarchy/omarchy-menu.jsonc +21 -9
  67. data/sty/galaaz-header.png +0 -0
  68. data/sty/galaaz-headers-from-p3.tex +4 -0
  69. data/sty/galaaz.sty +54 -23
  70. data/version.rb +1 -1
  71. metadata +30 -9
  72. data/blogs/galaaz_ggplot/galaaz_ggplot.log +0 -745
  73. data/blogs/gknit/gknit_files/gknit_files/figure-latex/bubble-1.png +0 -0
  74. data/blogs/manual/manual.log +0 -1530
  75. data/blogs/manual/manual_files/manual_files/figure-latex/bubble-1.png +0 -0
  76. data/blogs/nse_dplyr/nse_dplyr.log +0 -824
  77. data/blogs/oh_my/oh_my.log +0 -974
  78. data/blogs/ruby_plot/ruby_plot.log +0 -887
@@ -0,0 +1,670 @@
1
+ % Options for packages loaded elsewhere
2
+ \PassOptionsToPackage{unicode}{hyperref}
3
+ \PassOptionsToPackage{hyphens}{url}
4
+ \documentclass[
5
+ 11pt,
6
+ ]{article}
7
+ \usepackage{xcolor}
8
+ \usepackage[margin=1in]{geometry}
9
+ \usepackage{amsmath,amssymb}
10
+ \setcounter{secnumdepth}{5}
11
+ \usepackage{iftex}
12
+ \ifPDFTeX
13
+ \usepackage[T1]{fontenc}
14
+ \usepackage[utf8]{inputenc}
15
+ \usepackage{textcomp} % provide euro and other symbols
16
+ \else % if luatex or xetex
17
+ \usepackage{unicode-math} % this also loads fontspec
18
+ \defaultfontfeatures{Scale=MatchLowercase}
19
+ \defaultfontfeatures[\rmfamily]{Ligatures=TeX,Scale=1}
20
+ \fi
21
+ \usepackage{lmodern}
22
+ \ifPDFTeX\else
23
+ % xetex/luatex font selection
24
+ \fi
25
+ % Use upquote if available, for straight quotes in verbatim environments
26
+ \IfFileExists{upquote.sty}{\usepackage{upquote}}{}
27
+ \IfFileExists{microtype.sty}{% use microtype if available
28
+ \usepackage[]{microtype}
29
+ \UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
30
+ }{}
31
+ \makeatletter
32
+ \@ifundefined{KOMAClassName}{% if non-KOMA class
33
+ \IfFileExists{parskip.sty}{%
34
+ \usepackage{parskip}
35
+ }{% else
36
+ \setlength{\parindent}{0pt}
37
+ \setlength{\parskip}{6pt plus 2pt minus 1pt}}
38
+ }{% if KOMA class
39
+ \KOMAoptions{parskip=half}}
40
+ \makeatother
41
+ \usepackage{color}
42
+ \usepackage{fancyvrb}
43
+ \newcommand{\VerbBar}{|}
44
+ \newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
45
+ \DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
46
+ % Add ',fontsize=\small' for more characters per line
47
+ \usepackage{framed}
48
+ \definecolor{shadecolor}{RGB}{248,248,248}
49
+ \newenvironment{Shaded}{\begin{snugshade}}{\end{snugshade}}
50
+ \newcommand{\AlertTok}[1]{\textcolor[rgb]{0.94,0.16,0.16}{#1}}
51
+ \newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}}
52
+ \newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{#1}}
53
+ \newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}}
54
+ \newcommand{\BuiltInTok}[1]{#1}
55
+ \newcommand{\CharTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}}
56
+ \newcommand{\CommentTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textit{#1}}}
57
+ \newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}}
58
+ \newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{#1}}
59
+ \newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{\textbf{#1}}}
60
+ \newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{#1}}
61
+ \newcommand{\DecValTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}}
62
+ \newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}}
63
+ \newcommand{\ErrorTok}[1]{\textcolor[rgb]{0.64,0.00,0.00}{\textbf{#1}}}
64
+ \newcommand{\ExtensionTok}[1]{#1}
65
+ \newcommand{\FloatTok}[1]{\textcolor[rgb]{0.00,0.00,0.81}{#1}}
66
+ \newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{\textbf{#1}}}
67
+ \newcommand{\ImportTok}[1]{#1}
68
+ \newcommand{\InformationTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}}
69
+ \newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.13,0.29,0.53}{\textbf{#1}}}
70
+ \newcommand{\NormalTok}[1]{#1}
71
+ \newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.81,0.36,0.00}{\textbf{#1}}}
72
+ \newcommand{\OtherTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{#1}}
73
+ \newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textit{#1}}}
74
+ \newcommand{\RegionMarkerTok}[1]{#1}
75
+ \newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.81,0.36,0.00}{\textbf{#1}}}
76
+ \newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}}
77
+ \newcommand{\StringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}}
78
+ \newcommand{\VariableTok}[1]{\textcolor[rgb]{0.00,0.00,0.00}{#1}}
79
+ \newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.31,0.60,0.02}{#1}}
80
+ \newcommand{\WarningTok}[1]{\textcolor[rgb]{0.56,0.35,0.01}{\textbf{\textit{#1}}}}
81
+ \usepackage{longtable,booktabs,array}
82
+ \newcounter{none} % for unnumbered tables
83
+ \usepackage{calc} % for calculating minipage widths
84
+ % Correct order of tables after \paragraph or \subparagraph
85
+ \usepackage{etoolbox}
86
+ \makeatletter
87
+ \patchcmd\longtable{\par}{\if@noskipsec\mbox{}\fi\par}{}{}
88
+ \makeatother
89
+ % Allow footnotes in longtable head/foot
90
+ \IfFileExists{footnotehyper.sty}{\usepackage{footnotehyper}}{\usepackage{footnote}}
91
+ \makesavenoteenv{longtable}
92
+ \usepackage{graphicx}
93
+ \makeatletter
94
+ \newsavebox\pandoc@box
95
+ \newcommand*\pandocbounded[1]{% scales image to fit in text height/width
96
+ \sbox\pandoc@box{#1}%
97
+ \Gscale@div\@tempa{\textheight}{\dimexpr\ht\pandoc@box+\dp\pandoc@box\relax}%
98
+ \Gscale@div\@tempb{\linewidth}{\wd\pandoc@box}%
99
+ \ifdim\@tempb\p@<\@tempa\p@\let\@tempa\@tempb\fi% select the smaller of both
100
+ \ifdim\@tempa\p@<\p@\scalebox{\@tempa}{\usebox\pandoc@box}%
101
+ \else\usebox{\pandoc@box}%
102
+ \fi%
103
+ }
104
+ % Set default figure placement to htbp
105
+ \def\fps@figure{htbp}
106
+ \makeatother
107
+ \setlength{\emergencystretch}{3em} % prevent overfull lines
108
+ \providecommand{\tightlist}{%
109
+ \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
110
+ % usar portugues do Brasil
111
+ % \usepackage[brazilian]{babel}
112
+ \usepackage[utf8]{inputenc}
113
+
114
+ \usepackage{geometry}
115
+ \geometry{a4paper, top=1.05in, headheight=30pt, headsep=14pt}
116
+
117
+ % needed for kableExtra
118
+ \usepackage{longtable}
119
+ \usepackage{multirow}
120
+ \usepackage[table]{xcolor}
121
+ \usepackage{wrapfig}
122
+ \usepackage{float}
123
+ \usepackage{colortbl}
124
+ \usepackage{pdflscape}
125
+ \usepackage{tabu}
126
+ \usepackage{threeparttable}
127
+ \usepackage[normalem]{ulem}
128
+
129
+ \usepackage{bbm}
130
+ \usepackage{booktabs}
131
+ \usepackage{expex}
132
+
133
+ \usepackage{graphicx}
134
+
135
+ \usepackage{fancyhdr}
136
+ % Header: small Galaaz mark (left); page number + section title (right),
137
+ % matching classic LaTeX style: "4 AN EXTENSION TO THE EXAMPLE"
138
+ % (upright page number, uppercase italic section name).
139
+ % Logo paths cover repo blogs (../../sty/), installed ~/sty/, and local images/.
140
+ % Blogs may delay headers with \renewcommand{\galaazheaderfrompage}{3} after this sty
141
+ % (e.g. skip title/TOC and a logo splash page).
142
+ \newcommand{\galaazheaderfrompage}{1}
143
+ \newcommand{\galaazheaderlogo}{%
144
+ \raisebox{-0.12\height}{%
145
+ \IfFileExists{../../sty/galaaz-header.png}{%
146
+ \includegraphics[height=0.32in]{../../sty/galaaz-header.png}%
147
+ }{%
148
+ \IfFileExists{galaaz-header.png}{%
149
+ \includegraphics[height=0.32in]{galaaz-header.png}%
150
+ }{%
151
+ \IfFileExists{images/galaaz-header.png}{%
152
+ \includegraphics[height=0.32in]{images/galaaz-header.png}%
153
+ }{}%
154
+ }%
155
+ }%
156
+ }%
157
+ }
158
+ % Section title only in marks (no \thesection); page number is separate.
159
+ \renewcommand{\sectionmark}[1]{\markboth{#1}{}}
160
+ \renewcommand{\subsectionmark}[1]{}% keep the current section in the header
161
+ \newcommand{\galaazmaybeheaderlogo}{%
162
+ \ifnum\value{page}<\galaazheaderfrompage\relax\else\galaazheaderlogo\fi}
163
+ \newcommand{\galaazmayberighthead}{%
164
+ \ifnum\value{page}<\galaazheaderfrompage\relax
165
+ \else
166
+ \textup{\thepage}\hspace{1em}\textit{\MakeUppercase{\leftmark}}%
167
+ \fi
168
+ }
169
+ \pagestyle{fancy}
170
+ \fancyhf{}
171
+ \fancyhead[L]{\galaazmaybeheaderlogo}
172
+ \fancyhead[R]{\galaazmayberighthead}
173
+ \renewcommand{\headrulewidth}{0.3pt}
174
+ \makeatletter
175
+ \renewcommand{\headrule}{%
176
+ \ifnum\value{page}<\galaazheaderfrompage\relax
177
+ \else
178
+ \hrule\@height\headrulewidth\@width\headwidth
179
+ \vskip-\headrulewidth
180
+ \fi
181
+ }
182
+ \makeatother
183
+ % First page often uses plain; same conditional header logic.
184
+ \fancypagestyle{plain}{%
185
+ \fancyhf{}%
186
+ \fancyhead[L]{\galaazmaybeheaderlogo}%
187
+ \fancyhead[R]{\galaazmayberighthead}%
188
+ \renewcommand{\headrulewidth}{0.3pt}%
189
+ }
190
+
191
+ % make available command lastpage
192
+ \usepackage{lastpage}
193
+
194
+ % default fontsize 11pt better to add
195
+ % fontsize on the yaml header
196
+ % \usepackage[fontsize=11pt]{scrextend}
197
+
198
+ % comandos para formatar uma tabela
199
+ \usepackage{array}
200
+ \newcolumntype{L}[1]{>{\raggedright\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
201
+ \newcolumntype{C}[1]{>{\centering\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
202
+ \newcolumntype{R}[1]{>{\raggedleft\let\newline\\\arraybackslash\hspace{0pt}}m{#1}}
203
+
204
+ % necessário if we need to import other latex documents
205
+ \usepackage{import}
206
+
207
+ % Command to import an R variable to latex
208
+ \newcommand{\RtoLatex}[2]{\newcommand{#1}{#2}}
209
+
210
+ % Soft-wrap Pandoc highlighted code/output boxes (Shaded + Highlighting).
211
+ % This runs after Pandoc's default \DefineVerbatimEnvironment{Highlighting}
212
+ % (header includes come later in the generated .tex). Prefer manual line
213
+ % breaks in Rmd sources; breaklines is a safety net for leftovers/output.
214
+ % Requires TinyTeX/TeX Live package: tlmgr install fvextra
215
+ \IfFileExists{fvextra.sty}{%
216
+ \usepackage{fvextra}%
217
+ \DefineVerbatimEnvironment{Highlighting}{Verbatim}{%
218
+ breaklines=true,
219
+ breakanywhere=true,
220
+ breakindent=1.5em,
221
+ fontsize=\small,
222
+ commandchars=\\\{\}%
223
+ }%
224
+ }{%
225
+ % fancyvrb is already loaded by Pandoc; shrink code so more fits per line.
226
+ \DefineVerbatimEnvironment{Highlighting}{Verbatim}{%
227
+ fontsize=\small,
228
+ commandchars=\\\{\}%
229
+ }%
230
+ }
231
+
232
+ %
233
+ %\newcommand{\atraso}[1]{\color{red} \textbf {Tempo desde a Assinatura do Contrato: #1 dias}}
234
+ % No header on title/TOC (p.1) or splash logo page (p.2);
235
+ % logo + page number + section title from page 3 onward.
236
+ % Load after galaaz.sty.
237
+ \renewcommand{\galaazheaderfrompage}{3}
238
+ \usepackage{bookmark}
239
+ \IfFileExists{xurl.sty}{\usepackage{xurl}}{} % add URL line breaks if available
240
+ \urlstyle{same}
241
+ \hypersetup{
242
+ pdftitle={R-on-Rails: ship the ledger on Rails{,} keep the science in R},
243
+ pdfauthor={Rodrigo Botafogo},
244
+ hidelinks,
245
+ pdfcreator={LaTeX via pandoc}}
246
+
247
+ \title{R-on-Rails: ship the ledger on Rails, keep the science in R}
248
+ \usepackage{etoolbox}
249
+ \makeatletter
250
+ \providecommand{\subtitle}[1]{% add subtitle to \maketitle
251
+ \apptocmd{\@title}{\par {\large #1 \par}}{}{}
252
+ }
253
+ \makeatother
254
+ \subtitle{A portfolio stress tester that puts SQLite, Hotwire, and GNU R
255
+ on one desk}
256
+ \author{Rodrigo Botafogo}
257
+ \date{2026}
258
+
259
+ \begin{document}
260
+ \maketitle
261
+
262
+ {
263
+ \setcounter{tocdepth}{2}
264
+ \tableofcontents
265
+ }
266
+ \begin{center}\includegraphics[width=0.45\linewidth]{images/galaaz-lockup-stacked} \end{center}
267
+
268
+ \section{Two audiences, one gap}\label{two-audiences-one-gap}
269
+
270
+ \textbf{If you live in R}, you already have the hard part: VaR, density
271
+ estimates, Monte Carlo, Bioconductor, the whole CRAN catalog. What is
272
+ still painful is turning a notebook into a \textbf{product}---logins, a
273
+ database, background jobs, a page that updates when the job finishes,
274
+ and a deploy story that is not a pile of glue scripts.
275
+
276
+ \textbf{If you live in Ruby on Rails}, you already have the product
277
+ shell. What is still painful is serious statistics without standing up a
278
+ Python microservice fleet, learning another ORM, and ferrying CSV files
279
+ between processes.
280
+
281
+ \textbf{R-on-Rails} is the name we give to a simple division of labor:
282
+
283
+ \begin{itemize}
284
+ \tightlist
285
+ \item
286
+ \textbf{Rails} owns HTTP, Active Record, jobs, Hotwire, and the UX.
287
+ \item
288
+ \textbf{GNU R} owns the math and the statistical vocabulary.
289
+ \item
290
+ \textbf{\href{https://github.com/rbotafogo/galaaz}{Galaaz}} is the
291
+ bridge that makes R feel like a Ruby DSL---and moves bulky tables with
292
+ Apache Arrow when you need more than a few numbers.
293
+ \end{itemize}
294
+
295
+ This post walks through a real demo app---the
296
+ \textbf{\href{https://github.com/rbotafogo/r_on_rails_ledger}{R-on-Rails
297
+ Ledger}}--- so you can see the database, the calculators, and the charts
298
+ on one page.
299
+
300
+ \section{What the Ledger is}\label{what-the-ledger-is}
301
+
302
+ The Ledger is a \textbf{family-office style portfolio stress tester} on
303
+ \textbf{Rails 8}, \textbf{CRuby}, \textbf{SQLite}, \textbf{Solid Queue /
304
+ Solid Cable}, and \textbf{Hotwire}. It is a sibling app to the Galaaz
305
+ gem (not packaged inside the gem). One click runs two engines on the
306
+ same portfolio return panel:
307
+
308
+ \begin{itemize}
309
+ \tightlist
310
+ \item
311
+ \textbf{Engine A --- historical:} empirical VaR / expected shortfall /
312
+ return density from history (\texttt{R.quantile}, \texttt{R.mean},
313
+ \texttt{R.density}).
314
+ \item
315
+ \textbf{Engine B --- Monte Carlo:} forward GBM paths calibrated to
316
+ that history, then forward VaR-style KPIs and a 30-day ``cone.''
317
+ \end{itemize}
318
+
319
+ Rails stores the book and the job results; R does the science; the
320
+ browser gets Plotly charts from JSON that R (and a little Ruby)
321
+ produced.
322
+
323
+ \section{What's in Rails (and the
324
+ database)}\label{whats-in-rails-and-the-database}
325
+
326
+ The ``ledger'' here is a \textbf{portfolio price book}, not double-entry
327
+ accounting:
328
+
329
+ {\def\LTcaptype{none} % do not increment counter
330
+ \begin{longtable}[]{@{}
331
+ >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.5000}}
332
+ >{\raggedright\arraybackslash}p{(\linewidth - 2\tabcolsep) * \real{0.5000}}@{}}
333
+ \toprule\noalign{}
334
+ \begin{minipage}[b]{\linewidth}\raggedright
335
+ Table
336
+ \end{minipage} & \begin{minipage}[b]{\linewidth}\raggedright
337
+ Role
338
+ \end{minipage} \\
339
+ \midrule\noalign{}
340
+ \endhead
341
+ \bottomrule\noalign{}
342
+ \endlastfoot
343
+ \texttt{portfolios} & Named book (\texttt{name},
344
+ \texttt{total\_value}) \\
345
+ \texttt{assets} & Tickers + weights \\
346
+ \texttt{historical\_prices} & Bars: \texttt{adjusted\_close},
347
+ \texttt{daily\_return}, \ldots{} \\
348
+ \texttt{stress\_tests} & Job status, scalar KPIs,
349
+ \texttt{chart\_payload} JSON \\
350
+ \texttt{stress\_test\_runs} & Per-engine (\texttt{historical} /
351
+ \texttt{monte\_carlo}) payloads \\
352
+ \end{longtable}
353
+ }
354
+
355
+ Seed data is \textbf{synthetic GBM} (honest demo data---not a live
356
+ feed). \texttt{SEED\_PROFILE=fast} is laptop-friendly (\textasciitilde10
357
+ assets x 2k bars); \texttt{SEED\_PROFILE=wow} is the ``about a million
358
+ bars'' line.
359
+
360
+ The click path is ordinary Rails:
361
+
362
+ \begin{Shaded}
363
+ \begin{Highlighting}[]
364
+ \NormalTok{POST /portfolios/:id/stress\_tests}
365
+ \NormalTok{ {-}\textgreater{} StressTestsController\#create}
366
+ \NormalTok{ {-}\textgreater{} StressTestJob.perform\_later(...)}
367
+ \NormalTok{ {-}\textgreater{} Risk::DualOrchestrator}
368
+ \NormalTok{ |{-} Risk::HistoricalEngine}
369
+ \NormalTok{ |{-} Risk::MonteCarloEngine}
370
+ \NormalTok{ {-}\textgreater{} Turbo::StreamsChannel broadcast}
371
+ \NormalTok{ {-}\textgreater{} results partial + Plotly redraw}
372
+ \end{Highlighting}
373
+ \end{Shaded}
374
+
375
+ No React SPA. No Redis required for the demo. Development often runs the
376
+ job \textbf{\texttt{:async}} in-process so one \texttt{bin/dev} is
377
+ enough; Solid Queue stays in the Gemfile for production-shaped runs.
378
+
379
+ \section{R calculators that read like
380
+ Ruby}\label{r-calculators-that-read-like-ruby}
381
+
382
+ After returns land in R (via Arrow---see below), the historical engine
383
+ exposes small methods that call R the way Galaaz intends: \textbf{named
384
+ functions and vectors}, not a wall of quoted R source for every KPI.
385
+
386
+ From the Ledger's \texttt{Risk::HistoricalEngine} (abbreviated):
387
+
388
+ \begin{Shaded}
389
+ \begin{Highlighting}[]
390
+ \ControlFlowTok{def}\NormalTok{ historical\_var(returns\_vec, probs}\OperatorTok{:}\NormalTok{)}
391
+ \DataTypeTok{RValues}\AttributeTok{.scalar\_f}\NormalTok{(}
392
+ \ConstantTok{R}\AttributeTok{.as\_\_numeric}\NormalTok{(}
393
+ \ConstantTok{R}\AttributeTok{.quantile}\NormalTok{(returns\_vec,}
394
+ \WarningTok{probs:}\NormalTok{ probs,}
395
+ \WarningTok{names:} \DecValTok{false}\NormalTok{,}
396
+ \WarningTok{type:} \DecValTok{7}\NormalTok{)}
397
+ \NormalTok{ )}
398
+ \NormalTok{ )}
399
+ \ControlFlowTok{end}
400
+
401
+ \ControlFlowTok{def}\NormalTok{ expected\_shortfall(returns\_vec, var\_level)}
402
+ \NormalTok{ thr }\OperatorTok{=}\NormalTok{ var\_level}\AttributeTok{.is\_a?}\NormalTok{(}\DataTypeTok{Numeric}\NormalTok{) }\OperatorTok{?}
403
+ \NormalTok{ var\_level }\OperatorTok{:} \DataTypeTok{RValues}\AttributeTok{.scalar\_f}\NormalTok{(var\_level)}
404
+ \DataTypeTok{RValues}\AttributeTok{.scalar\_f}\NormalTok{(}
405
+ \ConstantTok{R}\AttributeTok{.as\_\_numeric}\NormalTok{(}
406
+ \ConstantTok{R}\AttributeTok{.mean}\NormalTok{(returns\_vec}\KeywordTok{[}\NormalTok{returns\_vec }\OperatorTok{\textless{}=}\NormalTok{ thr}\KeywordTok{]}\NormalTok{)}
407
+ \NormalTok{ )}
408
+ \NormalTok{ )}
409
+ \ControlFlowTok{end}
410
+
411
+ \ControlFlowTok{def}\NormalTok{ return\_density(returns\_vec, n}\OperatorTok{:} \DecValTok{128}\NormalTok{)}
412
+ \DataTypeTok{ArrowHandoff}\AttributeTok{.density\_xy\_for\_plotly}\NormalTok{(returns\_vec, }\WarningTok{n:}\NormalTok{ n)}
413
+ \ControlFlowTok{end}
414
+ \end{Highlighting}
415
+ \end{Shaded}
416
+
417
+ That is the pitch for Ruby developers: \texttt{R.quantile} is a method
418
+ call. For R developers: the quantile is still \textbf{R's}
419
+ quantile---same \texttt{type\ =\ 7} story you know---just invoked from
420
+ the app that owns the database.
421
+
422
+ A tiny live taste of the same idea (outside Rails):
423
+
424
+ \begin{Shaded}
425
+ \begin{Highlighting}[]
426
+ \NormalTok{x }\OperatorTok{=} \ConstantTok{R}\AttributeTok{.c}\NormalTok{(}\OperatorTok{{-}}\FloatTok{0.02}\NormalTok{, }\OperatorTok{{-}}\FloatTok{0.01}\NormalTok{, }\FloatTok{0.0}\NormalTok{, }\FloatTok{0.01}\NormalTok{, }\FloatTok{0.015}\NormalTok{, }\OperatorTok{{-}}\FloatTok{0.005}\NormalTok{)}
427
+ \NormalTok{q05 }\OperatorTok{=} \ConstantTok{R}\AttributeTok{.as\_\_numeric}\NormalTok{(}
428
+ \ConstantTok{R}\AttributeTok{.quantile}\NormalTok{(x, }\WarningTok{probs:} \FloatTok{0.05}\NormalTok{, }\WarningTok{names:} \DecValTok{false}\NormalTok{, }\WarningTok{type:} \DecValTok{7}\NormalTok{))}
429
+ \FunctionTok{puts} \StringTok{"5\% quantile: }\SpecialCharTok{\#\{}\NormalTok{q05 }\OperatorTok{\textgreater{}\textgreater{}} \DecValTok{0}\SpecialCharTok{\}}\StringTok{"}
430
+ \end{Highlighting}
431
+ \end{Shaded}
432
+
433
+ \begin{verbatim}
434
+ ## 5% quantile: -0.017499999999999998
435
+ \end{verbatim}
436
+
437
+ Engine B calibrates mu/sigma in R, simulates GBM paths, and returns
438
+ structured fields (\texttt{sim\_var\_30d}, sample paths, terminal
439
+ density) that Rails stores on each run's \texttt{payload} JSON.
440
+
441
+ \section{How data moves (Arrow,
442
+ honestly)}\label{how-data-moves-arrow-honestly}
443
+
444
+ Crossing a process boundary means you must be honest about
445
+ \textbf{copies}. Galaaz can move columnar data with Apache Arrow in
446
+ three ways:
447
+
448
+ \begin{enumerate}
449
+ \def\labelenumi{\arabic{enumi}.}
450
+ \tightlist
451
+ \item
452
+ \textbf{Copy into R} --- Ruby builds batches; R materialises an Arrow
453
+ table / proxy. Simple and available today.
454
+ \item
455
+ \textbf{IPC file handoff} --- Ruby writes an Arrow IPC file (often
456
+ under \texttt{/dev/shm}); R opens it by \textbf{path}. Only the path
457
+ crosses NewBridge---not megabytes of MsgPack. This is what the Ledger
458
+ prefers for the return panel.
459
+ \item
460
+ \textbf{Shared-memory zero-copy} --- both processes attach to one live
461
+ segment. \textbf{Not implemented yet}; we do not claim it.
462
+ \end{enumerate}
463
+
464
+ \subsection{Example: copy into R}\label{example-copy-into-r}
465
+
466
+ Good when the table is modest or you do not have a Ruby Arrow writer
467
+ installed. One call builds the R-side table; then you Remote-Control it.
468
+
469
+ \begin{Shaded}
470
+ \begin{Highlighting}[]
471
+ \NormalTok{arrow\_ok }\OperatorTok{=} \ConstantTok{R}\OperatorTok{::}\DataTypeTok{Support}\AttributeTok{.eval}\NormalTok{(}
472
+ \StringTok{"requireNamespace(\textquotesingle{}arrow\textquotesingle{}, quietly=TRUE)"}\NormalTok{) }\OperatorTok{==} \DecValTok{true}
473
+ \ControlFlowTok{unless}\NormalTok{ arrow\_ok}
474
+ \FunctionTok{puts} \VerbatimStringTok{\textquotesingle{}(Skip: need R package arrow.)\textquotesingle{}}
475
+ \ControlFlowTok{else}
476
+ \NormalTok{ batches }\OperatorTok{=} \KeywordTok{[}
477
+ \KeywordTok{[}\OperatorTok{\{} \WarningTok{daily\_return:} \OperatorTok{{-}}\FloatTok{0.01} \OperatorTok{\}}\NormalTok{, }\OperatorTok{\{} \WarningTok{daily\_return:} \FloatTok{0.02} \OperatorTok{\}}\KeywordTok{]}\NormalTok{,}
478
+ \KeywordTok{[}\OperatorTok{\{} \WarningTok{daily\_return:} \FloatTok{0.005} \OperatorTok{\}}\NormalTok{, }\OperatorTok{\{} \WarningTok{daily\_return:} \OperatorTok{{-}}\FloatTok{0.003} \OperatorTok{\}}\KeywordTok{]}
479
+ \KeywordTok{]}
480
+ \NormalTok{ tbl }\OperatorTok{=} \ConstantTok{R}\OperatorTok{::}\DataTypeTok{Arrow}\AttributeTok{.from\_ruby\_batches}\NormalTok{(batches)}
481
+ \FunctionTok{puts} \StringTok{"R class: }\SpecialCharTok{\#\{}\NormalTok{tbl}\AttributeTok{.rclass}\SpecialCharTok{\}}\StringTok{"}
482
+ \NormalTok{ vec }\OperatorTok{=} \ConstantTok{R}\AttributeTok{.as\_\_numeric}\NormalTok{(}
483
+ \ConstantTok{R}\AttributeTok{.dplyr\_\_\_collect}\NormalTok{(tbl)}\KeywordTok{[[}\VerbatimStringTok{\textquotesingle{}daily\_return\textquotesingle{}}\KeywordTok{]]}\NormalTok{)}
484
+ \NormalTok{ q05 }\OperatorTok{=} \ConstantTok{R}\AttributeTok{.as\_\_numeric}\NormalTok{(}
485
+ \ConstantTok{R}\AttributeTok{.quantile}\NormalTok{(vec, }\WarningTok{probs:} \FloatTok{0.05}\NormalTok{, }\WarningTok{names:} \DecValTok{false}\NormalTok{, }\WarningTok{type:} \DecValTok{7}\NormalTok{))}
486
+ \FunctionTok{puts} \StringTok{"5\% quantile: }\SpecialCharTok{\#\{}\NormalTok{q05 }\OperatorTok{\textgreater{}\textgreater{}} \DecValTok{0}\SpecialCharTok{\}}\StringTok{"}
487
+ \ControlFlowTok{end}
488
+ \end{Highlighting}
489
+ \end{Shaded}
490
+
491
+ \begin{verbatim}
492
+ ## R class: Table
493
+ ## 5% quantile: -0.00895
494
+ \end{verbatim}
495
+
496
+ The Ledger's fallback path is similar: a small \texttt{data.frame} plus
497
+ \texttt{R::Arrow.table\_from(df)} when IPC is unavailable.
498
+
499
+ \subsection{Example: IPC file handoff}\label{example-ipc-file-handoff}
500
+
501
+ Preferred for larger same-machine panels. Ruby writes the file; R opens
502
+ by path; Ruby can unlink after R has the table. The reverse direction
503
+ (\texttt{R::Arrow.write\_ipc} then \texttt{Galaaz::ArrowIpc.read}) is
504
+ how the Ledger can pull density coordinates back for Plotly.
505
+
506
+ \begin{Shaded}
507
+ \begin{Highlighting}[]
508
+ \NormalTok{ipc\_ok }\OperatorTok{=} \DataTypeTok{Galaaz}\OperatorTok{::}\DataTypeTok{ArrowIpc}\AttributeTok{.available?} \OperatorTok{\&\&}
509
+ \NormalTok{ (}\ConstantTok{R}\OperatorTok{::}\DataTypeTok{Support}\AttributeTok{.eval}\NormalTok{(}
510
+ \StringTok{"requireNamespace(\textquotesingle{}arrow\textquotesingle{}, quietly=TRUE)"}\NormalTok{) }\OperatorTok{==} \DecValTok{true}\NormalTok{)}
511
+ \ControlFlowTok{unless}\NormalTok{ ipc\_ok}
512
+ \FunctionTok{puts} \VerbatimStringTok{\textquotesingle{}(Skip: need Arrow IPC backend + R arrow.)\textquotesingle{}}
513
+ \ControlFlowTok{else}
514
+ \NormalTok{ returns }\OperatorTok{=} \KeywordTok{[}\OperatorTok{{-}}\FloatTok{0.01}\NormalTok{, }\FloatTok{0.02}\NormalTok{, }\FloatTok{0.005}\NormalTok{, }\OperatorTok{{-}}\FloatTok{0.003}\NormalTok{, }\OperatorTok{{-}}\FloatTok{0.008}\KeywordTok{]}
515
+ \NormalTok{ path }\OperatorTok{=} \DataTypeTok{Galaaz}\OperatorTok{::}\DataTypeTok{ArrowIpc}\AttributeTok{.write}\NormalTok{(}
516
+ \VerbatimStringTok{\textquotesingle{}daily\_return\textquotesingle{}} \OperatorTok{=\textgreater{}}\NormalTok{ returns}\AttributeTok{.map}\NormalTok{(}\OperatorTok{\&}\WarningTok{:to\_f}\NormalTok{))}
517
+ \ControlFlowTok{begin}
518
+ \NormalTok{ tbl }\OperatorTok{=} \ConstantTok{R}\OperatorTok{::}\DataTypeTok{Arrow}\AttributeTok{.open\_ipc}\NormalTok{(path)}
519
+ \FunctionTok{puts} \StringTok{"R class: }\SpecialCharTok{\#\{}\NormalTok{tbl}\AttributeTok{.rclass}\SpecialCharTok{\}}\StringTok{"}
520
+ \FunctionTok{puts} \StringTok{"IPC: }\SpecialCharTok{\#\{}\DataTypeTok{File}\AttributeTok{.basename}\NormalTok{(path)}\SpecialCharTok{\}}\StringTok{"}
521
+ \NormalTok{ vec }\OperatorTok{=} \ConstantTok{R}\AttributeTok{.as\_\_numeric}\NormalTok{(}
522
+ \ConstantTok{R}\AttributeTok{.dplyr\_\_\_collect}\NormalTok{(tbl)}\KeywordTok{[[}\VerbatimStringTok{\textquotesingle{}daily\_return\textquotesingle{}}\KeywordTok{]]}\NormalTok{)}
523
+ \NormalTok{ q05 }\OperatorTok{=} \ConstantTok{R}\AttributeTok{.as\_\_numeric}\NormalTok{(}
524
+ \ConstantTok{R}\AttributeTok{.quantile}\NormalTok{(vec, }\WarningTok{probs:} \FloatTok{0.05}\NormalTok{, }\WarningTok{names:} \DecValTok{false}\NormalTok{, }\WarningTok{type:} \DecValTok{7}\NormalTok{))}
525
+ \FunctionTok{puts} \StringTok{"5\% quantile: }\SpecialCharTok{\#\{}\NormalTok{q05 }\OperatorTok{\textgreater{}\textgreater{}} \DecValTok{0}\SpecialCharTok{\}}\StringTok{"}
526
+ \ControlFlowTok{ensure}
527
+ \DataTypeTok{Galaaz}\OperatorTok{::}\DataTypeTok{ArrowIpc}\AttributeTok{.release}\NormalTok{(path)}
528
+ \ControlFlowTok{end}
529
+ \ControlFlowTok{end}
530
+ \end{Highlighting}
531
+ \end{Shaded}
532
+
533
+ \begin{verbatim}
534
+ ## R class: Table
535
+ ## IPC: galaaz_ipc_34399_93fe645f9fa9172f.arrow
536
+ ## 5% quantile: -0.0096
537
+ \end{verbatim}
538
+
539
+ Then the product rule is \textbf{Remote Control}: keep the heavy panel
540
+ in R; unbox \textbf{KPIs and chart coordinates} back to Ruby for SQLite
541
+ and the browser.
542
+
543
+ \begin{Shaded}
544
+ \begin{Highlighting}[]
545
+ \NormalTok{SQLite {-}\textgreater{} portfolio return series (Ruby)}
546
+ \NormalTok{ {-}\textgreater{} Arrow IPC file (path on the bridge)}
547
+ \NormalTok{ {-}\textgreater{} GNU R (quantile / density / GBM)}
548
+ \NormalTok{ {-}\textgreater{} JSON{-}ish payloads on SQLite}
549
+ \NormalTok{ {-}\textgreater{} Turbo Stream {-}\textgreater{} Stimulus {-}\textgreater{} Plotly.js}
550
+ \end{Highlighting}
551
+ \end{Shaded}
552
+
553
+ \section{The page with the charts}\label{the-page-with-the-charts}
554
+
555
+ After a Local R stress test, the portfolio show page replaces the
556
+ results panel in place. Captures below are from a live run of the demo
557
+ (synthetic seed, Local engines, Plotly in the browser---\textbf{not}
558
+ ggplot2 SVG).
559
+
560
+ \subsection{Side-by-side KPIs}\label{side-by-side-kpis}
561
+
562
+ Engine A is \textbf{1-day historical} VaR. Engine B is \textbf{30-day
563
+ forward} Monte Carlo. The UI says so on purpose: compare directionally,
564
+ not as identical metrics.
565
+
566
+ \begin{figure}
567
+ \includegraphics[width=1\linewidth]{images/01_results_panel} \caption{Ledger results panel: Local R runtime banner, KPI table, and three Plotly charts.}\label{fig:fig_panel}
568
+ \end{figure}
569
+
570
+ \subsection{Density and tail risk}\label{density-and-tail-risk}
571
+
572
+ Historical kernel density from Engine A, Monte Carlo terminal-return
573
+ density from Engine B, with VaR markers as vertical lines.
574
+
575
+ \begin{figure}
576
+ \includegraphics[width=1\linewidth]{images/02_density_tail_risk} \caption{Density and tail risk (Plotly): historical vs Monte Carlo terminal returns.}\label{fig:fig_density}
577
+ \end{figure}
578
+
579
+ \subsection{Thirty-day Monte Carlo
580
+ cone}\label{thirty-day-monte-carlo-cone}
581
+
582
+ Sample paths, a 5--95\% band, median, and baseline---forward uncertainty
583
+ as a picture, not only a scalar.
584
+
585
+ \begin{figure}
586
+ \includegraphics[width=1\linewidth]{images/03_mc_cone} \caption{30-day Monte Carlo cone (Plotly): paths, median, and 5–95\% band.}\label{fig:fig_cone}
587
+ \end{figure}
588
+
589
+ \subsection{Rolling historical VaR and
590
+ breaches}\label{rolling-historical-var-and-breaches}
591
+
592
+ Trailing returns against a rolling 95\% VaR line, with breach markers
593
+ when returns punch through the threshold.
594
+
595
+ \begin{figure}
596
+ \includegraphics[width=1\linewidth]{images/04_rolling_var} \caption{Rolling historical VaR and breaches (Plotly).}\label{fig:fig_rolling}
597
+ \end{figure}
598
+
599
+ \section{Why this is a production-shaped
600
+ story}\label{why-this-is-a-production-shaped-story}
601
+
602
+ \begin{itemize}
603
+ \tightlist
604
+ \item
605
+ \textbf{One desk.} Rails conventions for the app; CRAN for the
606
+ science.
607
+ \item
608
+ \textbf{Jobs, not request-thread math.} Stress work runs in
609
+ \texttt{StressTestJob}; the UI waits on Turbo/Cable.
610
+ \item
611
+ \textbf{Process isolation.} GNU R is a separate process (NewBridge). A
612
+ bad R call need not take down Puma.
613
+ \item
614
+ \textbf{Optional dual-version R.} The same job can target Docker
615
+ images (e.g.~R 3.6.3 \textbar\textbar{} 4.3.3) when you want version
616
+ isolation; Local alone still tells the Arrow + DSL story.
617
+ \item
618
+ \textbf{OnRails family.} Ruby on Rails -\textgreater{} Omarchy /
619
+ LinuxOnRails -\textgreater{} \textbf{R-on-Rails}---same ``one person
620
+ can ship the whole product'' energy, applied to people whose science
621
+ already lives in R.
622
+ \end{itemize}
623
+
624
+ \section{Try it}\label{try-it}
625
+
626
+ \begin{Shaded}
627
+ \begin{Highlighting}[]
628
+ \FunctionTok{git}\NormalTok{ clone https://github.com/rbotafogo/r\_on\_rails\_ledger.git}
629
+ \BuiltInTok{cd}\NormalTok{ r\_on\_rails\_ledger}
630
+ \ExtensionTok{bundle}\NormalTok{ install}
631
+ \ExtensionTok{bin/rails}\NormalTok{ db:prepare}
632
+ \VariableTok{SEED\_PROFILE}\OperatorTok{=}\NormalTok{fast }\ExtensionTok{bin/rails}\NormalTok{ db:seed}
633
+ \ExtensionTok{bin/dev}
634
+ \CommentTok{\# open http://localhost:3000 {-}\textgreater{} portfolio {-}\textgreater{} Run stress test}
635
+ \end{Highlighting}
636
+ \end{Shaded}
637
+
638
+ With a Galaaz checkout nearby, \texttt{galaaz\ add\ ledger} (Omarchy /
639
+ CLI helper) can clone and boot the same demo. In-app docs live at
640
+ \texttt{/docs} (architecture, Ruby DSL excerpts, runbook).
641
+
642
+ For the bridge itself---JRuby/CRuby, Arrow handoffs,
643
+ \texttt{R::Job}---see the companion post \textbf{Galaaz 2.0} in this
644
+ blog series.
645
+
646
+ \section{Honest limits}\label{honest-limits}
647
+
648
+ \begin{itemize}
649
+ \tightlist
650
+ \item
651
+ Seed prices are \textbf{synthetic}, not broker data.
652
+ \item
653
+ VaR / GBM here are a \textbf{demo risk stack}, not a bank production
654
+ engine (no full PerformanceAnalytics suite in the live path).
655
+ \item
656
+ Charts are \textbf{Plotly.js} fed by JSON coordinates---not ggplot
657
+ embedded as SVG in this build.
658
+ \item
659
+ The Ledger's Arrow path is an \textbf{IPC file / mmap handoff}, not
660
+ shared Ruby/R heap memory (zero-copy is still future work).
661
+ \item
662
+ Historical and Monte Carlo \textbf{horizons differ}; the KPI footnotes
663
+ exist so the demo does not oversell a single number.
664
+ \end{itemize}
665
+
666
+ The point of honesty is the same as the point of the architecture: Rails
667
+ can be the product, R can stay R, and you can show the charts without
668
+ pretending the hard parts vanished.
669
+
670
+ \end{document}