diff --git a/docs/algorithms/DeltaF_usage.md b/docs/algorithms/DeltaF_usage.md
index ff93d774601967b7c784293904ca7a715d5dfb44..beb0b2d023fea4777daa0dd9f54a11fe1e2a6769 100644
--- a/docs/algorithms/DeltaF_usage.md
+++ b/docs/algorithms/DeltaF_usage.md
@@ -6,7 +6,7 @@ and a scalar total-field measurement made by independent sensors.  Read more
 about the [DeltaF Algorithm](./DeltaF.md).
 
 
-`geomag.py --deltaf {geo, obs, obsd}`
+`geomag.py --algorithm deltaf [--deltaf-from {geo, obs, obsd}]`
 
 
 ### Reference Frames
@@ -21,7 +21,7 @@ about the [DeltaF Algorithm](./DeltaF.md).
 To compute DeltaF from  HEZF data for Tucson observatory:
 ```
 geomag.py \
-    --deltaf obs \
+    --algorithm deltaf \
     --observatory TUC \
     --type variation \
     --interval minute \
diff --git a/docs/algorithms/XYZ_usage.md b/docs/algorithms/XYZ_usage.md
index b01fdd8be4834f4ed1884bb1840e3faa61b2613d..d154887f83005f19203821eb40d9c7504fc37992 100644
--- a/docs/algorithms/XYZ_usage.md
+++ b/docs/algorithms/XYZ_usage.md
@@ -5,7 +5,7 @@ The XYZ Algorithm rotates between `geographic`, `observatory`, and `magnetic`,
 channel orientations.  Read more about the [XYZ Algorithm](./XYZ.md).
 
 
-`geomag.py --xyz {geo, mag, obs, obsd} {geo, mag, obs, obsd}`
+`geomag.py --algorithm xyz [--xyz-from {geo,mag,obs,obsd}] [--xyz-to {geo,mag,obs,obsd}]`
 
 ### Reference Frames
 
@@ -30,7 +30,7 @@ There are 3 reference frames in this library.
 To convert HEZF data in pcdcp files to XYZF for Tucson observatory for all of
 March 2013 output to iaga2002 files:
 
-      geomag.py --xyz obs geo --observatory TUC \
+      geomag.py --algorithm xyz --observatory TUC \
       --starttime 2013-03-01T00:00:00Z --endtime 2013-03-31T23:59:00Z \
       --input-pcdcp-url file://data-pcdcp/./%(OBS)s%(year)s%(julian)s.%(i)s \
       --output-iaga-url file://data-iaga/./$(obs)s%(Y)s%(j)s.%(i)s \
diff --git a/docs/api.md b/docs/api.md
index 15c8df354c0b47fbcd11445a1c0b5e87fd246910..61ef90e0570f4a0b4ee1d6c40ff292ee792f3c71 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -44,11 +44,11 @@ Exception base class is `geomagio.TimeseriesFactoryException`.
 
 ## Algorithms
 
-Base class is `geomagio.Algorithm`
-Exception base class is `geomagio.AlgorithmException`
+Base class is `geomagio.algorithm.Algorithm`
+Exception base class is `geomagio.algorithm.AlgorithmException`
 
-- Delta F `geomagio.DeltaFAlgorithm`
-- XYZ `geomagio.XYZAlgorithm`
+- Delta F `geomagio.algorithm.DeltaFAlgorithm`
+- XYZ `geomagio.algorithm.XYZAlgorithm`
 
 
 ## Example
@@ -60,7 +60,7 @@ The following example:
 - Plots the data using matplotlib
 
 ```python
-from geomagio import XYZAlgorithm
+from geomagio.algorithm import XYZAlgorithm
 from geomagio.edge import EdgeFactory
 from obspy.core import UTCDateTime
 
diff --git a/docs/usage.md b/docs/usage.md
index bc28f80a448391409d1e1fc8e20c6e32147c23a4..a0bc321e649eef05c34694469449a2133f452330 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -68,10 +68,12 @@ There are flags to specify certain algorithms should be run against the data.
 
 #### XYZ ####
 
