quantitative 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/quant/dominant_cycles_source.rb +1 -32
- data/lib/quant/indicators/adx.rb +2 -5
- data/lib/quant/indicators/atr.rb +2 -0
- data/lib/quant/indicators/cci.rb +2 -0
- data/lib/quant/indicators/decycler.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/acr.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/band_pass.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/differential.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/half_period.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/homodyne.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +2 -0
- data/lib/quant/indicators/ema.rb +67 -0
- data/lib/quant/indicators/frama.rb +1 -0
- data/lib/quant/indicators/indicator.rb +17 -57
- data/lib/quant/indicators/indicator_point.rb +11 -0
- data/lib/quant/indicators/mama.rb +2 -0
- data/lib/quant/indicators/mesa.rb +3 -4
- data/lib/quant/indicators/ping.rb +2 -0
- data/lib/quant/indicators/pivots/atr.rb +11 -16
- data/lib/quant/indicators/pivots/bollinger.rb +10 -25
- data/lib/quant/indicators/pivots/camarilla.rb +30 -31
- data/lib/quant/indicators/pivots/classic.rb +3 -1
- data/lib/quant/indicators/pivots/demark.rb +5 -3
- data/lib/quant/indicators/pivots/donchian.rb +20 -23
- data/lib/quant/indicators/pivots/fibbonacci.rb +12 -9
- data/lib/quant/indicators/pivots/guppy.rb +3 -1
- data/lib/quant/indicators/pivots/keltner.rb +11 -19
- data/lib/quant/indicators/pivots/murrey.rb +8 -13
- data/lib/quant/indicators/pivots/pivot.rb +109 -0
- data/lib/quant/indicators/pivots/traditional.rb +2 -0
- data/lib/quant/indicators/pivots/woodie.rb +2 -0
- data/lib/quant/indicators/rocket_rsi.rb +57 -0
- data/lib/quant/indicators/roofing.rb +59 -0
- data/lib/quant/indicators/rsi.rb +67 -0
- data/lib/quant/indicators/snr.rb +64 -0
- data/lib/quant/indicators_registry.rb +63 -0
- data/lib/quant/indicators_source.rb +14 -9
- data/lib/quant/pivots_source.rb +1 -13
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +10 -3
- metadata +9 -3
- data/lib/quant/indicators/pivot.rb +0 -107
| @@ -4,35 +4,32 @@ module Quant | |
| 4 4 | 
             
              module Indicators
         | 
| 5 5 | 
             
                module Pivots
         | 
| 6 6 | 
             
                  class Donchian < Pivot
         | 
| 7 | 
            -
                     | 
| 7 | 
            +
                    register name: :donchian
         | 
| 8 8 |  | 
| 9 | 
            -
                    def  | 
| 10 | 
            -
             | 
| 11 | 
            -
                     | 
| 12 | 
            -
             | 
| 13 | 
            -
                    def st_highs; @st_highs ||= [].max_size!(st_period) end
         | 
| 14 | 
            -
                    def st_lows; @st_lows ||= [].max_size!(st_period) end
         | 
| 15 | 
            -
                    def mt_highs; @mt_highs ||= [].max_size!(mt_period) end
         | 
| 16 | 
            -
                    def mt_lows; @mt_lows ||= [].max_size!(mt_period) end
         | 
| 17 | 
            -
                    def lt_highs; @lt_highs ||= [].max_size!(lt_period) end
         | 
| 18 | 
            -
                    def lt_lows; @lt_lows ||= [].max_size!(lt_period) end
         | 
| 9 | 
            +
                    def compute_midpoint
         | 
| 10 | 
            +
                      p0.midpoint = (p0.high_price + p0.low_price) * 0.5
         | 
| 11 | 
            +
                    end
         | 
| 19 12 |  | 
| 20 13 | 
             
                    def compute_bands
         | 
| 21 | 
            -
                       | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                       | 
| 25 | 
            -
                      lt_highs << p0.high_price
         | 
| 26 | 
            -
                      lt_lows << p0.low_price
         | 
| 14 | 
            +
                      period_points(micro_period).tap do |period_points|
         | 
| 15 | 
            +
                        p0.l1 = period_points.map(&:low_price).min
         | 
