

                        NOROTON RIVER SOFTWARE

                    HOLT-WINTERS FORECASTING SYSTEM

                            DOCUMENTATION

1.0 General Information

The NRS Forecasting System is a Clipper 5.01 implementation of
the Holt-Winters forecasting method as described in FORECASTING
AND TIME SERIES ANALYSIS by Douglas C. Montgomery and Lynwood A.
Johnson (McGraw Hill Book Company, 1976), and many other texts. 
It is suitable for inclusion in inventory systems, sales
reporting systems, and many other applications where simple,
quick forecasts of seasonal time series are required.

The implementation is in the form of Clipper Subroutines which
communicate via static variables.  The programming has a flavor
of Object Oriented Programming as described in PROGRAMMING IN
CLIPPER 5 by Mike Schinkel (Addision-Wesley Publishing Co., Inc,
1992), and could easily be transformed to the OOP paradigm as
described there.  However, it is not OOP, it is subroutines.

The code is Copyright 1992 by Noroton River Software.

2.0 Noroton River Software

Noroton River Software is the management science/systems
development practice of the author,  Peter H. Vanderwaart, 29
Friar Tuck Lane, Stamford, CT 06907.  All questions should be
directed to me by mail or phone (203-322-4721).  Correspondence
via Compuserve (76116,2227) will be found to be undependable (my
fault, not Compuserve's; I don't log on when working at a client
site).

The program is offered for license on the following terms: notify
NRS that you are using it, no fee, casual support.

The purpose for offering this program is to determine if there is
interest in obtaining standard management science tools in
Clipper subroutine library format.  If you have an interest in
Clipper formulations of

	* Forecasting Algorithms,
	* Non-linear Optimization Algorithms,
	* Linear Programming Algorithms,
	* 'Front Ends' for specialized solvers,

or the like, please communicate with NRS.  I may have something
in my bag of tricks that can save you much time for little money.

The next program I intend to work on is an extension of this one
accepting daily sales updates (e.g. daily sales, weekly
forecasts).  This program will have a small shareware license
fee.

3.0 Program Concept

The forecasting method is variously called Holt-Winters, Winter's
Method, etc.  As implemented here, it uses a seasonal
decomposition trend model with multiplicative seasonality.  For
further discussion, see the reference given above (or later
edition) or look in texts on time series forecasting 'till you
find what you need.  If desperate, call NRS.

The data is assumed to be a time series with 4, 12, 13 or 52
periods per year.  Each observation is identified by integers
representing the year and period, e.g. for monthly data, year =
1992, period = 3 would imply March 1992. 

The forecasting algorithm accesses data and stores results using
code blocks defined within the application.  The code blocks are
stored as STATIC variables visible only within the NRS_FCST.PRG
file.  This allows the same code to adapt to data stored in
either arrays, or DBFs, and to variables and fields with
different naming conventions.

The code block definitions must be constructed with an eye to the
visibility of the variables involved.

The forecasting model must be initialized with the call of
several subroutines to define the code blocks.  Since the data
code blocks are STATIC, the initializing need only be done once,
and need not be done in the routine which updates the series or
calls for the forecast.  However, if various series have have
different data storage or naming conventions, the initializing
will have to be redone to suit.

The model parameters for each series also have to be initialized. 
This is done when the series is added to the system (e.g. when a
new brand is first included in a reporting system).  The various
reference books have discussions of the importance and the
methods.  If the initial model parameters are badly chosen, the
forecasts could be absurdly bad for several YEARS!  An example of
choosing model parameters for a series with four years of back
history is given in example 2.

If back history is not available, estimate the constant (use
planning estimates or the first value) and trend (ditto).  Borrow
the seasonals from another product that seems likely to have the
save seasonal pattern, e.g. for sun tan lotion, use the seasonals
from beach balls, not snow shovels.

4.0 Program Definitions

4.1 STATIC Variables

                  Default
        Variable  Value     Description
        -------   -----     -----------
        nPeriod    12       Model periods per year

        nAlpha    .15       Smoothing coeffecient for model constant
        nBeta     .15       Smoothing coeffecient for model trend
        nGamma    .50       Smoothing coeffecient for model seasonal
        nCoAlpha  .85       Complement  of Alpha
        nCoBeta   .85           "       "  Beta
        nCoGamma  .50           "       "  Gamma

	nFcstYear           Forecast model year
	nFcstPeriod         Forecast model period of year

