Élaboration de stratégies algorithmiques sur le commerce intelligent

SmartTradeisanalgorithmictradingplatformwhichallowsprogrammerstoeasilyimplementandback-testtradingstrategiesusingPython.AsSmartTradeprovidesaccesstomultipleessentiallibrariessuchasstatsmodelandta-lib,youcanusetechnicalanalysisorstatisticalmethodsforyourstrategies.Belowwewillillustrateanexampleusing2commontechnicalindicators:MACD(MovingAverageConvergenceDivergence) and Stochastics.

First, we login to SmartTrade’s coding interface, and we will be presented with 2 panels: 1) Python coding interface and 2) Backtesting results

smart1.png

We will begin the development process. There are 4 key steps in building a strategy on SmartTrade

  1. Import & Initialize Method Import the libraries desired (such as ta-lib) at the top.
import pandas as pd
import talib as ta
import numpy as np

It is recommended to import pandas library at a minimum in order to operate on the timeseries variables which come as DataFrame objects on SmartTrade. Next specify the list of stocks you would like to backtest in the initialize method. Currently, SmartTrade supports backtesting up to 50 stocks at a time.

def initialize(ctx):
    #Réglage
    ctx.logger.debug("initialize() called")
    ctx.configure(
      channels={          #Canal utilisé
        "jp.stock": {
          "symbols": [
            "jp.stock.9984",
            "jp.stock.9983",
            "jp.stock.7201",
            "jp.stock.8377",
            "jp.stock.5715",
            "jp.stock.7014",
            "jp.stock.9810",
            "jp.stock.6861",
            "jp.stock.2782",
          ],
          "columns": [
            "open_price_adj",    #Prix ouvert(Après ajustement pour le fractionnement d'actions)
            "high_price_adj",    #Prix élevé(Après ajustement pour le fractionnement d'actions)
            "low_price_adj",     #Bas prix(Après ajustement pour le fractionnement d'actions)
            "volume_adj",         #Le volume
            "txn_volume",         #Prix de négociation
            "close_price",        #le dernier prix
            "close_price_adj",    #le dernier prix(Après ajustement pour le fractionnement d'actions) 
          ]
        }
     }
    )
  1. Preparing the Required Data Given the selected stocks, time period, the timeseries will be provided in the signal method via the data object. Specifically, you can access the close, open, high, low and volume timeseries for each stock by directly accessing the data object as a dictionary like the follow:
open2 = data["open_price_adj"].fillna(method='pad')
high = data["high_price_adj"].fillna(method='pad')
low = data["low_price_adj"].fillna(method='pad')
close = data["close_price_adj"].fillna(method='pad')

Next, we will apply ta-lib to the above timeseries. Before we do that, we define additional objects to build the signals based on the results from ta-lib. We define the below objects with same DataFrame structures as data:

s1 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
s2 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
sto = pd.DataFrame(data=0, columns=close.columns, index=close.index)
sto2 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
s3 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
result = pd.DataFrame(data=0, columns=close.columns, index=close.index)

After that, we will begin applying ta-lib to the timeseries. To do that, we need to loop through the close objects by symbol as follow. We will use MACD and Stochastics functions as described above:

for (sym, val) in close.items():
    macd_talib, signal, s1[sym] = ta.MACD(val.values.astype(np.double),
                                          fastperiod=12,
                                          slowperiod=26,
                                          signalperiod=9)
    stok, stod = ta.STOCH(high[sym].values.astype(np.double),
                          low[sym].values.astype(np.double),
                          val.values.astype(np.double),
                          slowk_period=14,
                          slowd_period=3)
    s2[sym] = stok - stod
    sto[sym] = stok
    sto2[sym] = stod   	

It is important to note that he close, open, high and low timeseries must be converted to array as double first before they can be used in ta-lib.

  1. Constructing the Signal Logic Next we will go through the logic of the strategy. The “MACD & Stochastic Momentum” signal is a strategy that buys and sells based on change in momentum. In other words, it buys when momentum turns positive and sells when it turns negative. The MACD and Stochastic indicators allow us to detect changes in momentum.