| 16 | 
            +
                        p0.h1 = period_points.map(&:high_price).max
         | 
| 17 | 
            +
                      end
         | 
| 27 18 |  | 
| 28 | 
            -
                       | 
| 29 | 
            -
             | 
| 19 | 
            +
                      period_points(min_period).tap do |period_points|
         | 
| 20 | 
            +
                        p0.l2 = period_points.map(&:low_price).min
         | 
| 21 | 
            +
                        p0.h2 = period_points.map(&:high_price).max
         | 
| 22 | 
            +
                      end
         | 
| 30 23 |  | 
| 31 | 
            -
                       | 
| 32 | 
            -
             | 
| 24 | 
            +
                      period_points(half_period).tap do |period_points|
         | 
| 25 | 
            +
                        p0.l3 = period_points.map(&:low_price).min
         | 
| 26 | 
            +
                        p0.h3 = period_points.map(&:high_price).max
         | 
| 27 | 
            +
                      end
         | 
| 33 28 |  | 
| 34 | 
            -
                       | 
| 35 | 
            -
             | 
| 29 | 
            +
                      period_points(max_period).tap do |period_points|
         | 
| 30 | 
            +
                        p0.l4 = period_points.map(&:low_price).min
         | 
| 31 | 
            +
                        p0.h4 = period_points.map(&:high_price).max
         | 
| 32 | 
            +
                      end
         | 
| 36 33 | 
             
                    end
         | 
| 37 34 | 
             
                  end
         | 
| 38 35 | 
             
                end
         | 
| @@ -2,18 +2,21 @@ module Quant | |
| 2 2 | 
             
              module Indicators
         | 
| 3 3 | 
             
                module Pivots
         | 
| 4 4 | 
             
                  class Fibbonacci < Pivot
         | 
| 5 | 
            -
                     | 
| 6 | 
            -
                      half_period
         | 
| 7 | 
            -
                    end
         | 
| 5 | 
            +
                    register name: :fibbonacci
         | 
| 8 6 |  | 
| 9 | 
            -
                     | 
| 10 | 
            -
                      [0.146, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0, 1.146]
         | 
| 11 | 
            -
                    end
         | 
| 7 | 
            +
                    FIBBONACCI_SERIES = [0.146, 0.236, 0.382, 0.5, 0.618, 0.786, 1.0, 1.146].freeze
         | 
| 12 8 |  | 
| 13 9 | 
             
                    def compute_bands
         | 
| 14 | 
            -
                       | 
| 15 | 
            -
                         | 
| 16 | 
            -
                         | 
| 10 | 
            +
                      period_points(adaptive_period).tap do |period_points|
         | 
| 11 | 
            +
                        highest = period_points.map(&:high_price).max
         | 
| 12 | 
            +
                        lowest = period_points.map(&:low_price).min
         | 
| 13 | 
            +
                        p0.range = highest - lowest
         | 
| 14 | 
            +
                        p0.midpoint = (highest + lowest) * 0.5
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                      FIBBONACCI_SERIES.each_with_index do |ratio, index|
         | 
| 18 | 
            +
                        p0[index + 1] = p0.midpoint + ratio * p0.range
         | 
| 19 | 
            +
                        p0[-index - 1] = p0.midpoint - ratio * p0.range
         | 
| 17 20 | 
             
                      end
         | 
| 18 21 | 
             
                    end
         | 
| 19 22 | 
             
                  end
         | 
| @@ -2,6 +2,8 @@ module Quant | |
| 2 2 | 
             
              module Indicators
         | 
| 3 3 | 
             
                module Pivots
         | 
| 4 4 | 
             
                  class Guppy < Pivot
         | 
| 5 | 
            +
                    register name: :guppy
         | 
| 6 | 
            +
             | 
| 5 7 | 
             
                    def guppy_ema(period, band)
         | 
| 6 8 | 
             
                      return p0.input unless p1[band]
         | 
| 7 9 |  | 
| @@ -15,7 +17,7 @@ module Quant | |
| 15 17 |  | 
| 16 18 | 
             
                    # The short-term MAs are typically set at 3, 5, 8, 10, 12, and 15 periods. The
         | 
