class Timeseries::Series

Series - a time-ordered array of values

equations from here: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages

Public Class Methods

new(*args) click to toggle source
Calls superclass method
# File lib/timeseries/series.rb, line 7
def initialize *args
        super
        each { |item| raise ArgumentError , 'Invalid item added to Series, must be Timeseries::DataPoint' unless item.class == Timeseries::DataPoint }
end

Public Instance Methods

daily() click to toggle source

Generates a daily summation of the series

@return [Timeseries::Series] a daily version of the current series

# File lib/timeseries/series.rb, line 72
def daily
        grouped = group_by { |dp| dp.date.jd }
        daily_series = grouped.map { |k,v| Timeseries::DataPoint.new( v[0].date , v.inject(0) { |sum,c| sum.to_f + c.value.to_f } )  }
        Timeseries::Series.new( daily_series )
end
daily!() click to toggle source

Modifies the existing series to become a daily series

KEEP IN MIND: This will overwrite existing values if you have multiple DataPoints for individual days

@return [Timeseries::Series] self

# File lib/timeseries/series.rb, line 82
def daily!
        self.replace( daily )
end
exponential_moving_average(time_period = 7) click to toggle source

Generates time series where the DataPoint object values are exponential moving averages

@return [TimeSeries::Series] exponential moving average series

# File lib/timeseries/series.rb, line 44
def exponential_moving_average time_period = 7
        averages = self.each_with_index.map do |dp,i|
                Timeseries::DataPoint.new( dp.date , ema( time_period , i ) )
        end
        Timeseries::Series.new( averages )
end
method_missing(name , *args) click to toggle source

Sorting methods

Calls superclass method
# File lib/timeseries/series.rb, line 52
def method_missing(name , *args)
        # sort DataPoints ascending or descending based on attribute (date or value)
        if /^sort_by_(\w+)_([^!]+)(!?)$/.match(name)
                sort_field = $1.to_sym
                sort_dir = $2
                bang = $3
                return Timeseries::Series.new( self.send("sort#{bang}".to_sym) { |a,b| a.send(sort_field) <=> b.send(sort_field) } ) if sort_dir =~ /^asc/
                return Timeseries::Series.new( self.send("sort#{bang}".to_sym) { |a,b| b.send(sort_field) <=> a.send(sort_field) } ) if sort_dir =~ /^desc/
        elsif /^([^_]+)_day_((?:exponential_)?moving_average)$/.match(name)
                case $1
                when 'seven'
                        return send($2.to_sym , 7)
                end
        end
        super
end
moving_average(time_period = 7) click to toggle source

Generates time series where the DataPoint object values are moving averages

@return [Timeseries::Series] moving average series

# File lib/timeseries/series.rb, line 24
def moving_average time_period = 7
        moving_averages = zero_empty_dates.each_with_index.map do |c,i|
                dp = DataPoint.new 
                dp.value = single_day_moving_average( i , time_period )
                dp.date = c.date
                dp
        end
        Timeseries::Series.new( moving_averages )
end
single_day_moving_average(offset = 0 , time_period = 7) click to toggle source

Gets the moving average for a single day in the series

@return [Float] moving average

# File lib/timeseries/series.rb, line 37
def single_day_moving_average offset = 0 , time_period = 7
        self.sum_range( offset , time_period ) / time_period.to_f
end
sum() click to toggle source
# File lib/timeseries/series.rb, line 17
def sum 
        inject(0) { |total,dp| total.to_f + dp.value.to_f }
end
sum_range(offset = 0 , time_period = 7) click to toggle source

Returns

# File lib/timeseries/series.rb, line 13
def sum_range offset = 0 , time_period = 7 
        self[offset,time_period].inject(0) { |total,dp| total.to_f + dp.value.to_f }
end
zero_empty_dates() click to toggle source

Fills in 0 for any empty dates

@return [Timeseries::Series] an 0 filled time series

# File lib/timeseries/series.rb, line 89
def zero_empty_dates
        incomplete_series = daily.sort_by_date_descending!

        daily_series = Timeseries::Series.new

        return daily_series if incomplete_series.length < 2

        ( incomplete_series.last.date.jd .. incomplete_series.first.date.jd ).map do |jd_date|
                match = incomplete_series.find { |dp| dp.date.jd == jd_date } 
                value = ( match == nil ) ? 0 : match.value
                date = DateTime.jd( jd_date ).midnight
                daily_series.unshift( Timeseries::DataPoint.new( date , value ) )
        end

        daily_series
end

Private Instance Methods

data_points_for_jd_day(day) click to toggle source
# File lib/timeseries/series.rb, line 122
def data_points_for_jd_day day
        find_all { |dp| dp.date.jd == day }
end
ema(time_period = 7 , offset = 0) click to toggle source
# File lib/timeseries/series.rb, line 107
def ema time_period = 7 , offset = 0
        # equation:
        #  Multiplier: (2 / (Time periods + 1) ) = (2 / (10 + 1) ) = 0.1818 (18.18%)
        #  EMA: {value - EMA(previous day)} x multiplier + EMA(previous day).
        return nil if offset > ( self.length - time_period )

        # Exponential moving average always uses simple moving average as first value
        return single_day_moving_average( offset , time_period ) if offset == ( self.length - time_period )

        # generate the ema for the given day
        previous_day = offset + 1
        yesterday_ema = ema( time_period , previous_day )
        ( self[offset].value - yesterday_ema ) * exponential_multiplier( time_period ) + yesterday_ema
end
exponential_multiplier(time_period = 7) click to toggle source
# File lib/timeseries/series.rb, line 126
def exponential_multiplier time_period = 7
        ( 2 / ( time_period.to_f + 1.0 ) )
end