Simple Ruby Strategy Pattern

If you enjoy this article you may be interested in the book I am working on called Building and Deploying Crypto Trading Bots. You can find more info about it here.

Let's look at an example of the strategy pattern in Ruby.

A common design pattern in software development is known as the "Strategy" pattern. The pattern proposes that when you have a scenario with multiple execution paths, each path be considered as a "strategy" that is recomposed into individual objects (or functions in functional languages). Each Strategy contains the algorithm implementation to execute that code path.

The benefit of using this technique is two fold. First, it allows the algorithm to vary from the clients that uses it. Second, it liberates instances of logic ("strategies") from individual client implementations allow for higher reusability within the software. If you enjoy UML diagrams you can see a visualization below:

Yo dawg I heard you like UML so I put UML in your UML so now you can UML while you UML

To provide a more concrete example consider if you needed to write a program that forecasted the moving trend of a particular stock. In technical charting terms this can be computed by calculating the "Simple Moving Average" of a stock price. We want to write a program that calculates the twenty four and twelve day moving averages and tells us that direction the stock price is going. Sounds easy enough – if we know the strategy pattern.

We begin by defining an abstract base class called Strategy:

# Abstract base class
class Strategy
 def initialize
  raise "abstract"
 end
 
 def exec
  raise "abstract"
 end
end
strategy.rb

This base class is what all our Strategy objects will inherit from.  We then create two concrete strategy objects TwelveDayMovingAverage and TwentyFourDayMovingAverage that inherit from the base Strategy class and override the interface's "exec" method with their own algorithm to return a result.

class TwelveDayMovingAverage < Strategy
 attr_accessor :data
 def initialize(data)
   @data = data
 end

 def exec
   # data analysis and return 'up' or 'down'
 end
end
twelve_day.rb
class TwentyFourDayMovingAverage < Strategy
 attr_accessor :data
 def initialize(data)
   @data = data
 end

 def exec
   # data analysis and return 'up' or 'down'
 end
end
twenty_four_day.rb

Finally we have the context under which the strategies will be called:

class TradingBot
  attr_reader :trade_strategy
  def initialize(trade_strategy)
    @trade_strategy = trade_strategy
  end
  
  def should_trade?(data)
   case strategy
    when :twelve_day
     TwelveDayMovingAverage.new(data).exec
    when :twenty_four_day
     TwentyFourDayMovingAverage.new(data).exec
   else
     raise "invalid strategy"
   end
  end
end
trading_bot.rb

In the listing above a simple TradingBot class is instantiated with a trading strategy value. This initialization value is used to select the appropriate algorithm to use at run time when the trend? method is called by a client class. A full use case may look as follows:

# !/bin/ruby
# Program to demonstrate market indicators for Apple, Inc stock

# Instantiate two trading bots
bot12day = TradingBot.new(:twelve_day)
bot24day = TradingBot.new(:twenty_four_day)

# Read JSON from file
data = File.read('testdata/APPL.json')

# Ask the bots what the trend is, they will determine the correct
# algorithm to use 
12_day_trend = bot12day.trend?(data)
24_day_trend = bot24day.trend?(data)

# Tell the people
puts <<-MSG
Based on two analyses the twelve day moving average suggests APPL is going #{12_day_trend} while the twenty four day moving average suggests it is going #{24_day_trend}
MSG
main.rb

By abstracting the strategy here we gain the flexibility to reuse the same TradingBot class and can also implement the algorithms elsewhere.


Have fun!