-`--xyz {geo, mag, obs, obsd} {geo, mag, obs, obsd}`
+`--algorithm xyz`
+`--xyz-from {geo, mag, obs, obsd}` (default is `obs`)
+`--xyz-to {geo, mag, obs, obsd}` (default is `geo`)
 
 #### [XYZ Usage](./algorithms/XYZ_usage.md) ####
-Rotate data from HEZ or HDZ to XYZ and back.
+Rotate data from HEZ (obs) or HDZ (mag) to XYZ (geo) and back.
 
 Extensive explanation of all input and output methods:
 [IO Methods](./io.md)
diff --git a/geomagio/Controller.py b/geomagio/Controller.py
index 60094a11700cabdb4f778fa1120c73b3b74b507b..f6142619a289060adf0cb79ee185c355775277c9 100644
--- a/geomagio/Controller.py
+++ b/geomagio/Controller.py
@@ -4,7 +4,7 @@
 import argparse
 import sys
 from obspy.core import UTCDateTime
-from Algorithm import Algorithm
+from algorithm import algorithms
 import TimeseriesUtility
 from TimeseriesFactoryException import TimeseriesFactoryException
 from Util import ObjectView
@@ -14,9 +14,6 @@ import iaga2002
 import pcdcp
 import imfv283
 
-from DeltaFAlgorithm import DeltaFAlgorithm
-from XYZAlgorithm import XYZAlgorithm
-
 
 class Controller(object):
     """Controller for geomag algorithms.
@@ -301,17 +298,8 @@ def main(args):
     else:
             print >> sys.stderr, "Missing required output directive"
 
-    if args.xyz is not None:
-        algorithm = XYZAlgorithm(informat=args.xyz[0],
-                outformat=args.xyz[1])
-    elif args.deltaf is not None:
-        algorithm = DeltaFAlgorithm(informat=args.deltaf)
-    else:
-        # TODO get smarter on inchannels/outchannels since input doesn't always
-        # need to use the --inchannels argument, but might (as in iaga2002),
-        # get it from the file.
-        algorithm = Algorithm(inchannels=args.inchannels,
-                outchannels=args.outchannels or args.inchannels)
+    algorithm = algorithms[args.algorithm]()
+    algorithm.configure(args)
 
     # TODO check for unused arguments.
 
@@ -481,14 +469,11 @@ def parse_args(args):
             help='Edge IP #. See --output-edge-* for other optional arguments')
 
     # Algorithms group
-    algorithm_group = parser.add_mutually_exclusive_group()
-    algorithm_group.add_argument('--xyz',
-            nargs=2,
-            choices=['geo', 'mag', 'obs', 'obsd'],
-            help='Enter the geomagnetic orientation(s) you want to read from' +
-                    ' and to respectfully.')
-    algorithm_group.add_argument('--deltaf',
-            choices=['geo', 'obs', 'obsd'],
-            help='Enter the geomagnetic orientation you want to read from')
+    parser.add_argument('--algorithm',
+            choices=[k for k in algorithms],
+            default='default')
+
+    for k in algorithms:
+        algorithms[k].add_arguments(parser)
 
     return parser.parse_args(args)
diff --git a/geomagio/__init__.py b/geomagio/__init__.py
index 4b0f6a79f4f91a58d04ef4f4e7a9fd595ba840de..9e086ee633914218db2d191bafdd1a54bc6f4b4a 100644
--- a/geomagio/__init__.py
+++ b/geomagio/__init__.py
@@ -4,19 +4,14 @@ Geomag Algorithm Module
 import ChannelConverter
 import StreamConverter
 
-from Algorithm import Algorithm
-from AlgorithmException import AlgorithmException
 from Controller import Controller
 from ObservatoryMetadata import ObservatoryMetadata
 from TimeseriesFactory import TimeseriesFactory
 from TimeseriesFactoryException import TimeseriesFactoryException
 import TimeseriesUtility
 import Util
-from XYZAlgorithm import XYZAlgorithm
 
 __all__ = [
-    'Algorithm',
-    'AlgorithmException',
     'ChannelConverter',
     'Controller',
     'DeltaFAlgorithm',
diff --git a/geomagio/Algorithm.py b/geomagio/algorithm/Algorithm.py
similarity index 79%
rename from geomagio/Algorithm.py
rename to geomagio/algorithm/Algorithm.py
index bd7699089ba45eb8da9bd2e7085c057fb341efe1..fed7196b967c626b31472c0ed62abe94b1025365 100644
--- a/geomagio/Algorithm.py
+++ b/geomagio/algorithm/Algorithm.py
@@ -1,6 +1,6 @@
 """Algorithm Interface."""
 
-import TimeseriesUtility
+from .. import TimeseriesUtility
 
 
 class Algorithm(object):
@@ -91,3 +91,25 @@ class Algorithm(object):
                     endtime < input_gap[2]):
                 return False
         return True
+
+    @classmethod
+    def add_arguments(cls, parser):
+        """Add command line arguments to argparse parser.
+
+        Parameters
+        ----------
+        parser: ArgumentParser
+            command line argument parser
+        """
+        pass
+
+    def configure(self, arguments):
+        """Configure algorithm using comand line arguments.
+
+        Parameters
+        ----------
+        arguments: Namespace
+            parsed command line arguments
+        """
+        self._inchannels = arguments.inchannels
+        self._outchannels = arguments.outchannels or arguments.inchannels
diff --git a/geomagio/AlgorithmException.py b/geomagio/algorithm/AlgorithmException.py
similarity index 100%
rename from geomagio/AlgorithmException.py
rename to geomagio/algorithm/AlgorithmException.py
diff --git a/geomagio/DeltaFAlgorithm.py b/geomagio/algorithm/DeltaFAlgorithm.py
similarity index 66%
rename from geomagio/DeltaFAlgorithm.py
rename to geomagio/algorithm/DeltaFAlgorithm.py
index d0619dee63ae90534ec4034063c383219cc90947..616c83d87db39049276edba93466343cfad1cbe8 100644
--- a/geomagio/DeltaFAlgorithm.py
+++ b/geomagio/algorithm/DeltaFAlgorithm.py
@@ -4,7 +4,7 @@
 
 from Algorithm import Algorithm
 from AlgorithmException import AlgorithmException
-import StreamConverter as StreamConverter
+from .. import StreamConverter
 
 # List of channels by geomagnetic observatory orientation.
 # geo represents a geographic north/south orientation
@@ -27,10 +27,10 @@ class DeltaFAlgorithm(Algorithm):
         will be converting from.
     """
 