| 17 19 | 
             
                    # longer-term MAs are typically set at 30, 35, 40, 45, 50, and 60.
         | 
| 18 | 
            -
                    def  | 
| 20 | 
            +
                    def compute_bands
         | 
| 19 21 | 
             
                      p0[1] = guppy_ema(5,  1)
         | 
| 20 22 | 
             
                      p0[2] = guppy_ema(8,  2)
         | 
| 21 23 | 
             
                      p0[3] = guppy_ema(10, 3)
         | 
| @@ -2,6 +2,7 @@ module Quant | |
| 2 2 | 
             
              module Indicators
         | 
| 3 3 | 
             
                module Pivots
         | 
| 4 4 | 
             
                  class Keltner < Pivot
         | 
| 5 | 
            +
                    register name: :keltner
         | 
| 5 6 | 
             
                    depends_on Indicators::Atr
         | 
| 6 7 |  | 
| 7 8 | 
             
                    def atr_point
         | 
| @@ -9,33 +10,24 @@ module Quant | |
| 9 10 | 
             
                    end
         | 
| 10 11 |  | 
| 11 12 | 
             
                    def scale
         | 
| 12 | 
            -
                       | 
| 13 | 
            -
                    end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                    def alpha
         | 
| 16 | 
            -
                      bars_to_alpha(min_period)
         | 
| 13 | 
            +
                      3.0
         | 
| 17 14 | 
             
                    end
         | 
| 18 15 |  | 
| 19 16 | 
             
                    def compute_midpoint
         | 
| 17 | 
            +
                      alpha = bars_to_alpha(min_period)
         | 
| 20 18 | 
             
                      p0.midpoint = alpha * p0.input + (1 - alpha) * p1.midpoint
         | 
| 21 19 | 
             
                    end
         | 
| 22 20 |  | 
| 23 | 
            -
                     | 
| 24 | 
            -
                      atr_value = atr_point.slow * scale
         | 
| 21 | 
            +
                    KELTNER_SERIES = [0.236, 0.382, 0.500, 0.618, 0.786, 1.0].freeze
         | 
| 25 22 |  | 
| 26 | 
            -
             | 
| 27 | 
            -
                       | 
| 28 | 
            -
                      p0.h4 = p0.midpoint + 0.618 * atr_value
         | 
| 29 | 
            -
                      p0.h3 = p0.midpoint + 0.500 * atr_value
         | 
| 30 | 
            -
                      p0.h2 = p0.midpoint + 0.382 * atr_value
         | 
| 31 | 
            -
                      p0.h1 = p0.midpoint + 0.236 * atr_value
         | 
| 23 | 
            +
                    def compute_bands
         | 
| 24 | 
            +
                      atr_value = atr_point.value * scale
         | 
| 32 25 |  | 
| 33 | 
            -
                       | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
                       | 
| 38 | 
            -
                      p0.l6 = p0.midpoint - 1.000 * atr_value
         | 
| 26 | 
            +
                      KELTNER_SERIES.each_with_index do |ratio, index|
         | 
| 27 | 
            +
                        offset = ratio * atr_value
         | 
| 28 | 
            +
                        p0[index + 1] = p0.midpoint + offset
         | 
| 29 | 
            +
                        p0[-index - 1] = p0.midpoint - offset
         | 
| 30 | 
            +
                      end
         | 
| 39 31 | 
             
                    end
         | 
| 40 32 | 
             
                  end
         | 
| 41 33 | 
             
                end
         | 
| @@ -3,6 +3,8 @@ module Quant | |
| 3 3 | 
             
              module Indicators
         | 
| 4 4 | 
             
                module Pivots
         | 
| 5 5 | 
             
                  class Murrey < Pivot
         | 
| 6 | 
            +
                    register name: :murrey
         | 
| 7 | 
            +
             | 
| 6 8 | 
             
                    def multiplier
         | 
| 7 9 | 
             
                      0.125
         | 
| 8 10 | 
             
                    end
         | 
| @@ -12,20 +14,13 @@ module Quant | |
| 12 14 | 
             
                      p0.midpoint = p0.lowest + (p0.input * 4.0)
         | 
| 13 15 | 
             
                    end
         | 
