Please note this blog was originally published on March 21, 2024.

Have you ever wondered how some people create such amazing strategies and get the best results? Well, a major part of this success lies in the knowledge of creating strategies. Above that, if creating these strategies turns out to be a piece of cake, you will always have the energy needed to enhance the strategies to win. 

Phoenix is your true companion to allow you this comfort. Creating strategies from scratch has never been easier but with Phoenix, you can create your strategies within a few minutes so that you have ample time to enhance the codes and configurations.

This blog post is part of a series that is created to enhance your algorithmic trading skills. If this is your first post from the series, we suggest you first read “Exploring Trading Strategies with AlgoBulls’ Phoenix” so you get an overview of how Phoenix helps you in your journey to success. Once you are aware of the structure, let’s dive into the heart of algorithmic trading, which is creating your strategy.

Phoenix uses its own structural format for building a trading strategy in Python. This format is easy to understand and requires minimal setup. The strategy code that is used for Phoenix Web and pyalgotrading is one and the same. In this blog post, we are going to understand the structure of these strategy codes and their working.

Main Content

Understanding the Strategy Structure

The strategy code is basically a Python Class, with its Base Class as StrategyBase (for regular strategies) or StrategyBaseOptionsV2 (for options strategies). In this article, we will focus on a regular strategy called ‘EMA Regular Order’ - a strategy which gives BUY and SELL signals based on two EMA indicators and takes new-entry & previous-exit at every crossover. Inside the strategy class, there are many methods that could be divided into 2 different sections: Mandatory Methods: Initialization Methods, 4-Core Loop Methods & Optional Methods: Algorithmic Calculation Methods & Miscellaneous Methods. These sections are explained briefly below.

Mandatory Methods:

Initialization Methods

In this section, you will have the Strategy Class’ “__init__” method (a.k.a. Constructor). This method will extract the Strategy’s Configured Parameters and save them in the different variables. There is another method in this section called “initialization”, which will be called at the start of every trading day that will occur inside the timeline for which you are executing the strategy.

4 Core Loop Methods

These are the main methods that will be called by the AlgoBulls Core in a sequence for every candle (candle is the minimum time range for which the Open, High, Low, and Close values of that instrument are available. Basically, it is a representation of a time period, and the data corresponds to the trades executed during that period). All the logic design methods are called inside these Core Methods, along with the helping methods.

Optional Methods:

Algorithmic Calculations Methods

This section contains the methods that are defined by the user. These are not mandatory but good to keep your code organized and simple. Their main purpose will be to perform operations on historical data or LTP data of the selected instrument.  Based on the results of these operations, it needs to decide whether it should Buy, Sell or take no action on the instrument. 

Apart from decision making, some of the other methods can also be useful to calculate the stop loss or target price for a trade.

Point being, the purpose of these methods are totally dependent on the application of the user.

Miscellaneous Methods

These are handy methods that are created by a team of expert strategy developers and they are already a part of the base class. These methods do a lot of heavy lifting under the hood and can be used here for various purposes like getting the latest price of a stock, placing an order etc. These methods are not always necessary to be used and are generally a part of other methods mentioned above.

Given below is a sample diagram on how the strategy execution engine works. Blocks in green are the mandatory methods that need to be implemented for every strategy.

Strategy Code Creation: Let's Get Started

Now, it's time to dive into strategy creation. You can view the complete code for the given strategy here. We will explain it step-by-step here.

First, we create your strategy class. Be sure to choose the appropriate base class for your strategy. For regular equity strategies, the base class is "StrategyBase".

class EMARegularOrder(StrategyBase):
    name = 'EMA Regular Order'

Feel free to get creative with your class name, but it's a good practice to make it unique among your strategy class names. Below this declaration, we set the strategy's name in the “name” variable

Once you have declared the class, we will move on to the structure of the methods that are used in the class. These methods being part of the strategy class, will be an indentation level inside the strategy class

__init__(): The Constructor Method 