-    def __init__(self, informat=None):
+    def __init__(self, informat='obs'):
         Algorithm.__init__(self, inchannels=CHANNELS[informat],
                 outchannels=['G'])
-        self.informat = informat
+        self._informat = informat
 
     def check_stream(self, timeseries):
         """checks a stream to make certain all the required channels
@@ -61,10 +61,35 @@ class DeltaFAlgorithm(Algorithm):
         """
         self.check_stream(timeseries)
         out_stream = None
-
-        if self.informat == 'geo':
+        informat = self._informat
+        if informat == 'geo':
             out_stream = StreamConverter.get_deltaf_from_geo(timeseries)
-        elif self.informat == 'obs' or self.informat == 'obsd':
+        elif informat == 'obs' or informat == 'obsd':
             out_stream = StreamConverter.get_deltaf_from_obs(timeseries)
 
         return out_stream
+
+    @classmethod
+    def add_arguments(cls, parser):
+        """Add command line arguments to argparse parser.
+
+        Parameters
+        ----------
+        parser: ArgumentParser
+            command line argument parser
+        """
+        parser.add_argument('--deltaf-from',
+                choices=['geo', 'obs', 'obsd'],
+                default='obs',
+                help='Geomagnetic orientation to read from')
+
+    def configure(self, arguments):
+        """Configure algorithm using comand line arguments.
+
+        Parameters
+        ----------
+        arguments: Namespace
+            parsed command line arguments
+        """
+        self._informat = arguments.deltaf_from
+        self._inchannels = CHANNELS[self._informat]
diff --git a/geomagio/XYZAlgorithm.py b/geomagio/algorithm/XYZAlgorithm.py
similarity index 62%
rename from geomagio/XYZAlgorithm.py
rename to geomagio/algorithm/XYZAlgorithm.py
index 12c3a1e0cc195558843a0decc8443d2a214faac6..64d1e91587b64f396125bdb98327c5cb7f2c74c2 100644
--- a/geomagio/XYZAlgorithm.py
+++ b/geomagio/algorithm/XYZAlgorithm.py
@@ -5,7 +5,7 @@
 
 from Algorithm import Algorithm
 from AlgorithmException import AlgorithmException