| 14 16 |  | 
| 15 | 
            -
                     | 
| 16 | 
            -
                      p0.h6 = p0.midpoint + p0.input * 6.0
         | 
| 17 | 
            -
                      p0.h5 = p0.midpoint + p0.input * 5.0
         | 
| 18 | 
            -
                      p0.h4 = p0.midpoint + p0.input * 4.0
         | 
| 19 | 
            -
                      p0.h3 = p0.midpoint + p0.input * 3.0
         | 
| 20 | 
            -
                      p0.h2 = p0.midpoint + p0.input * 2.0
         | 
| 21 | 
            -
                      p0.h1 = p0.midpoint + p0.input * 1.0
         | 
| 17 | 
            +
                    MURREY_SERIES = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0].freeze
         | 
| 22 18 |  | 
| 23 | 
            -
             | 
| 24 | 
            -
                       | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
                       | 
| 28 | 
            -
                      p0.l6 = p0.midpoint - p0.input * 6.0
         | 
| 19 | 
            +
                    def compute_bands
         | 
| 20 | 
            +
                      MURREY_SERIES.each_with_index do |ratio, index|
         | 
| 21 | 
            +
                        p0[index + 1] = p0.midpoint + p0.input * ratio
         | 
| 22 | 
            +
                        p0[-index - 1] = p0.midpoint - p0.input * ratio
         | 
| 23 | 
            +
                      end
         | 
| 29 24 | 
             
                    end
         | 
| 30 25 | 
             
                  end
         | 
| 31 26 | 
             
                end
         | 
| @@ -0,0 +1,109 @@ | |
| 1 | 
            +
            module Quant
         | 
| 2 | 
            +
              module Indicators
         | 
| 3 | 
            +
                module Pivots
         | 
| 4 | 
            +
                  class PivotPoint < IndicatorPoint
         | 
| 5 | 
            +
                    attribute :avg_high, default: :high_price
         | 
| 6 | 
            +
                    attribute :highest, default: :input
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    attribute :avg_low, default: :low_price
         | 
| 9 | 
            +
                    attribute :lowest, default: :input
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    attribute :range, default: 0.0
         | 
| 12 | 
            +
                    attribute :avg_range, default: 0.0
         | 
| 13 | 
            +
                    attribute :std_dev, default: 0.0
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def bands
         | 
| 16 | 
            +
                      @bands ||= { 0 => input }
         | 
| 17 | 
            +
                    end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    def [](band)
         | 
| 20 | 
            +
                      bands[band]
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    def []=(band, value)
         | 
| 24 | 
            +
                      bands[band] = value
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    def key?(band)
         | 
| 28 | 
            +
                      bands.key?(band)
         | 
| 29 | 
            +
                    end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    def midpoint
         | 
| 32 | 
            +
                      bands[0]
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                    alias :h0 :midpoint
         | 
| 35 | 
            +
                    alias :l0 :midpoint
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    def midpoint=(value)
         | 
| 38 | 
            +
                      bands[0] = value
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                    alias :h0= :midpoint=
         | 
| 41 | 
            +
                    alias :l0= :midpoint=
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    (1..8).each do |band|
         | 
| 44 | 
            +
                      define_method("h#{band}") { bands[band] }
         | 
| 45 | 
            +
                      define_method("h#{band}=") { |value| bands[band] = value }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                      define_method("l#{band}") { bands[-band] }
         | 
| 48 | 
            +
                      define_method("l#{band}=") { |value| bands[-band] = value }
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  class Pivot < Indicator
         | 
| 53 | 
            +
                    def points_class
         | 
| 54 | 
            +
                      Quant::Indicators::Pivots::PivotPoint
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    def band?(band)
         | 
| 58 | 
            +
                      p0.key?(band)
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    def period
         | 
| 62 | 
            +
                      adaptive_period
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    def averaging_period
         | 
| 66 | 
            +
                      min_period
         | 
| 67 | 
            +
                    end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    def period_midpoints
         | 
| 70 | 
            +
                      period_points(period).map(&:midpoint)
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def smoothed_average_midpoint
         | 
| 74 | 
            +
                      three_pole_super_smooth :input, previous: :midpoint, period: averaging_period
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    def compute
         | 