Every great trading strategy starts with a solid foundation – the constructor method, also known as the "__init__" method. Here's what it looks like:

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)

Within this method, you extract the parameters provided by the user. These user-defined parameters are stored in a dictionary called "self.strategy_parameters". Here's how you can access these values:

self.timeperiod1 = self.strategy_parameters['TIMEPERIOD1']
self.timeperiod2 = self.strategy_parameters['TIMEPERIOD2']

Feel free to initialize other variables here, such as self.main_order_map: a dictionary object that will store the orders mapped with keys as an instrument. Orders are basically trades of buy and sell that have actually been executed.

Code Sample:

class EMARegularOrder(StrategyBase):
    name = 'EMA Regular Order'
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.timeperiod1 = self.strategy_parameters['TIMEPERIOD1']
        self.timeperiod2 = self.strategy_parameters['TIMEPERIOD2']
        self.main_order_map = None
initialize(): Initializing Your Strategy

The "initialize" method is your strategy's workspace, where you get everything ready for a fresh trading day. It's called at the start of each trading day, allowing you to reset variables or perform any other tasks to ensure your strategy is primed and ready.

Code Sample:

For Regular Strategy:

def initialize(self):
    self.main_order_map = {}
get_decision(): For Making Decisions

Now, let's dive into the heart of your strategy – the "get_decision" method. You may also see this method sometimes referred to as get_crossover_value(). This method is where the magic happens, where you decide when to enter or exit a trade, set stop losses, and target prices. Here's how it works:

Historical data is a time series data of open, high, low and close values of an instrument. These values are fetched at an interval same as that of the candle interval. The method given below fetches multiple historical data values till the current candle on which the strategy is executing. Historical data of the instrument up to a certain point in your strategy is collected using:

hist_data = self.get_historical_data(instrument)

Here the variable hist_data is a pandas.Dataframe object. A Pandas DataFrame is a 2 dimensional data structure, like a 2 dimensional array, or a table with rows and columns. The hist_data Dataframe will have 4 columns named as: “open”, “high”, “close“ and “low”. For every row there will be a unique timestamp, and the difference between each timestamp is of candle interval.

To access all the values of one particular column from the hist_data, you can use the following syntax code. Remember, the data type of the column fetched here is pandas.Serieshist_data[‘<column_name>’]. Eg: close = hist_data[‘close’]

To access the O-H-L-C values of the current latest candle you can use the following code. This also tells us that the index -1 is for latest, -2 will be for second latest and so on. This also implies that index 0 will get the oldest data, index 1 will give the second oldest data and so on: latest_ohlc = hist_data.iloc[-1]

Next, you analyse this historical data to determine trading signals and calculate indicator values. You can rely on functions from "talib" for this, as shown below:

ema_x = talib.EMA(hist_data['close'], timeperiod=self.timeperiod1)
ema_y = talib.EMA(hist_data['close'], timeperiod=self.timeperiod2)

As you can see, we have passed the “close” column and a strategy parameter value called “self.timeperiod1” and “self.timeperiod2” to the talib function. Each of the talib functions require unique input values, some require pandas.Series, some require constants like integers and floats and some require both. To understand the working of each talib function, refer here.

In many strategies, the condition to buy or sell can be triggered based on the crossover direction of 2 signals/indicators. A crossover refers to an instance where an indicator and a price, or multiple indicators, overlap and cross one another. Crossovers are used in technical analysis to confirm patterns and trends such as reversals and breakouts, generating buy or sell signals accordingly. Below we have given an example of how to calculate the crossover of the 2 indicator values we had calculated above.

crossover_value = self.utils.crossover(ema_x, ema_y)

Here if crossover_value is 0 then the indicators ema_x and ema_y have not crossed. If it is 1 then indicator ema_x has crossed ema_y in upward direction. Similarly if it is -1 then indicator ema_x has crossed ema_y in downward direction

