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
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
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))
I will leave the rest to your imagination but I like that Python makes this so easy. Thank you Guido!