| 78 | 
            +
                      compute_extents
         | 
| 79 | 
            +
                      compute_value
         | 
| 80 | 
            +
                      compute_midpoint
         | 
| 81 | 
            +
                      compute_bands
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    def compute_midpoint
         | 
| 85 | 
            +
                      p0.midpoint = p0.input
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    def compute_value
         | 
| 89 | 
            +
                      # No-op -- override in subclasses
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    def compute_bands
         | 
| 93 | 
            +
                      # No-op -- override in subclasses
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    def compute_extents
         | 
| 97 | 
            +
                      period_midpoints.tap do |midpoints|
         | 
| 98 | 
            +
                        p0.highest = midpoints.max
         | 
| 99 | 
            +
                        p0.lowest = midpoints.min
         | 
| 100 | 
            +
                        p0.range = p0.high_price - p0.low_price
         | 
| 101 | 
            +
                        p0.avg_low = three_pole_super_smooth(:low_price, previous: :avg_low, period: averaging_period)
         | 
| 102 | 
            +
                        p0.avg_high = three_pole_super_smooth(:high_price, previous: :avg_high, period: averaging_period)
         | 
| 103 | 
            +
                        p0.avg_range = three_pole_super_smooth(:range, previous: :avg_range, period: averaging_period)
         | 
| 104 | 
            +
                      end
         | 
| 105 | 
            +
                    end
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
            end
         | 
| @@ -0,0 +1,57 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Quant
         | 
| 4 | 
            +
              module Indicators
         | 
| 5 | 
            +
                class RocketRsiPoint < IndicatorPoint
         | 
| 6 | 
            +
                  attribute :hp, default: 0.0
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  attribute :delta, default: 0.0
         | 
| 9 | 
            +
                  attribute :gain, default: 0.0
         | 
| 10 | 
            +
                  attribute :loss, default: 0.0
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  attribute :gains, default: 0.0
         | 
| 13 | 
            +
                  attribute :losses, default: 0.0
         | 
| 14 | 
            +
                  attribute :denom, default: 0.0
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  attribute :inst_rsi, default: 0.5
         | 
| 17 | 
            +
                  attribute :rsi, default: 0.0
         | 
| 18 | 
            +
                  attribute :crosses, default: false
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                class RocketRsi < Indicator
         | 
| 22 | 
            +
                  register name: :rocket_rsi
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def quarter_period
         | 
| 25 | 
            +
                    half_period / 2
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def half_period
         | 
| 29 | 
            +
                    (dc_period / 2) - 1
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def compute
         | 
| 33 | 
            +
                    p0.hp = two_pole_butterworth :input, previous: :hp, period: quarter_period
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    lp = p(half_period)
         | 
| 36 | 
            +
                    p0.delta = p0.hp - lp.hp
         | 
| 37 | 
            +
                    p0.delta > 0.0 ? p0.gain = p0.delta : p0.loss = p0.delta.abs
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    period_points(half_period).tap do |period_points|
         | 
| 40 | 
            +
                      p0.gains = period_points.map(&:gain).sum
         | 
| 41 | 
            +
                      p0.losses = period_points.map(&:loss).sum
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    p0.denom = p0.gains + p0.losses
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    if p0.denom.zero?
         | 
| 47 | 
            +
                      p0.inst_rsi = p1.inst_rsi
         | 
| 48 | 
            +
                      p0.rsi = p1.rsi
         | 
| 49 | 
            +
                    else
         | 
| 50 | 
            +
                      p0.inst_rsi = ((p0.gains - p0.losses) / p0.denom)
         | 
| 51 | 
            +
                      p0.rsi = fisher_transform(p0.inst_rsi).clamp(-1.0, 1.0)
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                    p0.crosses = (p0.rsi >= 0.0 && p1.rsi < 0.0) || (p0.rsi <= 0.0 && p1.rsi > 0.0)
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
              end
         | 
| 57 | 
            +
            end
         | 
| @@ -0,0 +1,59 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Quant
         | 
| 4 | 
            +
              module Indicators
         | 
| 5 | 
            +
                # The ideal time to buy is when the cycle is at a trough, and the ideal time to exit a long position or to
         | 