By combining these calculated values with historical data, you can make informed decisions about when to initiate or close a trade. Additionally, you can use this information to compute potential target prices or set stop losses.

AlgoBulls' Phoenix empowers you to navigate the complexities of options trading with ease. 

Code Sample:

def get_decision(self, instrument):
        hist_data = self.get_historical_data(instrument)
        ema_x = talib.EMA(hist_data['close'], timeperiod=self.timeperiod1)
        ema_y = talib.EMA(hist_data['close'], timeperiod=self.timeperiod2)
        crossover_value = self.utils.crossover(ema_x, ema_y)
        return crossover_value

Now, let's talk about the engine that drives your strategy – the core loop methods. 

These include "strategy_select_instruments_for_entry", "strategy_enter_position", "strategy_select_instruments_for_exit" and "strategy_exit_position". These methods are called within a loop from the AlgoBulls core until an Exit Event for the strategy occurs.

strategy_select_instruments_for_entry(): Selecting Instruments for Entry

This method takes these parameters:

  • candle: The current candle where the strategy is executing its algorithm.
  • instrument_bucket: A list of all the instruments provided by the user when starting the strategy.

Here, you iterate through the instruments, calling "self.get_decision()" to determine actions for each instrument. Based on the decision, you create a dictionary, meta and a list of the corresponding selected instruments. As you would have realised, our code is capable of handling baskets of stocks very easily. The selected instruments and their meta-information are returned by the method.

Code Sample:

def strategy_select_instruments_for_entry(self, candle, instruments_bucket):
                selected_instruments, meta = [], []
        for instrument in instruments_bucket:
            crossover = self.get_decision(instrument)
            action_constants = {1: 'BUY', -1: 'SELL'}
            if crossover in [-1, 1]:
                selected_instruments.append(instrument)
                meta.append({'action': action_constants[crossover]})
        return selected_instruments, meta
strategy_enter_position(): Entering Positions

This method takes these parameters:

  • candle: The current candle where the strategy is executing its algorithm.
  • instrument: One of the instruments from the selected instruments list returned by "strategy_select_instruments_for_entry."
  • meta: Meta-information of that particular instrument, aligning with the instrument from the meta list. This is the same object as returned by strategy_select_instruments_for_entry.

Here, you place orders for the selected instruments, updating them in "self.main_order" for easy access in exit methods.

Code Sample:

def strategy_enter_position(self, candle, instrument, meta):
        self.main_order_map[instrument] = _ = self.broker.OrderRegular(instrument, meta['action'], quantity=self.number_of_lots * instrument.lot_size)
        return _
strategy_select_instruments_for_exit(): Selecting Instruments for Exit

Similar to entry, this method takes these parameters:

  • candle: The current candle where the strategy is executing its algorithm.
  • instrument_bucket: A list of all instruments which have an open long/short position currently.

The method iterates through the instrument bucket, checking if the instrument is present in "self.main_order_map" and whether its exit condition is satisfied. Instruments meeting exit conditions are added to the selected instruments list, along with their respective actions and meta-information.

Example:

    def strategy_select_instruments_for_exit(self, candle, instruments_bucket):
        selected_instruments, meta = [], []
        for instrument in instruments_bucket:
            if self.main_order_map.get(instrument) is not None:
                crossover = self.get_decision(instrument)
                if crossover in [1, -1]:
                    selected_instruments.append(instrument)
                    meta.append({'action': 'EXIT'})
        return selected_instruments, meta
strategy_exit_position(): Exiting the Positions

Finally, this method takes these parameters:

  • candle: The current candle where the strategy is executing its algorithm.
  • instrument: One of the instruments from the selected instruments list returned by "strategy_select_instruments_for_exit".
  • meta: Meta-information of that particular instrument, aligning with the instrument from the meta list. This is same as object as returned by strategy_select_instruments_for_exit

Here, you place orders for the selected instruments, removing them from "self.main_order" to prepare for the next iteration of the AlgoBulls core loop.

