Ring, Field and Series is more of a design pattern then it is a package. I use this pattern for all of my time series work. These examples are trivial and there is very little code in the three modules. So take a look at how they works:

https://github.com/leonhardbrenner/buckysoap/blob/master/src/buckysoap

ring.py - this is the fixer or root
field.py - let's us factor our code into smaller components
series.py - works with ring to give use indexing of time

In the example below we make up a timeline(dates). You should already understand properties(x, y, z) but what is interesting here is:

b = property(B) #look at Field.__getattr__

This let's us access self.date which is actually a property of an instance of A. Actually, all properties of A are available to B including horizons. A.series(horizon=(-1, 21)) returns a Series object which is bound to and will create instances of A for each of the dates in the timeseries. We can index the series. We can also index each ring to access a ring relative to the current ring.

If this sounds complicated then just look at the examples:


In [38]:
import buckysoap as bs

class A(bs.Ring):

    dates = [str(x) for x in bs.arange(10) + 20100101]

    @property
    def x(self):
        return 'x[%s]' % self.date
    
    class B(bs.Field):

        @property
        def y(self):
            return 'y[%s]' % self.date
            
        @property
        def z(self):
            return '(%s + %s)' % (self.x, self.y)
        
    b = property(B)
        
series = A.series(horizons=(-1, 21))
print series['20100107'].b.z
for o in series:
    print o.date, o.x, o.b.y, o.b.z


(x[20100107] + y[20100107])
20100101 x[20100101] y[20100101] (x[20100101] + y[20100101])
20100102 x[20100102] y[20100102] (x[20100102] + y[20100102])
20100103 x[20100103] y[20100103] (x[20100103] + y[20100103])
20100104 x[20100104] y[20100104] (x[20100104] + y[20100104])
20100105 x[20100105] y[20100105] (x[20100105] + y[20100105])
20100106 x[20100106] y[20100106] (x[20100106] + y[20100106])
20100107 x[20100107] y[20100107] (x[20100107] + y[20100107])
20100108 x[20100108] y[20100108] (x[20100108] + y[20100108])
20100109 x[20100109] y[20100109] (x[20100109] + y[20100109])
20100110 x[20100110] y[20100110] (x[20100110] + y[20100110])

In this example we are going to replace property y in class B. This is why I use the convention uppercase for the class and lowercase for the property. Here the difference in the code is trivial y becomes y`.


In [39]:
class A(A):
    
    class B(A.B):

        @property
        def y(self):
            return 'y[%s]`' % self.date
    
    b = property(B)

series = A.series(horizons=(-1, 43))
print series['20100108'].b.z
for o in series:
    print o.date, o.x, o.b.y, o.b.z


(x[20100108] + y[20100108]`)
20100101 x[20100101] y[20100101]` (x[20100101] + y[20100101]`)
20100102 x[20100102] y[20100102]` (x[20100102] + y[20100102]`)
20100103 x[20100103] y[20100103]` (x[20100103] + y[20100103]`)
20100104 x[20100104] y[20100104]` (x[20100104] + y[20100104]`)
20100105 x[20100105] y[20100105]` (x[20100105] + y[20100105]`)
20100106 x[20100106] y[20100106]` (x[20100106] + y[20100106]`)
20100107 x[20100107] y[20100107]` (x[20100107] + y[20100107]`)
20100108 x[20100108] y[20100108]` (x[20100108] + y[20100108]`)
20100109 x[20100109] y[20100109]` (x[20100109] + y[20100109]`)
20100110 x[20100110] y[20100110]` (x[20100110] + y[20100110]`)

Now we are going to use the horizons. We create a method change which takes a callable(func). As we iterate through the series we call change passing the lambda which change will use to curry the Ring(s) found at the horizons of the current Ring. Typically, I use this with Atom and Element allowing me to calculate the change in the cross section.


In [49]:
class A(A):
    
    dates = [str(x) for x in bs.arange(100) + 20100101]

    class B(A.B):

        def change(self, func):
            ring = self.ring
            horizons = ring.horizons
            values = [func(ring[x]) for x in horizons]
            return ((values[1] / values[0]) - 1) * 100
    
    b = property(B)

series = A.series(horizons=(-1, 2))
print series['20100108'].b.z
for o in series:
    if o.date < '20100153':
        print o.date, o.b.change(lambda x: float(x.date))


 (20100200 + y[20100108]`)
20100101 -0.000482582262862
20100102 1.49252981307e-05
20100103 1.4925297398e-05
20100104 1.4925296643e-05
20100105 1.49252959103e-05
20100106 1.49252951553e-05
20100107 1.49252944226e-05
20100108 1.49252936898e-05
20100109 1.49252929349e-05
20100110 1.49252922022e-05
20100111 1.49252914472e-05
20100112 1.49252907145e-05
20100113 1.49252899595e-05
20100114 1.49252892268e-05
20100115 1.4925288494e-05
20100116 1.49252877391e-05
20100117 1.49252870063e-05
20100118 1.49252862514e-05
20100119 1.49252855186e-05
20100120 1.49252847637e-05
20100121 1.49252840309e-05
20100122 1.4925283276e-05
20100123 1.49252825432e-05
20100124 1.49252818105e-05
20100125 1.49252810555e-05
20100126 1.49252803228e-05
20100127 1.49252795678e-05
20100128 1.49252788351e-05
20100129 1.49252780801e-05
20100130 1.49252773474e-05
20100131 1.49252765924e-05
20100132 1.49252758597e-05
20100133 1.49252751269e-05
20100134 1.4925274372e-05
20100135 1.49252736392e-05
20100136 1.49252728843e-05
20100137 1.49252721515e-05
20100138 1.49252713966e-05
20100139 1.49252706638e-05
20100140 1.49252699089e-05
20100141 1.49252691761e-05
20100142 1.49252684434e-05
20100143 1.49252676884e-05
20100144 1.49252669557e-05
20100145 1.49252662007e-05
20100146 1.4925265468e-05
20100147 1.4925264713e-05
20100148 1.49252639803e-05
20100149 1.49252632475e-05
20100150 1.49252624926e-05
20100151 1.49252617598e-05
20100152 1.49252610049e-05

I will leave the rest to your imagination but I like that Python makes this so easy. Thank you Guido!