| 6 | 
            +
                # sell short is when the cycle is at a peak.These conditions are flagged by the filter crossing itself
         | 
| 7 | 
            +
                # delayed by two bars, and are included as part of the indicator.
         | 
| 8 | 
            +
                class RoofingPoint < IndicatorPoint
         | 
| 9 | 
            +
                  attribute :hp, default: 0.0
         | 
| 10 | 
            +
                  attribute :value, default: 0.0
         | 
| 11 | 
            +
                  attribute :peak, default: 0.0
         | 
| 12 | 
            +
                  attribute :agc, default: 0.0
         | 
| 13 | 
            +
                  attribute :direction, default: 0
         | 
| 14 | 
            +
                  attribute :turned, default: false
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                class Roofing < Indicator
         | 
| 18 | 
            +
                  register name: :roofing
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def low_pass_period
         | 
| 21 | 
            +
                    dc_period
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def high_pass_period
         | 
| 25 | 
            +
                    low_pass_period * 2
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # //Highpass filter cyclic components whose periods are shorter than 48 bars
         | 
| 29 | 
            +
                  # alpha1 = (Cosine(.707*360 / HPPeriod) + Sine (.707*360 / HPPeriod) - 1) / Cosine(.707*360 / HPPeriod);
         | 
| 30 | 
            +
                  # HP = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(Close - 2*Close[1] + Close[2]) + 2*(1 - alpha1)*HP[1] - (1 - alpha1)*
         | 
| 31 | 
            +
                  # (1 - alpha1)*HP[2];
         | 
| 32 | 
            +
                  # //Smooth with a Super Smoother Filter from equation 3-3
         | 
| 33 | 
            +
                  # a1 = expvalue(-1.414*3.14159 / LPPeriod);
         | 
| 34 | 
            +
                  # b1 = 2*a1*Cosine(1.414*180 / LPPeriod);
         | 
| 35 | 
            +
                  # c2 = b1;
         | 
| 36 | 
            +
                  # c3 = -a1*a1;
         | 
| 37 | 
            +
                  # c1 = 1 - c2 - c3;
         | 
| 38 | 
            +
                  # Filt = c1*(HP + HP[1]) / 2 + c2*Filt[1] + c3*Filt[2
         | 
| 39 | 
            +
                  def compute
         | 
| 40 | 
            +
                    a = Math.cos(0.707 * deg2rad(360) / high_pass_period)
         | 
| 41 | 
            +
                    b = Math.sin(0.707 * deg2rad(360) / high_pass_period)
         | 
| 42 | 
            +
                    alpha1 = (a + b - 1) / a
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    p0.hp = (1 - alpha1 / 2)**2 * (p0.input - 2 * p1.input + p2.input) + 2 * (1 - alpha1) * p1.hp - (1 - alpha1)**2 * p2.hp
         | 
| 45 | 
            +
                    a1 = Math.exp(-1.414 * Math::PI / low_pass_period)
         | 
| 46 | 
            +
                    c2 = 2 * a1 * Math.cos(1.414 * deg2rad(180) / low_pass_period)
         | 
| 47 | 
            +
                    c3 = -a1**2
         | 
| 48 | 
            +
                    c1 = 1 - c2 - c3
         | 
| 49 | 
            +
                    p0.value = c1 * (p0.hp + p1.hp) / 2 + c2 * p1.value + c3 * p2.value
         | 
| 50 | 
            +
                    p0.direction = p0.value > p2.value ? 1 : -1
         | 
| 51 | 
            +
                    p0.turned = p0.direction != p2.direction
         | 
| 52 | 
            +
                    # Peak = .991 * Peak[1];
         | 
| 53 | 
            +
                    # If AbsValue(BP) > Peak Then Peak = AbsValue(BP); If Peak <> 0 Then Signal = BP / Peak;
         | 
| 54 | 
            +
                    p0.peak = [p0.value.abs, 0.991 * p1.peak].max
         | 
| 55 | 
            +
                    p0.agc = p0.peak == 0 ? 0 : p0.value / p0.peak
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
            end
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Quant
         | 
| 4 | 
            +
              module Indicators
         | 
