Investment Studio > Expressions > Functions > Indicator > MA
float array[*][2] ma(float array[*][2] dc, integer days)
Returns a two-column array containing dates (first column) and corresponding Moving Average (MA) values (second column). Given n input rows in dc, n - days + 1 rows are returned.
dc is a two-column array containing dates (first column) and corresponding daily closes (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 sets the timescale of the MA, i.e. the number of trading days contributing to the computation of each day's MA.
Interpretation
MA is a simple low pass filter: each day's value is the arithmetic average of the last days of input values. The idea is to attenuate short cycles (and noise) more than long cycles, so as to emphasize longer trends and cycles and reduce the risk of whipsaws (false, quickly negated signals). In practice, other low pass filters often do a better job. The pervasive use of the simple MA, especially as a building block in older indicators and trading systems, owes more to its computational simplicity than to its mathematical properties.
To explore the MA'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 MAs 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(ma(array(_dc), 5), 0, 2))
_10_days = fftp(index(ma(array(_dc), 10), 0, 2))
_20_days = fftp(index(ma(array(_dc), 20), 0, 2))
_50_days = fftp(index(ma(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 minimum in the amplitude response curve: 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 contribution of longer cycles to the output is roughly proportional to the distance (in terms of frequency, i.e. inverse period) from this point.
Note the presence of significant ripples beyond the first minimum: for instance, with days = 5, a cycle with a duration of 3.5 days will contribute more to the output than a cycle with a duration of 6 days, contrary to naive expectations based on the standard description of the MA function. One way to avoid this problem is to use EMA instead (see make_ema for amplitude response curves). The lopass filter might be an even better choice if a flat (rather than steadily declining) amplitude response in the stopband is important.
The simplest application of any moving average is to buy when prices cross above their moving average and sell when they cross below it. A slightly more sophisticated approach uses two moving averages of different length (e.g. 20 and 40 days), triggering buy signals when the short average crosses above the long average and vice versa. This method is usually more successful as a lagging validator of signals from faster indicators than as a leading buy or sell trigger.
Not only prices can benefit from smoothing, of course; any indicator prone to whipsaws is a candidate for MA filtering. Note however that there are also other low pass filters which may be more suitable for such purposes. See e.g. ema, convolve and lopass.
For a real life example of MAs in action, consider the Dow Jones 65 Composite in 2001:

The red line is MA(20) of daily closes, the blue line MA(50); the arrows denote MA crossings (red for short MA crossing above long MA, green for short MA crossing below long MA).
Example
Assuming standard US date format settings,
=ma({{"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}}, 4)
returns the array
{{33150, 125}, {33151, 135}, {33154, 140}, {33155, 140}, {33156, 135}, {33157, 125}, {33158, 120}, {33161, 120}, {33162, 125}, {33163, 135}, {33164, 140}, {33165, 140}, {33168, 135}, {33169, 125}}
Concentrating on the first row in the result, 33150 is the date code for October 4, 1990; 125 is the MA on that date. This is the first date in the result since days = 4, so days - 1 = 3 leading input data points are needed; October 4 is the 4th input data point.
The output in this example suggests the presence of an 8 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...).
The Dow Jones chart above uses the definitions
_SHORT_DAYS = 20
_LONG_DAYS = 40
_QUOTES = asset_quotes(SELF, "C", FROM_DATE - 2 * _LONG_DAYS, TO_DATE)
_SHORT_MA = ma(array(_QUOTES), _SHORT_DAYS)
_LONG_MA = ma(array(_QUOTES), _LONG_DAYS)
_CROSSES = crosses(array(_SHORT_MA), 2, array(_LONG_MA), 2)
_LONG_MA_TODAY = vlookup(X, array(_LONG_MA), 2, FALSE)
_SHORT_MA_TODAY = vlookup(X, array(_SHORT_MA), 2, FALSE)
the line sources
_LONG_MA_TODAY
_SHORT_MA_TODAY
and the image source
choose(2 + vlookup(X, array(_CROSSES), 2, FALSE), {9, _LONG_MA_TODAY, -8}, "", {10, _LONG_MA_TODAY, 8})