-import StreamConverter as StreamConverter
+from .. import StreamConverter
 
 # List of channels by geomagnetic observatory orientation.
 # geo represents a geographic north/south orientation
@@ -33,11 +33,11 @@ class XYZAlgorithm(Algorithm):
         be converting to.
     """
 
-    def __init__(self, informat=None, outformat=None):
+    def __init__(self, informat='obs', outformat='geo'):
         Algorithm.__init__(self, inchannels=CHANNELS[informat],
                 outchannels=CHANNELS[outformat])
-        self.informat = informat
-        self.outformat = outformat
+        self._informat = informat
+        self._outformat = outformat
 
     def check_stream(self, timeseries):
         """checks an stream to make certain all the required channels
@@ -69,36 +69,69 @@ class XYZAlgorithm(Algorithm):
         """
         self.check_stream(timeseries)
         out_stream = None
-        if self.outformat == 'geo':
-            if self.informat == 'geo':
+        informat = self._informat
+        outformat = self._outformat
+        if outformat == 'geo':
+            if informat == 'geo':
                 out_stream = timeseries
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = StreamConverter.get_geo_from_mag(timeseries)
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_geo_from_obs(timeseries)
-        elif self.outformat == 'mag':
-            if self.informat == 'geo':
+        elif outformat == 'mag':
+            if informat == 'geo':
                 out_stream = StreamConverter.get_mag_from_geo(timeseries)
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = timeseries
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_mag_from_obs(timeseries)
-        elif self.outformat == 'obs':
-            if self.informat == 'geo':
+        elif outformat == 'obs':
+            if informat == 'geo':
                 out_stream = StreamConverter.get_obs_from_geo(timeseries)
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = StreamConverter.get_obs_from_mag(timeseries)
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_obs_from_obs(timeseries,
                         include_e=True)
-        elif self.outformat == 'obsd':
-            if self.informat == 'geo':
+        elif outformat == 'obsd':
+            if informat == 'geo':
                 out_stream = StreamConverter.get_obs_from_geo(timeseries,
                         include_d=True)
-            elif self.informat == 'mag':
+            elif informat == 'mag':
                 out_stream = StreamConverter.get_obs_from_mag(timeseries,
                         include_d=True)
-            elif self.informat == 'obs' or self.informat == 'obsd':
+            elif informat == 'obs' or informat == 'obsd':
                 out_stream = StreamConverter.get_obs_from_obs(timeseries,
                         include_d=True)
         return out_stream
+
+    @classmethod
+    def add_arguments(cls, parser):
+        """Add command line arguments to argparse parser.
+
+        Parameters
+        ----------
+        parser: ArgumentParser
+            command line argument parser
+        """
+        parser.add_argument('--xyz-from',
+                choices=['geo', 'mag', 'obs', 'obsd'],
+                default='obs',
+                help='Geomagnetic orientation to read from')
+        parser.add_argument('--xyz-to',
+                choices=['geo', 'mag', 'obs', 'obsd'],
+                default='geo',
+                help='Geomagnetic orientation to convert to')
+
+    def configure(self, arguments):
+        """Configure algorithm using comand line arguments.
+
+        Parameters
+        ----------
+        arguments: Namespace
+            parsed command line arguments
+        """
+        self._informat = arguments.xyz_from
+        self._outformat = arguments.xyz_to
+        self._inchannels = CHANNELS[self._informat]
+        self._outchannels = CHANNELS[self._outformat]
diff --git a/geomagio/algorithm/__init__.py b/geomagio/algorithm/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..caaabb0c7a2ab733eb7cd9b3405a27aa00a2ecd3
--- /dev/null
+++ b/geomagio/algorithm/__init__.py
@@ -0,0 +1,28 @@
+"""
+Geomag Algorithms module
+"""
+
+# base classes
+from Algorithm import Algorithm
+from AlgorithmException import AlgorithmException
+# algorithms
+from DeltaFAlgorithm import DeltaFAlgorithm
+from XYZAlgorithm import XYZAlgorithm
+
+
+# algorithms is used by Controller to auto generate arguments
+algorithms = {
+    'identity': Algorithm,
+    'deltaf': DeltaFAlgorithm,
+    'xyz': XYZAlgorithm
+}
+
+
+__all__ = [
+    # base classes
+    'Algorithm',
+    'AlgorithmException',
+    # algorithms
+    'DeltaFAlgorithm',
+    'XYZAlgorithm'
+]
diff --git a/setup.py b/setup.py
index 0257b0d1df189943db32736561b7b92692d60446..84ef2911c87c6b82a32c55c9aaa286b5052151c1 100644
--- a/setup.py
+++ b/setup.py
@@ -7,6 +7,7 @@ setup(
     url='https://github.com/usgs/geomag-algorithms',
     packages=[
         'geomagio',
+        'geomagio.algorithm',
         'geomagio.iaga2002',
         'geomagio.imfv283',
         'geomagio.edge',
diff --git a/test/Controller_test.py b/test/Controller_test.py
index e407a9ecaa9463508a72b8cdea09fc083842e542..4b1992c98daa32abac7c47ffa35f9c806f773465 100644
--- a/test/Controller_test.py
+++ b/test/Controller_test.py
@@ -1,5 +1,6 @@
 #! /usr/bin/env python
-from geomagio import Algorithm, Controller, TimeseriesFactory
+from geomagio import Controller, TimeseriesFactory
+from geomagio.algorithm import Algorithm
 from nose.tools import assert_is_instance
 
 
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/Algorithm_test.py b/test/algorithm_test/Algorithm_test.py
similarity index 95%
rename from test/Algorithm_test.py
rename to test/algorithm_test/Algorithm_test.py
index cf4d2e3cbf249b8a50b4b75068abcb28c881a9ae..8ce878f1dd34cd6561cd9010d120d0d242c614c7 100644
--- a/test/Algorithm_test.py
+++ b/test/algorithm_test/Algorithm_test.py
@@ -2,7 +2,7 @@
 from obspy.core.stream import Stream
 from nose.tools import assert_equals
 from nose.tools import assert_is_instance
-from geomagio import Algorithm
+from geomagio.algorithm import Algorithm
 
 
 def test_algorithm_process():
diff --git a/test/XYZAlgorithm_test.py b/test/algorithm_test/XYZAlgorithm_test.py
similarity index 92%
rename from test/XYZAlgorithm_test.py
rename to test/algorithm_test/XYZAlgorithm_test.py
index b84f87198174e311879f46789c64a792d1dbdbbb..0ffcfb3ec543c6c1d8875c76798473bc4906a97c 100644
--- a/test/XYZAlgorithm_test.py
+++ b/test/algorithm_test/XYZAlgorithm_test.py
@@ -2,8 +2,8 @@
 from obspy.core.stream import Stream
 from nose.tools import assert_equals
 from nose.tools import assert_is
-from geomagio import XYZAlgorithm
-from StreamConverter_test import __create_trace
+from geomagio.algorithm import XYZAlgorithm
+from ..StreamConverter_test import __create_trace
 
 
 def test_xyzalgorithm_process():
diff --git a/test/algorithm_test/__init__.py b/test/algorithm_test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/edge_test/__init__.py b/test/edge_test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/iaga2002_test/__init__.py b/test/iaga2002_test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/imfv283_test/__init__.py b/test/imfv283_test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/pcdcp_test/__init__.py b/test/pcdcp_test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391