| 5 | 
            +
                class RsiPoint < IndicatorPoint
         | 
| 6 | 
            +
                  attribute :hp, default: 0.0
         | 
| 7 | 
            +
                  attribute :filter, default: 0.0
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  attribute :delta, default: 0.0
         | 
| 10 | 
            +
                  attribute :gain, default: 0.0
         | 
| 11 | 
            +
                  attribute :loss, default: 0.0
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  attribute :gains, default: 0.0
         | 
| 14 | 
            +
                  attribute :losses, default: 0.0
         | 
| 15 | 
            +
                  attribute :denom, default: 0.0
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  attribute :inst_rsi, default: 0.0
         | 
| 18 | 
            +
                  attribute :rsi, default: 0.0
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                # The Relative Strength Index (RSI) is a momentum oscillator that measures the
         | 
| 22 | 
            +
                # speed and change of price movements.  This RSI indicator is adaptive and
         | 
| 23 | 
            +
                # uses the half-period of the dominant cycle to calculate the RSI.
         | 
| 24 | 
            +
                # It is further smoothed by an exponential moving average of the last three bars
         | 
| 25 | 
            +
                # (or whatever the micro_period is set to).
         | 
| 26 | 
            +
                #
         | 
| 27 | 
            +
                # The RSI oscillates between 0 and 1.  Traditionally, and in this implementation,
         | 
| 28 | 
            +
                # the RSI is considered overbought when above 0.7 and oversold when below 0.3.
         | 
| 29 | 
            +
                class Rsi < Indicator
         | 
| 30 | 
            +
                  register name: :rsi
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def quarter_period
         | 
| 33 | 
            +
                    half_period / 2
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def half_period
         | 
| 37 | 
            +
                    (dc_period / 2) - 1
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def compute
         | 
| 41 | 
            +
                    # The High Pass filter is half the dominant cycle period while the
         | 
| 42 | 
            +
                    # Low Pass Filter (super smoother) is the quarter dominant cycle period.
         | 
| 43 | 
            +
                    p0.hp = high_pass_filter :input, period: half_period
         | 
| 44 | 
            +
                    p0.filter = ema :hp, previous: :filter, period: quarter_period
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    lp = p(half_period)
         | 
| 47 | 
            +
                    p0.delta = p0.filter - lp.filter
         | 
| 48 | 
            +
                    p0.delta > 0.0 ? p0.gain = p0.delta : p0.loss = p0.delta.abs
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    period_points(half_period).tap do |period_points|
         | 
| 51 | 
            +
                      p0.gains = period_points.map(&:gain).sum
         | 
| 52 | 
            +
                      p0.losses = period_points.map(&:loss).sum
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    p0.denom = p0.gains + p0.losses
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                    if p0.denom > 0.0
         | 
| 58 | 
            +
                      p0.inst_rsi = (p0.gains / p0.denom)
         | 
| 59 | 
            +
                      p0.rsi = ema :inst_rsi, previous: :rsi, period: micro_period
         | 
| 60 | 
            +
                    else
         | 
| 61 | 
            +
                      p0.inst_rsi = 0.5
         | 
| 62 | 
            +
                      p0.rsi = 0.5
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Quant
         | 
| 4 | 
            +
              module Indicators
         | 
| 5 | 
            +
                class SnrPoint < IndicatorPoint
         | 
| 6 | 
            +
                  attribute :smooth, default: 0.0
         | 
| 7 | 
            +
                  attribute :detrend, default: 0.0
         | 
| 8 | 
            +
                  attribute :i1, default: 0.0
         | 
| 9 | 
            +
                  attribute :q1, default: 0.0
         | 
| 10 | 
            +
                  attribute :noise, default: 0.0
         | 
| 11 | 
            +
                  attribute :signal, default: 0.0
         | 
| 12 | 
            +
                  attribute :ratio, default: 0.0
         | 
| 13 | 
            +
                  attribute :state, default: 0
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                class Snr < Indicator
         | 
| 17 | 
            +
                  register name: :snr
         | 
| 18 | 
            +
                  depends_on DominantCycles::Homodyne
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def homodyne_dominant_cycle
         | 
| 21 | 
            +
                    series.indicators[source].dominant_cycles.homodyne
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def current_dominant_cycle
         | 
