diff --git a/geomagio/algorithm/SQDistAlgorithm.py b/geomagio/algorithm/SQDistAlgorithm.py
index f3fa0d241d941255d9604c9bc756c7a87cb0f6f8..2905055b5f2a5745d754be6287745d74262cdfe9 100644
--- a/geomagio/algorithm/SQDistAlgorithm.py
+++ b/geomagio/algorithm/SQDistAlgorithm.py
@@ -12,345 +12,382 @@
         https://gist.github.com/andrequeiroz/5888967
 """
 
+from Algorithm import Algorithm
 from AlgorithmException import AlgorithmException
 import numpy as np
 from scipy.optimize import fmin_l_bfgs_b
 
 
-def additive(yobs, m, alpha, beta, gamma, phi=1,
-             yhat0=None, s0=None, l0=None, b0=None, sigma0=None,
-             zthresh=6, fc=0, hstep=0):
-    """Primary function for Holt-Winters smoothing/forecasting with
-      damped linear trend and additive seasonal component.
-
-    The adaptive standard deviation (sigma), multiplied by zthresh to
-    determine which observations should be smoothed or ignored, is always
-    updated using the latest error if a valid observation is available. This
-    way, if what seemed a spike in real-time was actually a more permanent
-    baseline shift, the algorithm will adjust to the new baseline once sigma
-    grows enough to accommodate the errors.
-
-    The standard deviation also updates when no obserations are present, but
-    does so according to Hyndman et al (2005) prediction intervals. The result
-    is a sigma that grows over gaps, and for forecasts beyond yobs[-1].
-
-    Parameters
-    ----------
-    yobs : array_like
-        input series to be smoothed/forecast
-    m : int
-        number of "seasons"
-    alpha : float
-        the level smoothing parameter (0<=alpha<=1).
-    beta : float
-        the slope smoothing parameter (0<=beta<=1).
-    gamma : float
-        the seasonal adjustment smoothing parameter (0<=gamma<=1).
-    phi : float
-        the dampening factor for slope (0<=phi<=1)
-        (if None, phi will be estimated; default is 1)
-    yhat0 : array_like
-        initial yhats for hstep>0 (len(yhat0) == hstep)
-        (if None, yhat0 will be set to NaNs)
-    s0 : array_like
-        initial set of seasonal adjustments
-        (if None, default is [yobs[i] - a[0] for i in range(m)])
-    l0 : float
-        initial level (i.e., l(t-hstep))
-        (if None, default is mean(yobs[0:m]))
-    b0 : float
-        initial slope (i.e., b(t-hstep))
-        (if None, default is (mean(yobs[m:2*m]) - mean(yobs[0:m]))/m )
-    sigma0 : float
-        initial standard-deviation estimate (len(sigma0) == hstep+1)
-        (if None, default is [sqrt(var(yobs))] * (hstep+1) )
-    zthresh : int
-        z-score threshold to determine whether yhat is updated by
-        smoothing observations, or by simulation alone; if exceeded,
-        only sigma is updated to reflect latest observation
-    fc : int
-        the number of steps beyond the end of yobs (the available
-        observations) to forecast
-    hstep : int
-        the number of steps ahead to predict yhat[i]
-        which forces an hstep prediction at each time step
-
-    Returns
-    -------
-    yhat      : series of smoothed/forecast values (aligned with yobs(t))
-    shat      : series of seasonal adjustments (aligned with yobs(t))
-    sigmahat: series of time-varying standard deviations (aligned with yobs(t))
-    yhat0next : use as yhat0 when function called again with new observations
-    s0next    : use as s0 when function called again with new observations
-    l0next    : use as l0 when function called again with new observations
-    b0next    : use as b0 when function called again with new observations
-    sigma0next: use as sigma0 when function called again with new observations
-
-    alpha     : optimized alpha (if input alpha is None)
-    beta      : optimized beta (if input beta is None)
-    gamma     : optimized gamma (if input gamma is None)
-    phi       : optimized phi (if input phi is None)
-    """
-
-    if alpha is None:
-        raise AlgorithmException('alpha is required')
-    if beta is None:
-        raise AlgorithmException('beta is required')
-    if gamma is None:
-        raise AlgorithmException('gamma is required')
-    if phi is None:
-        raise AlgorithmException('phi is required')
-
-    # set some default values
-    if l0 is None:
-        l = np.nanmean(yobs[0:int(m)])
-    else:
-        l = l0
-        if not np.isscalar(l0):
-            raise AlgorithmException("l0 must be a scalar")
-
-    if b0 is None:
-        b = (np.nanmean(yobs[m:2 * m]) - np.nanmean(yobs[0:m])) / m
-        b = 0 if np.isnan(b) else b  # replace NaN with 0
-    else:
-        b = b0
-        if not np.isscalar(b0):
-            raise AlgorithmException("b0 must be a scalar")
-
-    if yhat0 is None:
-        yhat = [np.nan for i in range(hstep)]
-    else:
-        yhat = list(yhat0)
-        if len(yhat) != hstep:
-            raise AlgorithmException("yhat0 must have length %d" % hstep)
-
-    if s0 is None:
-        s = [yobs[i] - l for i in range(m)]
-        s = [i if ~np.isnan(i) else 0 for i in s]  # replace NaNs with 0s
-    else:
-        s = list(s0)
-        if len(s) != m:
-            raise AlgorithmException("s0 must have length %d " % m)
-
-    if sigma0 is None:
-        sigma = [np.sqrt(np.nanvar(yobs))] * (hstep + 1)
-    else:
-        sigma = list(sigma0)
-        if len(sigma) != (hstep + 1):
-            raise AlgorithmException(
-                "sigma0 must have length %d" % (hstep + 1))
-
-    #
-    # Now begin the actual Holt-Winters algorithm
-    #
-
-    # ensure mean of seasonal adjustments is zero by setting first element of
-    # r equal to mean(s)
-    r = [np.nanmean(s)]
-
-    # determine sum(c^2) and phi_(j-1) for hstep "prediction interval" outside
-    # of loop; initialize variables for jstep (beyond hstep) prediction
-    # intervals
-    sumc2_H = 1
-    phiHminus1 = 0
-    for h in range(1, hstep):
-        phiHminus1 = phiHminus1 + phi ** (h - 1)
-        sumc2_H = sumc2_H + (alpha * (1 + phiHminus1 * beta) +
-                           gamma * (1 if (h % m == 0) else 0)) ** 2
-    phiJminus1 = phiHminus1
-    sumc2 = sumc2_H
-    jstep = hstep
-
-    # convert to, and pre-allocate numpy arrays
-    yobs = np.array(yobs)
-    sigma = np.concatenate((sigma, np.zeros(yobs.size + fc)))
-    yhat = np.concatenate((yhat, np.zeros(yobs.size + fc)))
-    r = np.concatenate((r, np.zeros(yobs.size + fc)))
-    s = np.concatenate((s, np.zeros(yobs.size + fc)))
-
-    # smooth/simulate/forecast yobs
-    for i in range(len(yobs) + fc):
-        # Update/append sigma for h steps ahead of i following
-        # Hyndman-et-al-2005. This will be over-written if valid observations
-        # exist at step i
-        if jstep == hstep:
-            sigma2 = sigma[i] * sigma[i]
-        sigma[i + hstep + 1] = np.sqrt(sigma2 * sumc2)
-
-        # predict h steps ahead
-        yhat[i + hstep] = l + phiHminus1 * b + s[i + hstep % m]
-
-        # determine discrepancy between observation and prediction at step i
-        if i < len(yobs):
-            et = yobs[i] - yhat[i]
-        else:
-            et = np.nan
+class SqDistAlgorithm(Algorithm):
+    """Solar Quiet, Secular Variation, and Disturbance algorithm"""
+
+    def __init__(self, alpha=None, beta=None, gamma=None, phi=1,
+                 yhat0=None, s0=None, l0=None, b0=None, sigma0=None,
+                 zthresh=6, fc=0, hstep=0):
+        self.alpha = alpha
+        self.beta = beta
+        self.gamma = gamma
+        self.phi = phi
+        self.yhat0 = yhat0
+        self.s0 = s0
+        self.l0 = l0
+        self.b0 = b0
+        self.sigma0 = sigma0
+        self.zthresh = zthresh
+        self.fc = fc
+        self.hstep = hstep
+
+    def process(self, yobs, m):
+        pass
+
+    @classmethod
+    def additive(yobs, m, alpha, beta, gamma, phi=1,
+                 yhat0=None, s0=None, l0=None, b0=None, sigma0=None,
+                 zthresh=6, fc=0, hstep=0):
+        """Primary function for Holt-Winters smoothing/forecasting with
+          damped linear trend and additive seasonal component.
+
+        The adaptive standard deviation (sigma), multiplied by zthresh to
+        determine which observations should be smoothed or ignored, is always
+        updated using the latest error if a valid observation is available.
+        This way, if what seemed a spike in real-time was actually a more
+        permanent baseline shift, the algorithm will adjust to the new baseline
+        once sigma grows enough to accommodate the errors.
+
+        The standard deviation also updates when no obserations are present,
+        but does so according to Hyndman et al (2005) prediction intervals.
+        The result is a sigma that grows over gaps, and for forecasts beyond
+        yobs[-1].
+
+        Parameters
+        ----------
+        yobs : array_like
+            input series to be smoothed/forecast
+        m : int
+            number of "seasons"
+        alpha : float
+            the level smoothing parameter (0<=alpha<=1).
+        beta : float
+            the slope smoothing parameter (0<=beta<=1).
+        gamma : float
+            the seasonal adjustment smoothing parameter (0<=gamma<=1).
+        phi : float
+            the dampening factor for slope (0<=phi<=1)
+            (if None, phi will be estimated; default is 1)
+        yhat0 : array_like
+            initial yhats for hstep>0 (len(yhat0) == hstep)
+            (if None, yhat0 will be set to NaNs)
+        s0 : array_like
+            initial set of seasonal adjustments
+            (if None, default is [yobs[i] - a[0] for i in range(m)])
+        l0 : float
+            initial level (i.e., l(t-hstep))
+            (if None, default is mean(yobs[0:m]))
+        b0 : float
+            initial slope (i.e., b(t-hstep))
+            (if None, default is (mean(yobs[m:2*m]) - mean(yobs[0:m]))/m )
+        sigma0 : float
+            initial standard-deviation estimate (len(sigma0) == hstep+1)
+            (if None, default is [sqrt(var(yobs))] * (hstep+1) )
+        zthresh : int
+            z-score threshold to determine whether yhat is updated by
+            smoothing observations, or by simulation alone; if exceeded,
+            only sigma is updated to reflect latest observation
+        fc : int
+            the number of steps beyond the end of yobs (the available
+            observations) to forecast
+        hstep : int
+            the number of steps ahead to predict yhat[i]
+            which forces an hstep prediction at each time step
+
+        Returns
+        -------
+        yhat : array_like
+            series of smoothed/forecast values (aligned with yobs(t))
+        shat : array_like
+            series of seasonal adjustments (aligned with yobs(t))
+        sigmahat : array_like
+            series of time-varying standard deviations (aligned with yobs(t))
+        yhat0next : array_like
+            use as yhat0 when function called again with new observations
+        s0next : float
+            use as s0 when function called again with new observations
+        l0next : float
+            use as l0 when function called again with new observations
+        b0next : float
+            use as b0 when function called again with new observations
+        sigma0next : float
+            use as sigma0 when function called again with new observations
+        alpha : float
+            optimized alpha (if input alpha is None)
+        beta : float
+            optimized beta (if input beta is None)
+        gamma : float
+            optimized gamma (if input gamma is None)
+        phi : float
+            optimized phi (if input phi is None)
+        """
 
-        if (np.isnan(et) or np.abs(et) > zthresh * sigma[i]):
-            # forecast (i.e., update l, b, and s assuming et==0)
+        if alpha is None:
+            raise AlgorithmException('alpha is required')
+        if beta is None:
+            raise AlgorithmException('beta is required')
+        if gamma is None:
+            raise AlgorithmException('gamma is required')
+        if phi is None:
+            raise AlgorithmException('phi is required')
+
+        # set some default values
+        if l0 is None:
+            l = np.nanmean(yobs[0:int(m)])
+        else:
+            l = l0
+            if not np.isscalar(l0):
+                raise AlgorithmException("l0 must be a scalar")
 
-            # no change in seasonal adjustments
-            r[i + 1] = 0
-            s[i + m] = s[i]
+        if b0 is None:
+            b = (np.nanmean(yobs[m:2 * m]) - np.nanmean(yobs[0:m])) / m
+            b = 0 if np.isnan(b) else b  # replace NaN with 0
+        else:
+            b = b0
+            if not np.isscalar(b0):
+                raise AlgorithmException("b0 must be a scalar")
 
-            # update l before b
-            l = l + phi * b
-            b = phi * b
+        if yhat0 is None:
+            yhat = [np.nan for i in range(hstep)]
+        else:
+            yhat = list(yhat0)
+            if len(yhat) != hstep:
+                raise AlgorithmException("yhat0 must have length %d" % hstep)
 
-            if np.isnan(et):
-                # when forecasting, grow sigma=sqrt(var) like a prediction
-                # interval; sumc2 and jstep will be reset with the next
-                # valid observation
-                phiJminus1 = phiJminus1 + phi ** jstep
-                sumc2 = sumc2 + (alpha * (1 + phiJminus1 * beta) +
-                                 gamma * (1 if (jstep % m == 0) else 0)) ** 2
-                jstep = jstep + 1
+        if s0 is None:
+            s = [yobs[i] - l for i in range(m)]
+            s = [i if ~np.isnan(i) else 0 for i in s]  # replace NaNs with 0s
+        else:
+            s = list(s0)
+            if len(s) != m:
+                raise AlgorithmException("s0 must have length %d " % m)
 
+        if sigma0 is None:
+            sigma = [np.sqrt(np.nanvar(yobs))] * (hstep + 1)
+        else:
+            sigma = list(sigma0)
+            if len(sigma) != (hstep + 1):
+                raise AlgorithmException(
+                    "sigma0 must have length %d" % (hstep + 1))
+
+        #
+        # Now begin the actual Holt-Winters algorithm
+        #
+
+        # ensure mean of seasonal adjustments is zero by setting first element
+        # of r equal to mean(s)
+        r = [np.nanmean(s)]
+
+        # determine sum(c^2) and phi_(j-1) for hstep "prediction interval"
+        # outside of loop; initialize variables for jstep (beyond hstep)
+        # prediction intervals
+        sumc2_H = 1
+        phiHminus1 = 0
+        for h in range(1, hstep):
+            phiHminus1 = phiHminus1 + phi ** (h - 1)
+            sumc2_H = sumc2_H + (alpha * (1 + phiHminus1 * beta) +
+                               gamma * (1 if (h % m == 0) else 0)) ** 2
+        phiJminus1 = phiHminus1
+        sumc2 = sumc2_H
+        jstep = hstep
+
+        # convert to, and pre-allocate numpy arrays
+        yobs = np.array(yobs)
+        sigma = np.concatenate((sigma, np.zeros(yobs.size + fc)))
+        yhat = np.concatenate((yhat, np.zeros(yobs.size + fc)))
+        r = np.concatenate((r, np.zeros(yobs.size + fc)))
+        s = np.concatenate((s, np.zeros(yobs.size + fc)))
+
+        # smooth/simulate/forecast yobs
+        for i in range(len(yobs) + fc):
+            # Update/append sigma for h steps ahead of i following
+            # Hyndman-et-al-2005. This will be over-written if valid
+            # observations exist at step i
+            if jstep == hstep:
+                sigma2 = sigma[i] * sigma[i]
+            sigma[i + hstep + 1] = np.sqrt(sigma2 * sumc2)
+
+            # predict h steps ahead
+            yhat[i + hstep] = l + phiHminus1 * b + s[i + hstep % m]
+
+            # discrepancy between observation and prediction at step i
+            if i < len(yobs):
+                et = yobs[i] - yhat[i]
             else:
-                # still update sigma using et when et > zthresh * sigma
-                # (and is not NaN)
+                et = np.nan
+
+            if (np.isnan(et) or np.abs(et) > zthresh * sigma[i]):
+                # forecast (i.e., update l, b, and s assuming et==0)
+
+                # no change in seasonal adjustments
+                r[i + 1] = 0
+                s[i + m] = s[i]
+
+                # update l before b
+                l = l + phi * b
+                b = phi * b
+
+                if np.isnan(et):
+                    # when forecasting, grow sigma=sqrt(var) like a prediction
+                    # interval; sumc2 and jstep will be reset with the next
+                    # valid observation
+                    phiJminus1 = phiJminus1 + phi ** jstep
+                    sumc2 = sumc2 + (alpha * (1 + phiJminus1 * beta) +
+                            gamma * (1 if (jstep % m == 0) else 0)) ** 2
+                    jstep = jstep + 1
+
+                else:
+                    # still update sigma using et when et > zthresh * sigma
+                    # (and is not NaN)
+                    sigma[i + 1] = alpha * np.abs(et) + (1 - alpha) * sigma[i]
+            else:
+                # smooth (i.e., update l, b, and s by filtering et)
+
+                # renormalization could occur inside loop, but we choose to
+                # integrate r, and adjust a and s outside the loop to improve
+                # performance.
+                r[i + 1] = gamma * (1 - alpha) * et / m
+
+                # update and append to s using equation-error formulation
+                s[i + m] = s[i] + gamma * (1 - alpha) * et
+
+                # update l and b using equation-error formulation
+                l = l + phi * b + alpha * et
+                b = phi * b + alpha * beta * et
+
+                # update sigma with et, then reset prediction interval
                 sigma[i + 1] = alpha * np.abs(et) + (1 - alpha) * sigma[i]
-        else:
-            # smooth (i.e., update l, b, and s by filtering et)
-
-            # renormalization could occur inside loop, but we choose to
-            # integrate r, and adjust a and s outside the loop to improve
-            # performance.
-            r[i + 1] = gamma * (1 - alpha) * et / m
-
-            # update and append to s using equation-error formulation
-            s[i + m] = s[i] + gamma * (1 - alpha) * et
-
-            # update l and b using equation-error formulation
-            l = l + phi * b + alpha * et
-            b = phi * b + alpha * beta * et
-
-            # update sigma with et, then reset prediction interval variables
-            sigma[i + 1] = alpha * np.abs(et) + (1 - alpha) * sigma[i]
-            sumc2 = sumc2_H
-            phiJminus1 = phiHminus1
-            jstep = hstep
-        # endif (np.isnan(et) or np.abs(et) > zthresh * sigma[i])
-
-    # endfor i in range(len(yobs) + fc - hstep)
-
-    r = np.cumsum(r)
-    l = l + r[-1]
-    s = list(np.array(s) - np.hstack((r, np.tile(r[-1], m - 1))))
-
-    return (yhat[:len(yobs) + fc],
-            s[:len(yobs) + fc],
-            sigma[1:len(yobs) + fc + 1],
-            yhat[len(yobs) + fc:],
-            s[len(yobs) + fc:],
-            l,
-            b,
-            sigma[len(yobs) + fc:],
-            alpha,
-            beta,
-            gamma)
-
-
-def estimate_parameters(yobs, m, alpha=None, beta=None, gamma=None, phi=1,
-        yhat0=None, s0=None, l0=None, b0=None, sigma0=None,
-        zthresh=6, fc=0, hstep=0,
-        alpha0=0.3, beta0=0.1, gamma0=0.1):
-    """Estimate alpha, beta, and gamma parameters based on observed data.
-
-    Parameters
-    ----------
-    yobs : array_like
-        input series to be smoothed/forecast
-    m : int
-        number of "seasons"
-    alpha : float
-        the level smoothing parameter (0<=alpha<=1).
-    beta : float
-        the slope smoothing parameter (0<=beta<=1).
-    gamma : float
-        the seasonal adjustment smoothing parameter (0<=gamma<=1).
-    phi : float
-        the dampening factor for slope (0<=phi<=1)
-        (if None, phi will be estimated; default is 1)
-    yhat0 : array_like
-        initial yhats for hstep>0 (len(yhat0) == hstep)
-        (if None, yhat0 will be set to NaNs)
-    s0 : array_like
-        initial set of seasonal adjustments
-        (if None, default is [yobs[i] - a[0] for i in range(m)])
-    l0 : float
-        initial level (i.e., l(t-hstep))
-        (if None, default is mean(yobs[0:m]))
-    b0 : float
-        initial slope (i.e., b(t-hstep))
-        (if None, default is (mean(yobs[m:2*m]) - mean(yobs[0:m]))/m )
-    sigma0 : float
-        initial standard-deviation estimate (len(sigma0) == hstep+1)
-        (if None, default is [sqrt(var(yobs))] * (hstep+1) )
-    zthresh : int
-        z-score threshold to determine whether yhat is updated by
-        smoothing observations, or by simulation alone; if exceeded,
-        only sigma is updated to reflect latest observation
-    fc : int
-        the number of steps beyond the end of yobs (the available
-        observations) to forecast
-    hstep : int
-        the number of steps ahead to predict yhat[i]
-        which forces an hstep prediction at each time step
-    alpha0 : float
-        initial value for alpha.
-        used only when alpha is None.
-    beta0 : float
-        initial value for beta.
-        used only when beta is None.
-    gamma0 : float
-        initial value for gamma.
-        used only when gamma is None.
-
-    Returns
-    -------
-    alpha : float
-        optimized parameter alpha (if alpha was None).
-    beta : float
-        optimized parameter beta (if beta was None).
-    gamma : float
-        optimized gamma, if gamma was None.
-    rmse : float
-        root-mean-squared-error for data using optimized parameters.
-    """
-    # if alpha/beta/gamma is specified, restrict bounds to "fix" parameter.
-    boundaries = [
-        (alpha, alpha) if alpha is not None else (0, 1),
-        (beta, beta) if beta is not None else (0, 1),
-        (gamma, gamma) if gamma is not None else (0, 1)
-    ]
-    initial_values = np.array([
-        alpha if alpha is not None else alpha0,
-        beta if beta is not None else beta0,
-        gamma if gamma is not None else gamma0
-    ])
-
-    def func(params, *args):
-        """Function that computes root-mean-squared-error based on current
-        alpha, beta, and gamma parameters; as provided by fmin_l_bfgs_b.
+                sumc2 = sumc2_H
+                phiJminus1 = phiHminus1
+                jstep = hstep
+            # endif (np.isnan(et) or np.abs(et) > zthresh * sigma[i])
+
+        # endfor i in range(len(yobs) + fc - hstep)
+
+        r = np.cumsum(r)
+        l = l + r[-1]
+        s = list(np.array(s) - np.hstack((r, np.tile(r[-1], m - 1))))
+
+        return (yhat[:len(yobs) + fc],
+                s[:len(yobs) + fc],
+                sigma[1:len(yobs) + fc + 1],
+                yhat[len(yobs) + fc:],
+                s[len(yobs) + fc:],
+                l,
+                b,
+                sigma[len(yobs) + fc:],
+                alpha,
+                beta,
+                gamma)
+
+    @classmethod
+    def estimate_parameters(yobs, m, alpha=None, beta=None, gamma=None, phi=1,
+            yhat0=None, s0=None, l0=None, b0=None, sigma0=None,
+            zthresh=6, fc=0, hstep=0,
+            alpha0=0.3, beta0=0.1, gamma0=0.1):
+        """Estimate alpha, beta, and gamma parameters based on observed data.
 
         Parameters
         ----------
-        params: list-like
-            list containing alpha, beta, and gamma parameters to test
+        yobs : array_like
+            input series to be smoothed/forecast
+        m : int
+            number of "seasons"
+        alpha : float
+            the level smoothing parameter (0<=alpha<=1).
+        beta : float
+            the slope smoothing parameter (0<=beta<=1).
+        gamma : float
+            the seasonal adjustment smoothing parameter (0<=gamma<=1).
+        phi : float
+            the dampening factor for slope (0<=phi<=1)
+            (if None, phi will be estimated; default is 1)
+        yhat0 : array_like
+            initial yhats for hstep>0 (len(yhat0) == hstep)
+            (if None, yhat0 will be set to NaNs)
+        s0 : array_like
+            initial set of seasonal adjustments
+            (if None, default is [yobs[i] - a[0] for i in range(m)])
+        l0 : float
+            initial level (i.e., l(t-hstep))
+            (if None, default is mean(yobs[0:m]))
+        b0 : float
+            initial slope (i.e., b(t-hstep))
+            (if None, default is (mean(yobs[m:2*m]) - mean(yobs[0:m]))/m )
+        sigma0 : float
+            initial standard-deviation estimate (len(sigma0) == hstep+1)
+            (if None, default is [sqrt(var(yobs))] * (hstep+1) )
+        zthresh : int
+            z-score threshold to determine whether yhat is updated by
+            smoothing observations, or by simulation alone; if exceeded,
+            only sigma is updated to reflect latest observation
+        fc : int
+            the number of steps beyond the end of yobs (the available
+            observations) to forecast
+        hstep : int
+            the number of steps ahead to predict yhat[i]
+            which forces an hstep prediction at each time step
+        alpha0 : float
+            initial value for alpha.
+            used only when alpha is None.
+        beta0 : float
+            initial value for beta.
+            used only when beta is None.
+        gamma0 : float
+            initial value for gamma.
+            used only when gamma is None.
+
+        Returns
+        -------
+        alpha : float
+            optimized parameter alpha (if alpha was None).
+        beta : float
+            optimized parameter beta (if beta was None).
+        gamma : float
+            optimized gamma, if gamma was None.
+        rmse : float
+            root-mean-squared-error for data using optimized parameters.
         """