4.2 STATIC Code Blocks

The code blocks stored in STATIC variables.

The expectation is that these code blocks will be initialized by
the application program, and therefore the default values are not
important.  Note that if the defaults are used, Clipper will use
its usual search for the appropriate variable and will use a
LOCAL if one if visible in the routine where the block is
defined, or a PRIVATE visible to the routine that executes the
block.  It is hard to see how the latter could be what you want!

In order to determine which code blocks must be initialized, note
the lists of blocks executed by FcstPostPer and FcstFcst in
section 4.4.

The block names have the 'b' prefix, except for those that are
permitted to take on values which are not simple blocks:
SetActual, SetDate, GetSeas, SetSeas, SetStd.

	Code			Default
        Block                    Value
        -------                  -----

        SetActual               { | x | nActData:= x}
        bGetActual              { |   | nActData}
	// SetActual stores the new actual time series observation
	   as part of the update cycle.  If SetActual is defined as
	   NIL, this step is skipped, saving a bit of time. 
	   bGetActual is included for completeness.

	bSetConst		{ | x | nConstant:= x}
	bGetConst		{ |   | nConstant}
	// bSetConst and bGetConst save and read the model constant
	   parameter.

	bSetTrend		{ | x | nTrend:= x}
	bGetTrend		{ |   | nTrend}
	// bSetTrend and bGetTrend save and read the model trend
	   parameter.

	bSetYear		{ | x | nYear:= x}
	bGetYear		{ |   | nYear}
	// bSetYear and bGetYear save and read the year of the most
	   recent model update.

        bSetPeriod              { | x | nPer:= x}
        bGetPeriod              { |   | nPer}
	// bSetPeriod and bGetPeriod save and read the period of the
	   most recent model update.
	
        SetDate                 { | x,y | dDate:= STR(x,4,0)+STR(y,2,0)}
	// SetDate does the job of bSetYear and bSetPeriod
	   simultaneously.  If it is set to NIL, this step is
	   omitted.

        SetSeas                 { | i , x | nSeas:= x}
        GetSeas                 { | i | nSeas}
	// SetSeas and Get Seas save and read the seasonal factor
	   for the period corresponding to the arguement.  These can
	   either be code blocks or arrays of code blocks.  If there
	   is an array, the ith element of the array will be
	   executed with the arguement x.

        SetStd                  { | x | std:= x}
        bGetStd                 { |   | std}
	// bSetStd and bGetStd save and read the forecast standard
	   deviation as of the most recent model update.  If SetStd
	   is NIL, this step is skipped.

