# TQuant Lab Price Deviation Ratio Trading Strategy

## Preface

The Price Deviation Ratio is a common technical indicator that compares the current stock price to the N-day moving average price, reflecting whether the current price is relatively high or low compared to its historical values. Generally, when the stock price consistently exceeds the moving average price, it’s called a ‘positive deviation.’ Conversely, it’s called’ negative deviation’ when it consistently falls below the moving average price.’ Therefore, when positive or negative deviation expands, it is interpreted as a sustained overbought or oversold condition in the market, serving as a basis for entry and exit decisions. However, using only the Price Deviation Ratio can generate too many trading signals. Hence, we include the highest and lowest prices over the past N days as a second filter. The actual strategy is as follows:

When the closing price is higher than the highest price over the past N days, and the Price Deviation Ratio is negative, enter a long position at the next day’s opening price.

When the closing price is lower than the lowest price over the past N days, and the Price Deviation Ratio is positive, exit the position and close the trade at the next day’s opening price.

## The Editing Environment and Module Required

``````import os
import pandas as pd
import numpy as np
import tejapi
import matplotlib.pyplot as plt``````

## Data Import

The back testing time period is between 2005/07/02 to 2023/07/02, and we take TSMC(2330) as an example.

``````os.environ['TEJAPI_BASE'] = 'https://api.tej.com.tw'
os.environ['TEJAPI_KEY'] = 'your_key'
os.environ['mdate'] = '20050702 20230702'
os.environ['ticker'] = '2330'
!zipline ingest -b tquant``````

## Module Import

``from zipline.api import (set_slippage,set_commission,set_benchmark,attach_pipeline,symbol,pipeline_output,record,order,order_target)from zipline.pipeline.filters import StaticSidsfrom zipline.finance import slippage, commissionfrom zipline import run_algorithmfrom zipline.pipeline import CustomFactor, Pipelinefrom zipline.pipeline.data import EquityPricingfrom zipline.pipeline.factors import ExponentialWeightedMovingAverage``

## Create Pipeline function

Pipeline() enables users to quickly process multiple assets’ trading-related data. In today’s article, we use it to process:

• EMA of price of the past 7 days
• The highest price of the past 7 days(custom factor: `NdaysMaxHigh`)
• The lowest price of the past7 days(custom factor: `NdaysMinLow`)
• Current close price
``````def make_pipeline():
ema = ExponentialWeightedMovingAverage(inputs = [EquityPricing.close],window_length = 7,decay_rate = 1/7)
high = NdaysMaxHigh(inputs = [EquityPricing.close], window_length = 8) # window_length 設定為 8，因為 factor 會包含當日價格。
low = NdaysMinLow(inputs = [EquityPricing.close], window_length = 8)
close = EquityPricing.close.latest
return Pipeline(
columns = {
'ema':ema,
'highesthigh':high,
'lowestlow':low,
'latest':close
}
)
class NdaysMaxHigh(CustomFactor):
def compute(self, today, assets, out, data):
out[:] = np.nanmax(data[:-2], axis=0)
class NdaysMinLow(CustomFactor):
def compute(self, today, assets, out, data):
out[:] = np.nanmin(data[:-2], axis=0)``````

## Creating Initialize Function

`Initialize()` enables users to set up the trading environment at the beginning of the back test period. In this article, we set up :

• Slippage
• Commission
• Set the return of buying and holding TSMC as the benchmark.
• Attach `Pipline()` function into back testing.
``def initialize(context):set_slippage(slippage.VolumeShareSlippage())set_commission(commission.PerShare(cost=0.00285))set_benchmark(symbol('2330'))attach_pipeline(make_pipeline(), 'mystrategy')``

## Create Handle_data Function

`handle_data()` is used to process data and make orders daily.

• Condition1: When current close price is greater than the highest price of last 7 days and the bias is greater than 0, we regard it as a selling signal.
• Condition2: When current close price is lower than the lowest price of last 7 days and the bias is lower than 0, we regard it as a buying signal.
``````def handle_data(context, data):

pipe = pipeline_output('mystrategy')