Code Sample:

def strategy_exit_position(self, candle, instrument, meta):
        if meta['action'] == 'EXIT':
            self.main_order_map[instrument].exit_position()
            self.main_order_map[instrument] = None
            return True
        return False

Conclusion

That’s it! Now your strategy is ready to take trades on crossovers generated by 2 EMAs. Note, this same code will work for Backtesting, Paper Trading and also Live Trading on Alpaca. Plus, the code already handles many features like basket of stocks, auto-square off for Intraday mode at EOD and more. You need not take care of any of it in the above code! These are covered in detail in the next blog in this series.

Feeling empowered? Note, the number of methods are not limited. You can define your own methods, just like the "get_decision" method, and structure them based on functionality. Whether it's calculating stop losses, crossovers, or any other aspect of your strategy – you're in control.

Phoenix by AlgoBulls is a versatile platform that simplifies the creation and execution of trading strategies. It offers a structured approach for strategy development and robust tools for analyzing and making trading decisions. 

Whether you're a novice or an experienced trader, Phoenix streamlines the process, ensuring quick and accurate strategy deployment. With options for both local coding and web-based strategy creation, it provides the flexibility needed to navigate the competitive world of algorithmic trading.

Congratulations! Armed with these building blocks, you're now equipped to code your Algorithmic Trading Strategy. Once your masterpiece is ready and polished, head on to configure and execute your strategy, which is also the next part of this blog series.

You can also explore our collection of tried and tested trading strategies on our GitHub library. These strategies have been carefully selected and have a track record of success. They serve as valuable references and can be integrated into your own strategy code to improve its performance. Give us a star on GitHub to keep us motivated to add more such strategies for the community. 

This article is your guide, your ally. May your algorithms be profitable and your strategies resilient. Happy coding!


Options trading is not suitable for all investors due to its inherent high risk, which can potentially result in significant losses. Please read Characteristics and Risks of Standardized Options before investing in options.

Alpaca Securities LLC and AlgoBulls are not affiliated and neither are responsible for the liabilities of the other. 

Please note that this article is for educational and informational purposes only. All screenshots are for illustrative purposes only. The views and opinions expressed are those of the author and do not reflect or represent the views and opinions of Alpaca. Alpaca does not recommend any specific securities or investment strategies.

Alpaca does not prepare, edit, or endorse Third Party Content. Alpaca does not guarantee the accuracy, timeliness, completeness or usefulness of Third Party Content, and is not responsible or liable for any content, advertising, products, or other materials on or available from third party sites.

Securities brokerage services are provided by Alpaca Securities LLC ("Alpaca Securities"), member FINRA/SIPC, a wholly-owned subsidiary of AlpacaDB, Inc. Technology and services are offered by AlpacaDB, Inc.

This is not an offer, solicitation of an offer, or advice to buy or sell securities or open a brokerage account in any jurisdiction where Alpaca Securities are not registered or licensed, as applicable.

All investments involve risk and the past performance of a security, or financial product does not guarantee future results or returns. There is no guarantee that any investment strategy will achieve its objectives. Please note that diversification does not assure a profit, or protect against loss. There is always the potential of losing money when you invest in securities, or other financial products. Investors should consider their investment objectives and risks carefully before investing.

Past hypothetical backtest results do not guarantee future returns, and actual results may vary from the analysis.

The Paper Trading API is offered by AlpacaDB, Inc. and does not require real money or permit a user to transact in real securities in the market. Providing use of the Paper Trading API is not an offer or solicitation to buy or sell securities, securities derivative or futures products of any kind, or any type of trading or investment advice, recommendation or strategy, given or in any manner endorsed by AlpacaDB, Inc. or any AlpacaDB, Inc. affiliate and the information made available through Paper Trading is not an offer or solicitation of any kind in any jurisdiction where AlpacaDB, Inc. or any AlpacaDB, Inc. affiliate is not authorized to do business.