4.3 Initialization & Communication Functions

	FUNCTION FcstPeriod(i)

		i = number of observations in one year.
		Sets smoothing coeffecients to standard values.
		RETURNS TRUE if initialization is successful, 
			otherwise FALSE.

	FUNCTION FcstAlpha(x)

		x = new value for Alpha
		Sets Alpha, CoAlpha
		RETURNS TRUE if initialization is successful, 
			otherwise FALSE.

	FUNCTION FcstBeta(x)

		x = new value for Beta
		Sets Beta, CoBeta
		RETURNS TRUE if initialization is successful, 
			otherwise FALSE.

	FUNCTION FcstGamma(x)

		x = new value for Gamma
		Sets Gamma, CoGamma
		RETURNS TRUE if initialization is successful, 
			otherwise FALSE.


	FUNCTION FcstActBlk(bSet,bGet)

		bSet = block to store actual value, can be block or
			NIL.
		bGet = block to read actual value.
		RETURNS NIL

	FUNCTION FcstStdBlk(bSet,bGet)

		bSet = block to store forecast standard deviation,
			can be block or NIL.
		bGet = block to read forecast standard deviation.
		RETURNS NIL


	FUNCTION FcstConBlk(bSet,bGet)

		bSet = block to store forecast model constant
			parameter.
		bGet = block to read forecast model constant parameter.
		RETURNS NIL

	FUNCTION FcstTrndBlk(bSet,bGet)

		bSet = block to store forecast model trend parameter.
		bGet = block to read forecast model trend parameter.
		RETURNS NIL


	FUNCTION FcstSeasBlk(bSet,bGet)

		bSet = block to store forecast model seasonality
			parameters; can be block or array of blocks.
		bGet = block to read forecast model seasonality
			parameters; can be block or array of blocks.
		RETURNS NIL

	FUNCTION FcstYearBlk(bSet,bGet)

		bSet = block to store forecast model year parameter.
		bGet = block to read forecast model year parameter.
		RETURNS NIL

	FUNCTION FcstPerBlk(bSet,bGet)
		bSet = block to store forecast model period parameter.
		bGet = block to read forecast model period parameter.
		RETURNS NIL

	FUNCTION FcstDtBlk(bSet)
		bSet = block to store both model year and period
		parameters.
		RETURNS NIL

	// The following functions are used to execute the code
	   blocks defined above.  It is hard to imagine a use for
	   some of them, but all of the various permutations are
	   included for completeness.  The availablity of the
	   function (which is visible everywhere) helps skirt any
	   trouble due to limited visibility of the variables in the
	   code blocks.

	// The actual functions are listed here since they are
	   mostly only two lines long.  In a couple, there is a type
	   check so that the block will not be executed if the type
	   is NIL.

	FUNCTION FcstSetAct(x)
	IF VALTYPE(SetActual) == 'B' ; EVAL(SetActual,x) ; ENDIF
	RETURN NIL

	FUNCTION FcstGetAct()
	RETURN EVAL(bGetActual)

	FUNCTION FcstSetStd(x)
	IF VALTYPE(SetStd) == 'B' ; EVAL(SetStd,x) ; ENDIF
	RETURN NIL

	FUNCTION FcstGetStd()
	RETURN EVAL(bGetStd)

	FUNCTION FcstSetCon(x)
	RETURN EVAL(bSetConst,x)

	FUNCTION FcstGetCon()
	RETURN EVAL(bGetConst)

	FUNCTION FcstGetTrnd()
	RETURN EVAL(bGetTrend)

	FUNCTION FcstSetTrnd(x)
	RETURN EVAL(bSetTrend,x)

	FUNCTION FcstSetSeas(i,x)
	IF VALTYPE(SetSeas) == 'B'
		RETURN EVAL(SetSeas,i,x)
	ELSEIF VALTYPE(SetSeas) == 'A'
		RETURN EVAL(SetSeas[i],x)
	ENDIF
	RETURN NIL

	FUNCTION FcstGetSeas(i)
	IF VALTYPE(GetSeas) == 'B'
		RETURN EVAL(GetSeas,i)
	ELSEIF VALTYPE(GetSeas) == 'A'
		RETURN EVAL(GetSeas[i])
	ENDIF
	RETURN NIL

	FUNCTION FcstSetDate(year, per)
	IF VALTYPE(SetDate) == 'B' ; EVAL(SetDate,year,per) ; ENDIF
	RETURN NIL

4.4 Forecast Functions

	// These are the functions that do the work!

	FUNCTION FcstPerPost(nActYear, nActPeriod, nActual)

	// This function takes an actual time series observation,
	   e.g. sales for 3rd quarter 1991, updates the model
	   parameters, stores the new parameters, stores the actual
	   (if desired), stores the date (if desired), and computes
	   and stores the standard deviation (if desired).

	// New observations are expected in order.  If a period is
	   skipped, the routine adjusts, but if the year and period
	   are previous to or the same as the last observation, the
	   data is discarded.

		nActYear = year of actual series observation
		nActPeriod = period of actual series observation
		nActual = series observation

		Code Blocks Evaluated:
			bGetYear
			bGetPeriod
			SetDate (via FcstSetDate if SetDate <> NIL)
			bGetConst
			bSetConst
			bGetTrend
			bSetTrend
			GetSeas (via FcstGetSeas())
			SetSeas (via FcstSetSeas)
			bGetStd (if SetStd <> NIL)
			SetStd  (via FcstSetStd if SetStd <> NIL)
			bSetAct (via FcstSetAct if bSetAct <> NIL)

		Functions Called:
			FcstGetSeas
			FcstSetSeas
			FcstSetDate
			FcstSetAct

	FUNCTION FcstFcst(nYear, nPer, nEst)

	// This function computes and returns a forecast of the time
	   series.  If a single estimate is requested, it is
	   returned as a value.  If a series of estimates is
	   requested, it is returned as an array of values.

		nYear = year of first forecast period
		nPer = period of first forecast period
		nEst = number of forecast estimates requested.

		Code Blocks Evaluated:
			bGetYear
			bGetPeriod
			bGetConst
			bGetTrend
			GetSeas (via FcstGetSeas)

		Functions Called:
			FcstGetSeas

		RETURNS Forecasts. Single value if nEst == 1, otherwise
			Array of values.