for i in pipe.index:
ema = pipe.loc[i, 'ema']
highesthigh = pipe.loc[i, 'highesthigh']
lowestlow = pipe.loc[i, 'lowestlow']
close = pipe.loc[i, 'latest']
bias = close - ema
residual_position = context.portfolio.positions[i].amount # 當日該資產的股數
condition1 = (close > highesthigh) and (bias > 0) and (residual_position > 0) # 賣出訊號
condition2 = (close < lowestlow) and (bias < 0) # 買入訊號

record( # 用以紀錄以下資訊至最終產出的 result 表格中
con1 = condition1,
con2 = condition2,
price = close,
ema = ema,
bias = bias,
highesthigh = highesthigh,
lowestlow = lowestlow
)

if condition1:
order_target(i, 0)
elif condition2:
order(i, 10)
else:
pass``````

## Creating Analyze Function

Here, we apply `matplotlib.pyplot` for the trading signals and the portfolio value visualization.

``````def analyze(context, perf):
fig = plt.figure()
perf.portfolio_value.plot(ax=ax1)
ax1.set_ylabel("Portfolio value (NTD)")
ax2.set_ylabel("Price (NTD)")
perf.price.plot(ax=ax2)
ax2.plot( # 繪製買入訊號
perf.index[perf.con2],
perf.loc[perf.con2, 'price'],
'^',
markersize=5,
color='red'
)
ax2.plot( # 繪製賣出訊號
perf.index[perf.con1],
perf.loc[perf.con1, 'price'],
'v',
markersize=5,
color='green'
)
plt.legend(loc=0)
plt.gcf().set_size_inches(18,8)
plt.show()``````

## Run Algorithms

We expliot `run_algorithm()` to execute our strategy. The backtesting time period is set between 2015–01–06 to 2022–11–25. The data bundle we use is tquant. We assume the initial capital base is 10,000. The output of `run_algorithm()`, which is results, contains information on daily performance and trading receipts.

``````results = run_algorithm(start = pd.Timestamp('20150106', tz='UTC'),
end = pd.Timestamp('20221125', tz='UTC'),
initialize=initialize,
bundle='tquant',
analyze=analyze,
capital_base=1e4,
handle_data = handle_data
)``````

## Performance Analysis

Then, we used `Pyfolio `module which came with TQuant Lab to analyze strategy`s performance and risk. First, we use `extract_rets_pos_txn_from_zipline()` to calculate returns, positions, and trading records.

``````import pyfolio as pf
returns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)``````

## Daily Returns

Calculating daily portfolio return.

## Holding Positions

• Equity(0 ): TSMC
• Cash

## Transaction Record

• sid: index
• symbol: ticker symbol
• order_id: order number
• commission: commission cost

## Making Performance Table

With `show_perf_stats()`, one can easily showcase the performance and risk analysis table.

``````import pyfolio as pf
pf.plotting.show_perf_stats(
returns,
benchmark_rets,
positions=positions,
transactions=transactions)``````

## Plot Accumulated Return and Benchmark Return

``````benchmark_rets = results['benchmark_return']
pf.plotting.plot_rolling_returns(returns, factor_returns=benchmark_rets)``````

## Conclusion

The strategy introduced in this session is one of the mean-reversion trading strategies. When the market is oversold (Bullish Divergence, BDI < 0), and the closing price is higher than the highest price over a certain period, it’s assumed that the stock price will gradually return to the moving average price, so a long position is entered. Conversely, when the stock price is overbought (Bearish Divergence, BDI > 0), and the closing price is lower than the lowest price over a certain period, it’s believed that the stock price has risen too much and has a downward trend. In this case, the long position is exited. However, it’s important to note that this strategy involves frequent trading, which transaction costs and taxes can erode. Therefore, it’s recommended to combine other technical indicators to optimize entry and exit points.

Finally, it’s worth mentioning again that the stocks discussed in this article are for illustrative purposes only and do not constitute recommendations for any financial products. If readers are interested in topics such as building strategies, performance backtesting, and empirical research, you are welcome to purchase solutions from TEJ E-Shop, which provides comprehensive databases to easily perform various tests and analyses.