It's been a long time since I started a new open source project. During hiatus, I've reflected a lot on why I was even creating open source projects in the first place. Mostly, I just wanted to give back while building something that forces me to think through design, use, documentation, and so to craft something elegant.
In short, open source projects were a way for to push myself to become a better software engineer. To some extent, I also sought some sort of public gratification from having thousands adopt and use a library I conceived. But over the years, I came to realize something was missing from a large majority of these projects, but I didn't quite know what.
I realized two common themes about libraries I've built for Ruby (i.e. Ruby gems). One, my projects often served a very, very small niche. Those projects often were one-offs that not a lot of other developers would likely be interested in. Two, I lost interest in my own projects as I moved on to something else, so invariably, I drifted away from maintaining the gems I released simply because I no longer had a need for them.
So what's different this time around as I launch and continue to feverishly build out Quantitative. First, this is a library I'm not likely to lose interest in maintaining for a while. Quantitative is an extract from an actively maintained project that I use for automating investment and trading around crypto-currency, stocks, and forex markets.
Admittedly, Quantitative is one of my first attempts at fully extracting something truly complex from a working system and then retrofitting that working system with the library. That in of itself probably makes this library far more valuable and useful to others and that's a general trait I've noticed about many of the most successful projects out there. But perhaps the aspect of the effort of extracting that I am most proud of is the craftmanship and improvements I made during the whole process. I discovered many bugs in the code I was relying on that really motivated me to build test coverage around everything extracted. Some bugs, I'm not sure if they're bugs -- there's a lot of computations involved in this library with a lot of math concepts I'm barely any good at. But that's ok! This work and this project of algorithmic trading has really stretched my mathematical skills and made me better all around at math. Plus, I know if I continue to work through strengthening this library, someone will come along and tell me everything I am doing wrong and we'll make it better together!
What was really challenging in the extraction process was recasting a lot of things I did very ad-hoc and messy in the original implementation to something I wouldn't outright blush at first sight when someone else takes a gander at my work.
One thing that really got me excited to launch Quantitative was the fact that Ruby 3.3 just came out and I realized the last projects I was doing, Rails 5 and Ruby 2.4 were hot off the presses and bundler was becoming de-facto. I realized Ruby had markedly matured in the last few years and I wanted to see what it was like to build with all the latest at my command. I was not disappointed. Building Gems is more or less unchanged at the core, but there are so many shiny new language constructs I hadn't had a chance to touch on, I knew I had to do it. I used everything from prepend to refinements to Ruby's key/value shorthand notation and YJIT. One of the reasons I went to Crystal Language was because Ruby was not great at large volume of data/number crunching. YJIT dramatically speeds up algorithmic computations by many orders of magnitudes.
Aside from the language improvements, I also really got to flex my skills at some DSL/syntax-sugar that let me sprinkle in a bit of meta-programming in a way not only a joy to work in and is extensible within the library, but also extensible external to the library. Here's a great example:
module Quant
module Indicators
class FramaPoint < IndicatorPoint
attribute :frama, default: :input
attribute :dimension, default: 0.0
attribute :alpha, default: 0.0
end
class Frama < Indicator
register name: :frama
using Quant
# ...
Every Indicator has an IndicatorPoint class that defines the values as properties on a Tick that it computes. Defaults can be a value, a property from the tick itself, or even a Lambda block. attributes
is a short-hand method made possible through meta-programming to define these easily and consistently throughout the library. Meanwhile, the Indicator itself uses "register" to add itself to a global registry of Indicators. using Quant
is needed where the refined Array instances are utilized, that adds lots of helpful perks to the Array class such as mean, standard deviations, and so on. The point is, this project gave me the opportunity to explore these little-used corners of Ruby and experiment and think deeply about design.
The very same tooling that let me build out the library as shown above makes it possible for me to build my own one-offs that take full advantage of the library. Here's what one such custom Indicator looks like.
module Indicators
class RrrPoint < Quant::Indicators::IndicatorPoint
attribute :lowest, default: :input
attribute :highest, default: :input
attribute :stop_loss_range, default: 0.0
attribute :stop_loss, default: 0.0
attribute :ratio, default: 0.0
attribute :ratio_peak, default: 0.0
attribute :ratio_agc, default: 0.0
end
class Rrr < Quant::Indicators::Indicator
register name: :rrr
using Quant
depends_on Quant::Indicators::Atr
def points_class
RrrPoint
end
# ...
The big gain in the end is not that there's a brand new open-source project out there in the community. No, it's the journey I got to go on in building this library that taught me many new things along the way. I now have a far more performant tool in my toolbox and I am truly enjoying working on my personal projects.
If you happen to dig into this library and see something interesting you'd like me to cover in a future blog, please do give me a shout out. I definitely plan to dive into the deep end to surface some really cool gems in this gem of a gem.
Top comments (0)