{"id":1827,"date":"2023-09-19T14:00:00","date_gmt":"2023-09-19T14:00:00","guid":{"rendered":"https:\/\/www.tejwin.com\/en\/insights\/tquant-lab-price-deviation-ratio-trading-strategy\/"},"modified":"2023-09-19T14:00:00","modified_gmt":"2023-09-19T14:00:00","slug":"tquant-lab-price-deviation-ratio-trading-strategy","status":"publish","type":"insight","link":"https:\/\/www.tejwin.com\/en\/insights\/tquant-lab-price-deviation-ratio-trading-strategy\/","title":{"rendered":"TQuant Lab Price Deviation Ratio Trading\u00a0Strategy"},"content":{"rendered":"<figure class=\"wp-block-image aligncenter caption-align-center\"><img decoding=\"async\" alt=\"\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/1_0VPNogsRljnzIEH-A.jpg\"\/><figcaption class=\"wp-element-caption\">Photo by <a href=\"https:\/\/unsplash.com\/@lxrcbsv?utm_source=medium&amp;utm_medium=referral\" rel=\"noreferrer noopener\" target=\"_blank\">\u0410\u043b\u0435\u043a\u0441 \u0410\u0440\u0446\u0438\u0431\u0430\u0448\u0435\u0432<\/a> on\u00a0<a href=\"https:\/\/unsplash.com?utm_source=medium&amp;utm_medium=referral\" rel=\"noreferrer noopener\" target=\"_blank\">Unsplash<\/a><\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Highlight<\/h2>\n<ul class=\"wp-block-list\">\n<li>Difficulty: \u2605\u2606\u2606\u2606\u2606<\/li>\n<li>Determining when to long or short stocks with price deviation ratio.<\/li>\n<li>This article is revised from <a class=\"ek-link\" href=\"\/en\/insight\/price-deviation-ratio-trading-strategy\/\">Price Deviation Ratio Trading Strategy <\/a>via TQuant Lab.<\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\">Preface<\/h2>\n<p class=\"wp-block-paragraph\">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\u2019s called a \u2018positive deviation.\u2019 Conversely, it\u2019s called\u2019 negative deviation\u2019 when it consistently falls below the moving average price.\u2019 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:<\/p>\n<h2 class=\"wp-block-heading\">Trading Strategy<\/h2>\n<p class=\"wp-block-paragraph\">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\u2019s opening price.<\/p>\n<p>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\u2019s opening price.<\/p>\n<h2 class=\"wp-block-heading\">The Editing Environment and Module\u00a0Required<\/h2>\n<p class=\"wp-block-paragraph\">This article uses MacOS and employs Jupyter as the editor.<\/p>\n<pre class=\"wp-block-code\"><code><code>import os\nimport pandas as pd\nimport numpy as np\nimport tejapi\nimport matplotlib.pyplot as plt<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Data Import<\/h2>\n<p class=\"wp-block-paragraph\">The back testing time period is between 2005\/07\/02 to 2023\/07\/02, and we take TSMC(2330) as an example.<\/p>\n<pre class=\"wp-block-code\"><code><code>os.environ['TEJAPI_BASE'] = 'https:\/\/api.tej.com.tw'\nos.environ['TEJAPI_KEY'] = 'your_key'\nos.environ['mdate'] = '20050702 20230702'\nos.environ['ticker'] = '2330'\n!zipline ingest -b tquant<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Module Import<\/h2>\n<pre class=\"wp-block-code\"><code><code>from zipline.api import (set_slippage,<br\/>set_commission,<br\/>set_benchmark,<br\/>attach_pipeline,<br\/>symbol,<br\/>pipeline_output,<br\/>record,<br\/>order,<br\/>order_target<br\/>)<br\/>from zipline.pipeline.filters import StaticSids<br\/>from zipline.finance import slippage, commission<br\/>from zipline import run_algorithm<br\/>from zipline.pipeline import CustomFactor, Pipeline<br\/>from zipline.pipeline.data import EquityPricing<br\/>from zipline.pipeline.factors import ExponentialWeightedMovingAverage<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Create Pipeline\u00a0function<\/h2>\n<p class=\"wp-block-paragraph\">Pipeline() enables users to quickly process multiple assets\u2019 trading-related data. In today\u2019s article, we use it to process:<\/p>\n<ul class=\"wp-block-list\">\n<li>EMA of price of the past 7 days<\/li>\n<li>The highest price of the past 7 days(custom factor: <code>NdaysMaxHigh<\/code>)<\/li>\n<li>The lowest price of the past7 days(custom factor: <code>NdaysMinLow<\/code>)<\/li>\n<li>Current close price<\/li>\n<\/ul>\n<pre class=\"wp-block-code\"><code><code>def make_pipeline():\nema = ExponentialWeightedMovingAverage(inputs = [EquityPricing.close],window_length = 7,decay_rate = 1\/7)\nhigh = NdaysMaxHigh(inputs = [EquityPricing.close], window_length = 8) # window_length \u8a2d\u5b9a\u70ba 8\uff0c\u56e0\u70ba factor \u6703\u5305\u542b\u7576\u65e5\u50f9\u683c\u3002\nlow = NdaysMinLow(inputs = [EquityPricing.close], window_length = 8)\nclose = EquityPricing.close.latest\nreturn Pipeline(\ncolumns = {\n'ema':ema,\n'highesthigh':high,\n'lowestlow':low,\n'latest':close\n}\n)\nclass NdaysMaxHigh(CustomFactor):\ndef compute(self, today, assets, out, data):\nout[:] = np.nanmax(data[:-2], axis=0)\nclass NdaysMinLow(CustomFactor):\ndef compute(self, today, assets, out, data):\nout[:] = np.nanmin(data[:-2], axis=0)<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Creating Initialize Function<\/h2>\n<p class=\"wp-block-paragraph\"><code>Initialize()<\/code> enables users to set up the trading environment at the beginning of the back test period. In this article, we set up\u00a0:<\/p>\n<ul class=\"wp-block-list\">\n<li>Slippage<\/li>\n<li>Commission<\/li>\n<li>Set the return of buying and holding TSMC as the benchmark.<\/li>\n<li>Attach <code>Pipline()<\/code> function into back testing.<\/li>\n<\/ul>\n<pre class=\"wp-block-code\"><code><code>def initialize(context):<br\/>set_slippage(slippage.VolumeShareSlippage())<br\/>set_commission(commission.PerShare(cost=0.00285))<br\/>set_benchmark(symbol('2330'))<br\/>attach_pipeline(make_pipeline(), 'mystrategy')<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Create Handle_data Function<\/h2>\n<p class=\"wp-block-paragraph\"><code>handle_data()<\/code> is used to process data and make orders daily.<\/p>\n<ul class=\"wp-block-list\">\n<li>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.<\/li>\n<li>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.<\/li>\n<\/ul>\n<pre class=\"wp-block-code\"><code><code>def handle_data(context, data):\n\npipe = pipeline_output('mystrategy')\n\nfor i in pipe.index:\nema = pipe.loc[i, 'ema']\nhighesthigh = pipe.loc[i, 'highesthigh']\nlowestlow = pipe.loc[i, 'lowestlow']\nclose = pipe.loc[i, 'latest']\nbias = close - ema\nresidual_position = context.portfolio.positions[i].amount # \u7576\u65e5\u8a72\u8cc7\u7522\u7684\u80a1\u6578\ncondition1 = (close &gt; highesthigh) and (bias &gt; 0) and (residual_position &gt; 0) # \u8ce3\u51fa\u8a0a\u865f\ncondition2 = (close &lt; lowestlow) and (bias &lt; 0) # \u8cb7\u5165\u8a0a\u865f\n\nrecord( # \u7528\u4ee5\u7d00\u9304\u4ee5\u4e0b\u8cc7\u8a0a\u81f3\u6700\u7d42\u7522\u51fa\u7684 result \u8868\u683c\u4e2d\ncon1 = condition1,\ncon2 = condition2,\nprice = close,\nema = ema,\nbias = bias,\nhighesthigh = highesthigh,\nlowestlow = lowestlow\n)\n\nif condition1:\norder_target(i, 0)\nelif condition2:\norder(i, 10)\nelse:\npass<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Creating Analyze\u00a0Function<\/h2>\n<p class=\"wp-block-paragraph\">Here, we apply <code>matplotlib.pyplot<\/code> for the trading signals and the portfolio value visualization.<\/p>\n<pre class=\"wp-block-code\"><code><code>def analyze(context, perf):\nfig = plt.figure()\nax1 = fig.add_subplot(211)\nperf.portfolio_value.plot(ax=ax1)\nax1.set_ylabel(\"Portfolio value (NTD)\")\nax2 = fig.add_subplot(212)\nax2.set_ylabel(\"Price (NTD)\")\nperf.price.plot(ax=ax2)\nax2.plot( # \u7e6a\u88fd\u8cb7\u5165\u8a0a\u865f\nperf.index[perf.con2],\nperf.loc[perf.con2, 'price'],\n'^',\nmarkersize=5,\ncolor='red'\n)\nax2.plot( # \u7e6a\u88fd\u8ce3\u51fa\u8a0a\u865f\nperf.index[perf.con1],\nperf.loc[perf.con1, 'price'],\n'v',\nmarkersize=5,\ncolor='green'\n)\nplt.legend(loc=0)\nplt.gcf().set_size_inches(18,8)\nplt.show()<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Run Algorithms<\/h2>\n<p class=\"wp-block-paragraph\">We expliot <code>run_algorithm()<\/code> to execute our strategy. The backtesting time period is set between 2015\u201301\u201306 to 2022\u201311\u201325. The data bundle we use is <em>tquant<\/em>. We assume the initial capital base is 10,000. The output of <code>run_algorithm()<\/code>, which is <em>result<\/em>s, contains information on daily performance and trading receipts.<\/p>\n<pre class=\"wp-block-code\"><code><code>results = run_algorithm(start = pd.Timestamp('20150106', tz='UTC'),\nend = pd.Timestamp('20221125', tz='UTC'),\ninitialize=initialize,\nbundle='tquant',\nanalyze=analyze,\ncapital_base=1e4,\nhandle_data = handle_data\n)<\/code><\/code><\/pre>\n<figure class=\"wp-block-image aligncenter caption-align-center\"><img decoding=\"async\" alt=\"Portfolio value and trading time point\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/1_1BJZW8JsdZLV9cBH5qHkgZg.png\"\/><figcaption class=\"wp-element-caption\">Portfolio value and trading time point<\/figcaption><\/figure>\n<figure class=\"wp-block-image aligncenter is-resized caption-align-center\"><img loading=\"lazy\" decoding=\"async\" alt=\"Trading record\" height=\"497\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/1_1EukbHfeuXN7nZmv32_7GVg.png\" style=\"width:800px;height:497px\" width=\"800\"\/><figcaption class=\"wp-element-caption\">Trading record<\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Performance Analysis<\/h2>\n<p class=\"wp-block-paragraph\">Then, we used <code>Pyfolio <\/code>module which came with TQuant Lab to analyze strategy`s performance and risk. First, we use <code>extract_rets_pos_txn_from_zipline()<\/code> to calculate returns, positions, and trading records.<\/p>\n<pre class=\"wp-block-code\"><code><code>import pyfolio as pf\nreturns, positions, transactions = pf.utils.extract_rets_pos_txn_from_zipline(results)<\/code><\/code><\/pre>\n<h2 class=\"wp-block-heading\">Daily Returns<\/h2>\n<p class=\"wp-block-paragraph\">Calculating daily portfolio return.<\/p>\n<figure class=\"wp-block-image aligncenter caption-align-center\"><img decoding=\"async\" alt=\"Daily portfolio return\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/18SpZCxTnYIIjWEOJA5czDg.png\"\/><figcaption class=\"wp-element-caption\">Daily portfolio return<\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Holding Positions<\/h2>\n<ul class=\"wp-block-list\">\n<li>Equity(0 [2330]): TSMC<\/li>\n<li>Cash<\/li>\n<\/ul>\n<figure class=\"wp-block-image aligncenter caption-align-center\"><img decoding=\"async\" alt=\"Holding position record\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/1-tOQkCkIAveGGLb-Zs4Bg.png\"\/><figcaption class=\"wp-element-caption\">Holding position record<\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Transaction Record<\/h2>\n<ul class=\"wp-block-list\">\n<li>sid: index<\/li>\n<li>symbol: ticker symbol<\/li>\n<li>price: buy\/sell price<\/li>\n<li>order_id: order number<\/li>\n<li>amount: trading amount<\/li>\n<li>commission: commission cost<\/li>\n<li>dt: trading date<\/li>\n<li>txn_dollar: trading dollar volume<\/li>\n<\/ul>\n<figure class=\"wp-block-image aligncenter caption-align-center\"><img decoding=\"async\" alt=\"Trading record\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/12Xs75mm7d1eq4eCxXa61DA.png\"\/><figcaption class=\"wp-element-caption\">Trading record<\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Making Performance Table<\/h2>\n<p class=\"wp-block-paragraph\">With <code>show_perf_stats()<\/code>, one can easily showcase the performance and risk analysis table.<\/p>\n<pre class=\"wp-block-code\"><code><code>import pyfolio as pf\npf.plotting.show_perf_stats(\nreturns,\nbenchmark_rets,\npositions=positions,\ntransactions=transactions)<\/code><\/code><\/pre>\n<figure class=\"wp-block-image aligncenter caption-align-center\"><img decoding=\"async\" alt=\"Performance Table\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/1hVzmGySCXlTTed9ve1x9Hw.png\"\/><figcaption class=\"wp-element-caption\">Performance Table<\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Plot Accumulated Return and Benchmark Return<\/h2>\n<pre class=\"wp-block-code\"><code><code>benchmark_rets = results['benchmark_return'] \npf.plotting.plot_rolling_returns(returns, factor_returns=benchmark_rets)<\/code><\/code><\/pre>\n<figure class=\"wp-block-image aligncenter caption-align-center\"><img decoding=\"async\" alt=\"Figure for strategy returns\" src=\"https:\/\/www.tejwin.com\/en\/wp-content\/uploads\/2026\/08\/1BdXv5h4FZ77IB5v6wDfOIw.png\"\/><figcaption class=\"wp-element-caption\">Figure for strategy returns<\/figcaption><\/figure>\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n<p class=\"wp-block-paragraph\">The strategy introduced in this session is one of the mean-reversion trading strategies. When the market is oversold (Bullish Divergence, BDI &lt; 0), and the closing price is higher than the highest price over a certain period, it\u2019s 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 &gt; 0), and the closing price is lower than the lowest price over a certain period, it\u2019s believed that the stock price has risen too much and has a downward trend. In this case, the long position is exited. However, it\u2019s important to note that this strategy involves frequent trading, which transaction costs and taxes can erode. Therefore, it\u2019s recommended to combine other technical indicators to optimize entry and exit points.<\/p>\n<p class=\"wp-block-paragraph\">Finally, it\u2019s 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.<\/p>\n<h2 class=\"wp-block-heading\">Source Code<\/h2>\n<ul class=\"wp-block-list\">\n<li><a class=\"ek-link\" href=\"https:\/\/gist.github.com\/tej87681088\/04f7c31fa6d32fa3d04a149f492ff69d\" rel=\"noopener\" target=\"_blank\">Click here to go Github<\/a><\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\">Extended Reading<\/h2>\n<ul class=\"wp-block-list\">\n<li><a class=\"ek-link\" href=\"\/en\/insight\/tquant-lab-bollinger-bands-trading-strategy\/\">TQuant Lab Bollinger Bands Trading Strategy<\/a><\/li>\n<li><a class=\"ek-link\" href=\"\/en\/insight\/tquant-lab-rookie-manual\/\">TQuant Lab Rookie Manual<\/a><\/li>\n<\/ul>\n<h2 class=\"wp-block-heading\">Related Link<\/h2>\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/tejtw\/TQuant-Lab\" rel=\"noreferrer noopener\" target=\"_blank\">TQuant Lab Github<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>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\u2019s called a \u2018positive deviation.\u2019 Conversely, it\u2019s called\u2019 negative deviation\u2019 when it consistently falls below the moving average price.\u2019 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:<\/p>\n","protected":false},"featured_media":1819,"template":"","tags":[50,52],"insight_category":[16],"class_list":["post-1827","insight","type-insight","status-publish","has-post-thumbnail","hentry","tag-market-data","tag-quantitative-analysis","insight_category-quant-data-science"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/insight\/1827","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/insight"}],"about":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/types\/insight"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/media\/1819"}],"wp:attachment":[{"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/media?parent=1827"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/tags?post=1827"},{"taxonomy":"insight_category","embeddable":true,"href":"https:\/\/www.tejwin.com\/en\/wp-json\/wp\/v2\/insight_category?post=1827"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}