MACD The MACD keeps track of 2 exponential moving averages (usually 12 and 26 period), and detects when the shorter moving average “crosses-over” the longer one. When a cross-over occurs, the MACD histogram crosses the zero line. The idea is that we locate the time when the histogram crosses from negative to positive, which indicates positive momentum, and vice-versa on the negative side. Below is an example illustrating buy signal using the MACD:

smartmacd.png

We use the below code to keep track on when the cross-over occurs:

    test = np.sign(s1[sym].values[1:]) - np.sign(s1[sym].values[:-1])
    n = len(test)

    for i, row in enumerate(test):
        result[sym][i+1] = 0 if isNaN(test[i]) else test[i]

Stochastic There are 2 sub-indicators within Stochastic – the K line and D line. The trend is said to shift to positive when the K line “crosses-over” the D line. In other words, when K Line > D Line. Here is an example:

smartsto.png

Therefore, in our Python code, we keep track of stok – stod timeseries in the s2 object and detect when the s2 changes from negative to positive in order to find the time when K line crosses over the D line positively using the following code:

    test = np.sign(s2[sym].values[1:]) - np.sign(s2[sym].values[:-1])
    n = len(test)
    for i, row in enumerate(test):
        s3[sym][i+1] = 0 if isNaN(test[i]) else test[i]

In brief, when s3 equals +1, the crossover is positive, and -1 when the crossover is negative otherwise.

Finally once we constructed the above 2 signals, we provide SmartTrade with when to buy and signal using the below code:

    buy_sig = result[(result>=2) & (s2>0) & ((sto>=40) | (sto2>=40))]
    sell_sig = result[(result<=-2) | ((s3<0) & ((sto<=40) | (sto2<=40)))]
    return {
            "buy:sig": buy_sig,
            "sell:sig": sell_sig
           }
  1. Handle the Signals & Positions After creating the signal, we need to actually execute the trades. This is done in the last method called handle_signals. Inside handle_signals, there are 2 major functions:

This is simply done by looping through the buy:sig and sell:sig objects returned from the above.

    buy = current["buy:sig"].dropna()
    for (sym, val) in buy.items():
        if sym in done_syms:
            continue

        sec = ctx.getSecurity(sym)
        sec.order(sec.unit() * 1, comment="SIGNAL BUY")

    sell = current["sell:sig"].dropna()
    for (sym, val) in sell.items():
        if sym in done_syms:
            continue
    
        sec = ctx.getSecurity(sym)
        sec.order(sec.unit() * -1, comment="SIGNAL SELL")

This is a custom logic to cut losses or take profit. SmartTrade provides a position object for the program to check the current return and maximum return to date. Based on that, we create a “trailing-stop” logic as follow:

    for (sym, val) in ctx.portfolio.positions.items():
        returns = val["returns"]
        trailing = val["max_returns"] - returns
        if (trailing > 0.02 and returns < 0) or (returns > 0 and trailing > 0.04) or  (returns > 0.05 and trailing > 0.02) or (returns > 0.1 and trailing > 0.01):
            sec = ctx.getSecurity(sym)
            sec.order(-val["amount"], comment="Trailing Stop(%f)" % returns)
            done_syms.add(sym)

In brief, the logic cuts losses if greater than 2%, and takes profit depending on how much the trailing return is. The idea is to maximize profit and ride the up trend as long as the trailing stop is not hit.

And we are done with the coding! Next we execute the strategy and below are the backtesting results since 2007:

smart2.png

As shown above, the strategy is able to navigate the bear market with relatively mild losses while generating a positive return bigger than the Nikkei 225. When applied on more stocks, the result would be even more profound.

Since the strategy is public on SmartTrade, feel free to check out the full code and run the strategy yourself below:

https://beta.smarttrade.co.jp/demo/2fdd1b2f08e04d15a45f244bcd970ef1

Recommended Posts

Élaboration de stratégies algorithmiques sur le commerce intelligent
Construire des stratégies de trading sur SmartTrade avec des fonctions personnalisées
Construire des stratégies de trading sur SmartTrade avec des fonctions personnalisées
Élaboration de stratégies algorithmiques sur le commerce intelligent
Construire un environnement Python sur Mac
Construire un environnement Python sur Ubuntu
Enquête sur la construction et le fonctionnement de kivi