| 25 | 
            +
                    homodyne_dominant_cycle.points[t0]
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def threshold
         | 
| 29 | 
            +
                    @threshold ||= 10 * Math.log(0.5)**2
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def compute_values
         | 
| 33 | 
            +
                    current_dominant_cycle.tap do |dc|
         | 
| 34 | 
            +
                      p0.i1 = dc.i1
         | 
| 35 | 
            +
                      p0.q1 = dc.q1
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def compute_noise
         | 
| 40 | 
            +
                    noise = (p0.input - p2.input).abs
         | 
| 41 | 
            +
                    p0.noise = p1.noise.zero? ? noise : (0.1 * noise) + (0.9 * p1.noise)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def compute_ratio
         | 
| 45 | 
            +
                    # p0.ratio = 0.25 * (10 * Math.log(p0.i1**2 + p0.q1**2) / Math.log(10)) + 0.75 * p1.ratio
         | 
| 46 | 
            +
                    # ratio = .25*(10 * Log(I1*I1 + Q1*Q1)/(Range*Range))/Log(10) + 6) + .75*ratio[1]
         | 
| 47 | 
            +
                    if p0 == p1
         | 
| 48 | 
            +
                      p0.signal = 0.0
         | 
| 49 | 
            +
                      p0.ratio = 1.0
         | 
| 50 | 
            +
                    else
         | 
| 51 | 
            +
                      p0.signal = threshold + 10.0 * (Math.log((p0.i1**2 + p0.q1**2)/(p0.noise**2)) / Math.log(10))
         | 
| 52 | 
            +
                      p0.ratio = (0.25 * p0.signal) + (0.75 * p1.ratio)
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
                    p0.state = p0.ratio >= threshold ? 1 : 0
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def compute
         | 
| 58 | 
            +
                    compute_values
         | 
| 59 | 
            +
                    compute_noise
         | 
| 60 | 
            +
                    compute_ratio
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -0,0 +1,63 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Quant
         | 
| 4 | 
            +
              module IndicatorsRegistry
         | 
| 5 | 
            +
                def self.included(base)
         | 
| 6 | 
            +
                  base.extend(ClassMethods)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def define_indicator_accessors(indicator_source:)
         | 
| 10 | 
            +
                  self.class.define_indicator_accessors(indicator_source:)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                module ClassMethods
         | 
| 14 | 
            +
                  def registry
         | 
| 15 | 
            +
                    @registry ||= {}
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  class RegistryEntry
         | 
| 19 | 
            +
                    attr_reader :name, :indicator_class
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def initialize(name:, indicator_class:)
         | 
| 22 | 
            +
                      @name = name
         | 
| 23 | 
            +
                      @indicator_class = indicator_class
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    def key
         | 
| 27 | 
            +
                      "#{indicator_class.name}::#{name}"
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    def standard?
         | 
| 31 | 
            +
                      !pivot? && !dominant_cycle?
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    def pivot?
         | 
| 35 | 
            +
                      indicator_class < Indicators::Pivots::Pivot
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def dominant_cycle?
         | 
| 39 | 
            +
                      indicator_class < Indicators::DominantCycles::DominantCycle
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def register(name:, indicator_class:)
         | 
| 44 | 
            +
                    entry = RegistryEntry.new(name:, indicator_class:)
         | 
| 45 | 
            +
                    registry[entry.key] = entry
         | 
| 46 | 
            +
                    # registry[name] = indicator_class
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def registry_entries_for(indicator_source:)
         | 
| 50 | 
            +
                    return registry.values.select(&:pivot?) if indicator_source.is_a?(PivotsSource)
         | 
| 51 | 
            +
                    return registry.values.select(&:dominant_cycle?) if indicator_source.is_a?(DominantCyclesSource)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    registry.values.select(&:standard?)
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def define_indicator_accessors(indicator_source:)
         | 
| 57 | 
            +
                    registry_entries_for(indicator_source:).each do |entry|
         | 
| 58 | 
            +
                      indicator_source.define_singleton_method(entry.name) { indicator(entry.indicator_class) }
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
              end
         | 
| 63 | 
            +
            end
         |