-
Notifications
You must be signed in to change notification settings - Fork 178
Sample Strategy
This is no longer applicable. Visit the strategy page for more information.
This is going to be an extremely simple example that leverages Bollinger Bands and two moving averages.
Let's starting thinking about our strategy. Since we are using both moving averages and Bollinger Bands, we'll need to define their inputs.
Our Bollinger Bands will have the following inputs.
- Moving Average Periods (the number, so for SMA(5) for instance, the periods would be 5)
- Moving Average Parameter (high, low, open, close, etc)
- Moving Average Type (SMA, WMA, EMA)
- BB Coefficient (self-explanatory)
- Volatility Type (Yang Zhang, Rogers Satchell, etc)
- Volatility Look Back Periods (number of periods to look back)
Next, since we are using moving averages as well, we'll need:
- Moving Average Type
- Moving Average Parameter
- Moving Average 1st Bound
- Moving Average 2nd Bound
Let's for the sake of this example, assume our strategy is as follows:
If the opening price is less than the lower band, then our trend is BULLISH. If the opening price is higher than the upper band, then our is trend is BEARISH.
Next, we are leveraging moving averages too, so for that, if the 1st moving average is higher than the second one, we are bullish, and vice-versa.
Makes sense? Sweet, let's start.
The full source code can be found below.
So, we have 10 inputs in total, so our inputs list must be 10 in size. An easy trick we can use is (None,) * 10
class SampleStrategy(Strategy):
def __init__(self, parent=None, inputs: list = (None,) * 10, precision: int = 2):
super().__init__(name='Sample Example', parent=parent, precision=precision)
self.description = "This is a sample strategy with Bollinger Bands."
self.moving_average_periods = inputs[0]
self.moving_average_parameter = inputs[1]
self.moving_average_type = inputs[2]
self.bb_coefficient = inputs[3]
self.volatility_type = inputs[4]
self.volatility_look_back_periods = inputs[5]
self.ma_moving_average_type = inputs[6]
self.ma_moving_average_parameter = inputs[7]
self.ma_moving_average_1 = inputs[8]
self.ma_moving_average_2 = inputs[9]
We've defined the init method here, and we now successfully consumed the inputs. Next, let's give it access to things we want to plot in the graph. For example, we want to plot the moving averages and the Bollinger bands. To do so, let's augment the init method with this:
def __init__:
....
ma1 = f'{self.ma_moving_average_type}({self.ma_moving_average_1}) - {self.ma_moving_average_parameter}'
ma2 = f'{self.ma_moving_average_type}({self.ma_moving_average_2}) - {self.ma_moving_average_parameter}'
self.plotDict[ma1] = [0, '00ff00']
self.plotDict[ma2] = [0, 'FF0000']
self.plotDict['Upper Band'] = [0, get_random_color()]
self.plotDict['Middle Band'] = [0, get_random_color()]
self.plotDict['Lower Band'] = [0, get_random_color()]
Basically, we have added keys to our plot dictionary. The key is the legend, and the value is a list. In that list, the first item is the value followed by the color.
In this example, we defined random colors and one green and red color, but if you'd like to have a specific color, you can provide the hex value. For instance, if you wanted green, you would input '00ff00' (as we did for the first moving average).
Excellent, we have added support for plotting. Now, let's augment our statistics window. To do so, let's add the following also in the init method.
def __init__():
...
self.strategyDict['general'] = {
'Moving Average Periods BB': self.moving_average_periods,
'Moving Average Type BB': self.moving_average_type,
'BB Coefficient': self.bb_coefficient,
'Volatility Type': self.volatility_type,
'Volatility Look Back Periods': self.volatility_look_back_periods,
'Moving Average Type MA': self.ma_moving_average_type,
'Moving Average Parameter MA': self.ma_moving_average_parameter,
}
This will show our inputs in the statistics window. Note that these are all 'static' values, meaning that they are not bound to change. This is intentional, as these are just for verification and aesthetic purposes. We'll set the actual dynamic values later in this tutorial.
Great, now we should be done with the init function. All in all, it should like something like:
class SampleStrategy(Strategy):
def __init__(self, parent=None, inputs: list = (None,) * 10, precision: int = 2):
super().__init__(name='Sample Example', parent=parent, precision=precision)
self.description = "This is a sample strategy with Bollinger Bands."
self.moving_average_periods = inputs[0]
self.moving_average_parameter = inputs[1]
self.moving_average_type = inputs[2]
self.bb_coefficient = inputs[3]
self.volatility_type = inputs[4]
self.volatility_look_back_periods = inputs[5]
self.ma_moving_average_type = inputs[6]
self.ma_moving_average_parameter = inputs[7]
self.ma_moving_average_1 = inputs[8]
self.ma_moving_average_2 = inputs[9]
ma1 = f'{self.ma_moving_average_type}({self.ma_moving_average_1}) - {self.ma_moving_average_parameter}'
ma2 = f'{self.ma_moving_average_type}({self.ma_moving_average_2}) - {self.ma_moving_average_parameter}'
self.plotDict[ma1] = [0, '00ff00']
self.plotDict[ma2] = [0, 'FF0000']
self.plotDict['Upper Band'] = [0, get_random_color()]
self.plotDict['Middle Band'] = [0, get_random_color()]
self.plotDict['Lower Band'] = [0, get_random_color()]
self.strategyDict['general'] = {
'Moving Average Periods BB': self.moving_average_periods,
'Moving Average Type BB': self.moving_average_type,
'Moving Average parameter': self.moving_average_parameter,
'BB Coefficient': self.bb_coefficient,
'Volatility Type': self.volatility_type,
'Volatility Look Back Periods': self.volatility_look_back_periods,
'Moving Average Type MA': self.ma_moving_average_type,
'Moving Average Parameter MA': self.ma_moving_average_parameter,
}
Now, let's move forward to the next required functions.
We will need to override the set_inputs() function. It's basically the same as the one from the init. Take a look:
def set_inputs(self, inputs: list):
self.moving_average_periods = inputs[0]
self.moving_average_parameter = inputs[1]
self.moving_average_type = inputs[2]
self.bb_coefficient = inputs[3]
self.volatility_type = inputs[4]
self.volatility_look_back_periods = inputs[5]
self.ma_moving_average_type = inputs[6]
self.ma_moving_average_parameter = inputs[7]
self.ma_moving_average_1 = inputs[8]
self.ma_moving_average_2 = inputs[9]
Optimizers use this to reset values, so it's imperative that we define this.
Next, we need to override the get_params() functions. All this is doing is returning our parameters in a list.
def get_params(self):
return [
self.moving_average_periods,
self.moving_average_parameter,
self.moving_average_type,
self.bb_coefficient,
self.volatility_type,
self.volatility_look_back_periods,
self.ma_moving_average_type,
self.ma_moving_average_parameter,
self.ma_moving_average_1,
self.ma_moving_average_2
]
Excellent, now we need to let the GUI know what type of parameters they are. Here's an example:
@staticmethod
def get_param_types():
moving_averages = ['SMA', 'EMA', 'WMA']
moving_average_parameters = ['High', 'Low', 'Open', 'Close', 'High/Low', 'Open/Close']
volatility_types = ['Yang Zhang (ZH)', 'Rogers Satchell (RS)', 'Garman-Klass (GK)', 'Parkinson', 'Basic']
return [
('Volatility Moving Average Periods', int),
('Volatility Moving Average Parameter', tuple, moving_average_parameters),
('Volatility Moving Average Type', tuple, moving_averages),
('BB Coefficient', float),
('Volatility Type', tuple, volatility_types),
('Volatility Look Back Periods', int),
('Moving Average', tuple, moving_averages),
('Moving Average Parameter', tuple, moving_average_parameters),
('Moving Average 1', int),
('Moving Average 2', int),
]
By the way, note that these are defined in the order of our inputs! Very important to make sure you define them in the same order.
Next, let's override the get_min_option_period() function. This function is basically ensuring the amount of minimum periods you need for the strategy to work. With this strategy for instance, the minimum periods you need is the maximum of moving average 1, moving average 2, and BB moving average, and the volatility look back periods. The highest number is the minimum periods required, right? Let's implement it:
def get_min_option_period(self) -> int:
return max([
self.moving_average_periods,
self.ma_moving_average_1,
self.ma_moving_average_2,
self.volatility_look_back_periods
])
Great. Simple enough? Now, let's implement the final, but the most important function, get_trend().
def get_trend(self, data: Union[List[dict], Data] = None, log_data: bool = False):
data_obj = data
if type(data) == Data:
data = data.data + [data.current_values]
# Gathering the actual indicator values here.
ma1 = get_moving_average(moving_average=self.ma_moving_average_type,
moving_average_parameter=self.ma_moving_average_parameter,
moving_average_periods=self.ma_moving_average_1,
data=data)
ma2 = get_moving_average(moving_average=self.ma_moving_average_type,
moving_average_parameter=self.ma_moving_average_parameter,
moving_average_periods=self.ma_moving_average_2,
data=data)
lower_band, middle_band, upper_band = get_bollinger_bands(
bb_coefficient=self.bb_coefficient,
moving_average=self.moving_average_type,
moving_average_parameter=self.moving_average_parameter,
moving_average_periods=self.moving_average_periods,
volatility=self.volatility_type,
volatility_look_back_periods=self.volatility_look_back_periods,
data=data
)
prefix, interval = self.get_prefix_and_interval_type(data_obj)
# Note that we use the interval key, because we also have support for lower intervals.
# Now, let's these values in the statistics window.
self.strategyDict[interval][f'{prefix}Lower Band'] = lower_band
self.strategyDict[interval][f'{prefix}Middle Band'] = middle_band
self.strategyDict[interval][f'{prefix}Upper Band'] = upper_band
ma1_string = f'{self.ma_moving_average_type}({self.ma_moving_average_1}) - {self.ma_moving_average_parameter}'
ma2_string = f'{self.ma_moving_average_type}({self.ma_moving_average_2}) - {self.ma_moving_average_parameter}'
self.strategyDict[interval][ma1_string] = ma1
self.strategyDict[interval][ma2_string] = ma2
if interval == 'regular': # Only plot for regular interval values.
# Note that the value of this dictionary is a list. The first contains the value and the second contains
# the color. We only want to change the value, so modify the first value (which is at the 0th index).
self.plotDict['Lower Band'][0] = lower_band
self.plotDict['Middle Band'][0] = middle_band
self.plotDict['Upper Band'][0] = upper_band
self.plotDict[ma1_string][0] = ma1
self.plotDict[ma2_string][0] = ma2
# Now, finally, the trend itself. The final value in our data is the latest one.
open_price = data[-1]['open'] # Get the latest open price.
if open_price < lower_band and ma1 > ma2:
self.trend = BULLISH
elif open_price > upper_band and ma1 < ma2:
self.trend = BEARISH
else:
self.trend = None
return self.trend
You can view the full strategy code here with commented documentation and typing hints.
If you want to hide this strategy, just rename it to "Sample" as opposed to "Sample Example". Algobot will ignore all strategies named "Sample" by default. It's hidden by default, so if you want to experiment with this strategy, rename it something other than "Sample".
Algobot Wiki