Re: Calculating moving average

From: Charles Hooper <hooperc2000_at_yahoo.com>
Date: Sun, 18 May 2008 07:29:53 -0700 (PDT)
Message-ID: <8d96966c-a1ec-44ba-8489-54c2f67bb394@u6g2000prc.googlegroups.com>


On May 18, 2:19 am, nickli2..._at_gmail.com wrote:
> Hi,
>
> I am trying to calculating the following with SQL:
>
> Stock Date Price
> X 5/1/2008 10
> X 5/2/2008 12
> X 5/3/2008 13
> Y 5/1/2008 20
> Y 5/2/2008 22
> Y 5/3/2008 23
> ..........
>
> It will be a month of data with stock, date, price as unique,
> with thousands of symbols.
>
> Today's moving average is calculated based on previous days'
> moving average plus today's price. The sum will be divided by the
> number of days as in the following:
>
> For stock X:
> The moving average on 5/1/2008 will be 10
> The moving average on 5/2/2008 will be (10 + 12) / 2 = 11
> The moving average on 5/2/2008 will be (10 + 11 + 13) / 3 =
> 11.33
>
> The same for the stock Y:
> The moving average on 5/1/2008 will be 20
> The moving average on 5/2/2008 will be (20 + 22) / 2 = 21
> The moving average on 5/2/2008 will be (20 + 21 + 23) / 3 =
> 21.33
>
> I am trying to using windowing function to get the moving aveage
> but it doesn't seem to be working. Do I have to use PL/SQL to
> accomplish this?
>
> Thanks in advance.
>
> Nick Li

It appears that the AVG analytical function with a PARTITION BY and ORDER BY clause will provide a moving average solution to the problem, but not the solution that you are looking for. In the future, please include the DDL and DML to set up a test case for your problem.

Test case DDL and DML:
CREATE TABLE T1(
  STOCK VARCHAR2(5),
  STOCK_DATE DATE,
  PRICE NUMBER(10,2));

INSERT INTO T1 VALUES('X',TO_DATE('05/01/2008','MM/DD/YYYY'),10);
INSERT INTO T1 VALUES('X',TO_DATE('05/02/2008','MM/DD/YYYY'),12);
INSERT INTO T1 VALUES('X',TO_DATE('05/03/2008','MM/DD/YYYY'),13);
INSERT INTO T1 VALUES('Y',TO_DATE('05/01/2008','MM/DD/YYYY'),20);
INSERT INTO T1 VALUES('Y',TO_DATE('05/02/2008','MM/DD/YYYY'),22);
INSERT INTO T1 VALUES('Y',TO_DATE('05/03/2008','MM/DD/YYYY'),23);

SELECT
  STOCK,
  STOCK_DATE,
  AVG(PRICE) OVER (PARTITION BY STOCK ORDER BY STOCK_DATE) AVG_PRICE FROM
  T1;

STOCK STOCK_DAT AVG_PRICE

----- --------- ----------
X     01-MAY-08         10
X     02-MAY-08         11
X     03-MAY-08 11.6666667
Y     01-MAY-08         20
Y     02-MAY-08         21
Y     03-MAY-08 21.6666667

You will note that the last average for X and Y are different than what you expected. Let's see if we can mathematically calculate your rolling average:
X 10 = (10 + 0) / 1
X 11 = (12 + (10 + 0) / 1) / 2
X 11.333 = (13 + (10 + 0) / 1 + (12 + (10 + 0) / 1) / 2) / 3

Converting the numbers into variables, with P1 as the first price, P2 as the second price, P3 as the third price, etc. Xa3 = (P3 + (P1 + 0) / 1 + (P2 + (P1 + 0) / 1) / 2) / 3 Simplifying the above, which seems to imply that at Xa3, P1 has a greater impact on the average than either P2 or P3: Xa3 = P3/3 + P1/3 + P2/6 + P1/6
Working the example a couple more steps: Xa4 = (P4 + P3/3 + P1/3 + P2/6 + P1/6) / 4 Xa4 = P4/4 + P3/12 + P1/12 + P2/24 + P1/24

Xa5 = (P5 + P4/4 + P3/12 + P1/12 + P2/24 + P1/24) / 5 Xa5 = P5/5 + P4/20 + P3/60 + P1/60 + P2/120 + P1/120

Following this pattern, the following rule seems to apply, where n is a number, and n! is n factorial (n times every integer number down to 1)
Xan = Pn / ((n! / (n-1)!) + Pn-1 / ((n! / (n-2)!) + Pn-2 / ((n! / (n-3)!) +

     ... + P2 / ((n! / 1) + P1 / ((n! / 1) + P1 / ((n! / 2)

Are you sure that this is how the moving average is calculated? If it is, I suggest that you add another column to the table to store the previously calculated moving average, and then it is a simple matter to calculate the average of the moving average column without pegging the server's CPU trying to repeatedly calculate 1000! (assuming n is 1000) many times over.

Charles Hooper
IT Manager/Oracle DBA
K&M Machine-Fabricating, Inc. Received on Sun May 18 2008 - 09:29:53 CDT

Original text of this message