5.0 Examples

This section contains a couple of examples.  Example 1 uses data
in dbf files.  Example 2 uses data in arrays.

5.1 Example 1

This example is meant to demonstrate the usual situation in
inventory or sales forecasting systems:  There is a DBF with a
single record for each brand (or sku) which contains, among other
things, the forecast model (constant, trend & seasonals) for that
brand.  Each period (quarter in the example) the dbf is
proccessed against a file of the sales results for the previous
quarter.  Forecasts from the updated model can be used for
planning purposes.


	Brand database: FCSTTEST.dbf

	   Field
	#  Name     Type Len Dec

      1 BRAND      C   8   0
      2 M_DATE     C   6   0
      3 M_ACTUAL   N  10   2
      4 M_CONST    N  10   4
      5 M_TREND    N  10   4
      6 M_SEAS01   N  10   4
      7 M_SEAS02   N  10   4
      8 M_SEAS03   N  10   4
      9 M_SEAS04   N  10   4
     10 M_STD      N  10   4

	Quarterly Sales Databse: FCSTSALE.dbf

	   Field
	#  Name     Type Len Dec

      1 BRAND      C   8   0
      2 M_DATE     C   6   0
      3 M_ACTUAL   N  10   2

In this example, dates are kept as a 6 character string in YYYYPP
format, e.g. '199201' = 1st quarter 1992.

The following code fragment shows the initialization of the
forecasting model.

	// Clipper must be told that the variables in the blocks are 
	// fields.  This is done here with these FIELD statements.
	// It could be done with prefixes: FCSTTEST->m_const.

	FIELD m_actual, m_std, m_const, m_trend, m_date IN FCSTTEST
	FIELD m_seas01, m_seas02, m_seas03, m_seas04 IN FCSTTEST
	
	FcstPeriod(4)
	FcstConBlk({ | x | m_const := x },{ |  | m_const })
	FcstTrndBlk({ | x | m_trend := x },{ |  | m_trend})
	FcstYearBlk({ | x | m_date := STR(x,4,0)+RIGHT(m_date,2) },;
		{ |   | VAL(LEFT(m_date,4)) })
	FcstPerBlk({ | x | m_date := LEFT(m_date,4)+STR(x,2,0) }, ;
		{ |   | VAL(RIGHT(m_date,2)) })
	FcstActBlk( { | x | m_actual := x }, { |  | m_actual})
	FcstStdBlk( { | x | m_std := x }, { |  | m_std})
	FcstDtBlk( { | x,y | m_date:= STR(x,4,0)+STR(y,2,0)})

	// These array elements are defined individually here, but
	// could be done in a loop using macros, especially for
	// weekly data.

	S_array := ARRAY(4)
	S_ARRAY[1] := { | x | m_seas01 := x }
	S_ARRAY[2] := { | x | m_seas02 := x }
	S_ARRAY[3] := { | x | m_seas03 := x }
	S_ARRAY[4] := { | x | m_seas04 := x }

	G_array := ARRAY(4)
	G_ARRAY[1] := { |  | m_seas01 }
	G_ARRAY[2] := { |  | m_seas02 }
	G_ARRAY[3] := { |  | m_seas03 }
	G_ARRAY[4] := { |  | m_seas04 }
	FcstSeasBlk(S_array,G_array)


