Investment Studio > Expressions > Functions > Indicator > DPO
float array[*][2] dpo(float array[*][2] dc, integer days)
Returns a two-column array containing dates (first column) and corresponding Detrended Price Oscillator (DPO) values (second column). Given n input rows in dc, n - days rows are returned; no output is generated for the first and last days div 2 input rows.
dc is a two-column array containing dates (first column) and corresponding daily closing prices (second column). The array is assumed to be time-sorted, with earlier dates preceding later dates.
Automatic type conversion allows the use of date strings as arguments instead of explicit date values.
days > 0 is a cycle length cutoff.
Interpretation
DPO is simple, symmetric high pass filter. It's usually described as a tool for removing cycles longer than days while leaving shorter cycles in place. It can be useful when looking for recurring, short term price patterns (as can be hipass, convolve, fftp, memp and other DSP functions).
The DPO's output is the difference between the price on the output date and the arithmetic moving average MA(days) of the price, computed days div 2 + 1 after the output date. This means that half the data going into the moving average is in the future, half in the past (which is what makes this a symmetric filter). As a consequence, there is no output for the last days div 2 input points.
| Warning: It's not unusual to see the DPO incorrectly defined as the difference between the price on the output date and the MA(days) of the price, computed days div 2 + 1 before (rather than after) the output date. While the confusion is understandable, using the wrong definition can be catastrophic: the result is a simple resonator which actually amplifies cycles in a wide band from days and up! This effect is particularly treacherous since the amplitude response still falls to zero in the long wavelength limit, making the error easy to miss on superficial inspection. |
To explore the relation between the number of days and this filter's behaviour, we can exploit the fact that the frequency response of any linear, time-invariant filter is just the Fourier transform of the filter's impulse response (the filter's output when the input sequence is a unit impulse, i.e. a single 1 surrounded by zeros, effectively all the way both to positive and to negative infinity). This means that we can visualize the difference in frequency response between DPOs of different lengths by passing a unit impulse through each one and computing the Fourier transforms of their output.
With the definitions
_n = 1024
_dc = makevector(_n, 1, 1) * {1, 0} + subarray(swapcols(m1(_n + 1), 2, _n div 2 + 1), 2, _n + 1, 1, 2)
_5_days = fftp(index(dpo(array(_dc), 5), 0, 2))
_10_days = fftp(index(dpo(array(_dc), 10), 0, 2))
_20_days = fftp(index(dpo(array(_dc), 20), 0, 2))
_50_days = fftp(index(dpo(array(_dc), 50), 0, 2))
the graph sources
=_n * index(array(_5_days), x + 1, 1)
=_n * index(array(_10_days), x + 1, 1)
=_n * index(array(_20_days), x + 1, 1)
=_n * index(array(_50_days), x + 1, 1)
(where the "+ 1" is to skip the zeroth, constant "harmonic") yield the following amplitude response graph (red for 5 days, green for 10, blue for 20, black for 50):

The days argument can be seen to determine the position of the first crossing of the unit amplitude response level (marked in gray): 1024 / 210 » 5 for days = 5; 1024 / 100 » 10 for days = 10; 1024 / 50 » 20 for days = 20; 1024 / 20 » 51 for days = 50. The reduction of longer cycles is roughly proportional to the distance (in terms of frequency, i.e. inverse period) from this point.
Note that the passband is subject to ripples, particularly pronounced near the first unit crossing. The hipass filter might be a better choice if a flat amplitude response in the passband is important.
For a real life example of the DPO in action, consider DoubleClick (NASD:DCLK) from June 1 to December 31, 2001:

Example
Assuming standard US date format settings,
=dpo({{"10/1/1990", 100}, {"10/2/1990", 120}, {"10/3/1990", 140}, {"10/4/1990", 140}, {"10/5/1990", 140}, {"10/8/1990", 140}, {"10/9/1990", 140}, {"10/10/1990", 120}, {"10/11/1990", 100}, {"10/12/1990", 120}, {"10/15/1990", 140}, {"10/16/1990", 140}, {"10/17/1990", 140}, {"10/18/1990", 140}, {"10/19/1990", 140}, {"10/22/1990", 120}, {"10/23/1990", 100}}, 5)
returns the array
{{33149, 12}, {33150, 4}, {33151, 0}, {33154, 4}, {33155, 12}, {33156, -4}, {33157, -24}, {33158, -4}, {33161, 12}, {33162, 4}, {33163, 0}, {33164, 4}}
Concentrating on the first row in the result, 33149 is the date code for October 3, 1990; 12 is the DPO on that date. This is the first date in the result since days = 5, so days div 2 = 2 leading input data points are needed; October 3 is the 3d input data point.
The output in this example suggests the presence of a 4 day cycle. The input is indeed the sum of a cycle with wavelength 8 (100, 110, 120, 130, 140, 130, 120, 110, 100...) and one with wavelength 4 (0, 10, 20, 10, 0...).