-        # extract parameters to fit
-        alpha, beta, gamma = params
-        # call Holt-Winters with additive seasonality
-        yhat, _, _, _, _, _, _, _, _, _, _ = additive(yobs, m,
-                alpha=alpha, beta=beta, gamma=gamma, l0=l0, b0=b0, s0=s0,
-                zthresh=zthresh, hstep=hstep)
-        # compute root-mean-squared-error of predictions
-        error = np.sqrt(np.nanmean(np.square(np.subtract(yobs, yhat))))
-        return error
-
-    parameters = fmin_l_bfgs_b(func, x0=initial_values, args=(),
-            bounds=boundaries, approx_grad=True)
-    alpha, beta, gamma = parameters[0]
-    rmse = parameters[1]
-    return (alpha, beta, gamma, rmse)
+        # if alpha/beta/gamma is specified, restrict bounds to "fix" parameter.
+        boundaries = [
+            (alpha, alpha) if alpha is not None else (0, 1),
+            (beta, beta) if beta is not None else (0, 1),
+            (gamma, gamma) if gamma is not None else (0, 1)
+        ]
+        initial_values = np.array([
+            alpha if alpha is not None else alpha0,
+            beta if beta is not None else beta0,
+            gamma if gamma is not None else gamma0
+        ])
+
+        def func(params, *args):
+            """Function that computes root-mean-squared-error based on current
+            alpha, beta, and gamma parameters; as provided by fmin_l_bfgs_b.
+
+            Parameters
+            ----------
+            params: list-like
+                list containing alpha, beta, and gamma parameters to test
+            """
+            # extract parameters to fit
+            alpha, beta, gamma = params
+            # call Holt-Winters with additive seasonality
+            yhat, _, _, _, _, _, _, _, _, _, _ = SqDistAlgorithm.additive(
+                    yobs, m,
+                    alpha=alpha, beta=beta, gamma=gamma, l0=l0, b0=b0, s0=s0,
+                    zthresh=zthresh, hstep=hstep)
+            # compute root-mean-squared-error of predictions
+            error = np.sqrt(np.nanmean(np.square(np.subtract(yobs, yhat))))
+            return error
+
+        parameters = fmin_l_bfgs_b(func, x0=initial_values, args=(),
+                bounds=boundaries, approx_grad=True)
+        alpha, beta, gamma = parameters[0]
+        rmse = parameters[1]
+        return (alpha, beta, gamma, rmse)
diff --git a/geomagio/algorithm/__init__.py b/geomagio/algorithm/__init__.py
index caaabb0c7a2ab733eb7cd9b3405a27aa00a2ecd3..b7509ad6bdc9fdc68544eea72bc9f7c9de41407f 100644
--- a/geomagio/algorithm/__init__.py
+++ b/geomagio/algorithm/__init__.py
@@ -7,6 +7,7 @@ from Algorithm import Algorithm
 from AlgorithmException import AlgorithmException
 # algorithms
 from DeltaFAlgorithm import DeltaFAlgorithm
+from SqDistAlgorithm import SqDistAlgorithm
 from XYZAlgorithm import XYZAlgorithm
 
 
@@ -14,6 +15,7 @@ from XYZAlgorithm import XYZAlgorithm
 algorithms = {
     'identity': Algorithm,
     'deltaf': DeltaFAlgorithm,
+    'sqdist': SqDistAlgorithm,
     'xyz': XYZAlgorithm
 }
 
@@ -24,5 +26,6 @@ __all__ = [
     'AlgorithmException',
     # algorithms
     'DeltaFAlgorithm',
+    'SqDistAlgorithm',
     'XYZAlgorithm'
 ]