Strategy Pattern and Service Objects in Ruby
#Ruby #Software Development #Software-Design-Patterns
The following is an excerpt from my book Building and Deploying Crypto Trading Bots
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” object (or function in functional languages) that contains the algorithm implementation to execute. The benefit of doing this 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 reuse within the software.
To provide a more concrete example we use the following example with about trading:
# Abstract base class
class Strategy
def initialize
raise "abstract"
end
def exec
raise "abstract"
end
end
We define an abstract “Strategy” class which our strategies will inherit from. Our strategy objects in this example will encapsulate the algorithms necessary to calculate the “Simple Moving Average” for a particular data set. Calculating simple moving average is a technique used by technical trading analysts to determine the trend direction of a particular market (for example a stock). In our case we will choose arbitrary calculations for twelve and twenty-four day moving average, the implementation is not important here:
class TwelveDayMovingAverage < Strategy
attr_accessor :data
def initialize(data)
@data = data
end
def exec
# data analysis and return true or false result
end
end
class TwentyFourDayMovingAverage < Strategy
attr_accessor :data
def initialize(data)
@data = data
end
def exec
# data analysis and return 'up' or 'down'
end
end
We 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. 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 trend?(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
In the listing above a simple TradingBot class is instantiated with a trading strategy value that it uses 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:
Example
Lets build a program to demonstrate market indicators for Apple, Inc stock
bot12day = TradingBot.new(:twelve_day)
bot24day = TradingBot.new(:twenty_four_day)
data = File.read('testdata/APPL.json')
12_day_trend = bot12day.trend?(data)
24_day_trend = bot24day.trend?(data)
puts "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}"
By abstracting the strategy here we gain the flexibility to reuse the same TradingBot
class and can also implement the algorithms elsewhere.