The following code fragment processes the brand file against the
sales file to update the forecast models.

	USE Fcsttest INDEX Fcsttest NEW
	USE FcstSale INDEX FcstSale NEW
	SELECT FcstSale

	DO WHILE .NOT. EOF()
	   SELECT Fcsttest
	   SEEK FcstSale->brand
	   IF EOF()
		// Code to handle brands not included in the database
		// goes here.
	   ENDIF
	   T_year   := VAL(LEFT(FcstSale->date,4))
	   T_period := VAL(RIGHT(FcstSale->date,2))
	   FcstPerPost(T_year,T_period,FcstSale->sales)
	   SELECT FcstSale
	   SKIP
	ENDDO


This code fragment computes the forecast four quarters into the
future and prints a simple report.

	SELECT Fcsttest
	GO TOP
	DO WHILE .NOT. EOF()
	   nActual := FcstGetAct()
	   nForecast := FcstFcst(1992,2,4)

	   ?brand,STR(nActual,8,2)
	   FOR I = 1 to 4
	      ?? STR(nForecast[i],9,2)
	   NEXT
	   SKIP
	ENDDO

5.2 Example 2

This sample demonstrates both a method of setting up the code
blocks for data contained in arrays, rather than dbf's, and also
demonstrates a way of computing initial model parameters, given
some history.


	// All variables are LOCAL
	// Data are 16 values beginning 1988, Q1

	LOCAL Series := { 1020, 1180, 1254, 1315, ;
	                  1418, 1579, 1645, 1724, ;
	                  1830, 1967, 2061, 2118, ;
	                  2225, 2386, 2439, 2519}
	LOCAL Seas := {1,1,1,1}
	LOCAL I_per, nConstant, nTrend, nYear, nPeriod, nStd
	LOCAL nActYear, nActPer, nForecast

	// Model Initialization

	FcstPeriod(4)
	FcstConBlk( { | x | nConstant:= x },{ |  | nConstant})
	FcstTrndBlk({ | x | nTrend   := x },{ |  | nTrend   })
	FcstYearBlk({ | x | nYear    := x },{ |  | nYear    })
	FcstPerBlk ({ | x | nPeriod  := x },{ |  | nPeriod  })
	FcstActBlk( NIL                    ,{ |  | Series[I_per]})
	FcstStdBlk( { | x | nStd     := x },{ |  | nStd     })
	FcstDtBlk(  { | x,y | nYear := x,     nPeriod := y  })
	FcstSeasBlk({ | i,x | Seas[i] := x},{ | i | Seas[i]})

	// Determine Initial model parameters.
	// Initial model date is period before start of data, 
	// i.e. 1987, Q4.

	nYear := 1987
	nPeriod := 4

	// Trend estimate is difference between average for first
	// year and average for second year, divided by four.

	nTrend := 0.25 *  ;
		 (0.25*(Series[5]+Series[6]+Series[7]+Series[8])  ;
		- 0.25*(Series[1]+Series[2]+Series[3]+Series[4]))

	// Constant estimate is average for first year, minus 2.5 *
	// nTrend to it to 'reset' q4, previous year

	nConstant := 0.25 *  ;
		(Series[1]+Series[2]+Series[3]+Series[4]) ;
		- 2.5 * nTrend

	// Seasonal estimates are derived from the model and first
	// year's data.

	FOR I_per = 1 TO 4
		Seas[I_per] := Series[I_per] / (nConstant+I_per*nTrend)
	NEXT

	// Standard deviation estimate is arbitrary 10% of first
	// value

	nStd := 0.1 * Series[1]

	// Run the 16 data values through the model to refine the
	// model constants

	nActYear := 1988
	nActPer  := 1
	FOR I_per = 1 to 16
		FcstPerPost(nActYear, nActPer, Series[I_per])
		IF nActPer < 4
			nActPer++
		ELSE
			nActYear++
			nActPer := 1
		ENDIF
	NEXT

	// Compute Forecast

	nForecast := FcstFcst(1992,1,4)

	// Print result

	?'  1991  1992  Diff'
	FOR I_per = 1 TO 4
		J_per := 12 + I_per
		?STR(Series[J_per],6,0), STR(nForecast[I_per],6,0),;
		STR(nForecast[I_per] - Series[J_per],6,0)
	NEXT

	?
	? '          Constant = ', STR(nConstant,8,2)
	? '             Trend = ', STR(nTrend,8,2)
	? 'Standard Deviation = ', STR(nStd,8,2)
	?


 
