quantitative 0.2.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/README.md +5 -0
- data/lib/quant/asset.rb +0 -2
- data/lib/quant/{dominant_cycle_indicators.rb → dominant_cycles_source.rb} +18 -10
- data/lib/quant/experimental.rb +11 -2
- data/lib/quant/indicators/adx.rb +3 -4
- data/lib/quant/indicators/atr.rb +1 -4
- data/lib/quant/indicators/cci.rb +1 -1
- data/lib/quant/indicators/decycler.rb +17 -31
- data/lib/quant/indicators/dominant_cycles/acr.rb +3 -6
- data/lib/quant/indicators/dominant_cycles/band_pass.rb +2 -4
- data/lib/quant/indicators/dominant_cycles/differential.rb +2 -2
- data/lib/quant/indicators/dominant_cycles/dominant_cycle.rb +11 -4
- data/lib/quant/indicators/dominant_cycles/half_period.rb +2 -4
- data/lib/quant/indicators/dominant_cycles/homodyne.rb +2 -5
- data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +2 -4
- data/lib/quant/indicators/frama.rb +6 -9
- data/lib/quant/indicators/indicator.rb +46 -3
- data/lib/quant/indicators/indicator_point.rb +1 -1
- data/lib/quant/indicators/mama.rb +1 -1
- data/lib/quant/indicators/mesa.rb +1 -1
- data/lib/quant/indicators/ping.rb +1 -1
- data/lib/quant/indicators/pivot.rb +107 -0
- data/lib/quant/indicators/pivots/atr.rb +41 -0
- data/lib/quant/indicators/pivots/bollinger.rb +45 -0
- data/lib/quant/indicators/pivots/camarilla.rb +61 -0
- data/lib/quant/indicators/pivots/classic.rb +24 -0
- data/lib/quant/indicators/pivots/demark.rb +50 -0
- data/lib/quant/indicators/pivots/donchian.rb +40 -0
- data/lib/quant/indicators/pivots/fibbonacci.rb +22 -0
- data/lib/quant/indicators/pivots/guppy.rb +39 -0
- data/lib/quant/indicators/pivots/keltner.rb +43 -0
- data/lib/quant/indicators/pivots/murrey.rb +33 -0
- data/lib/quant/indicators/pivots/traditional.rb +36 -0
- data/lib/quant/indicators/pivots/woodie.rb +59 -0
- data/lib/quant/indicators.rb +1 -13
- data/lib/quant/indicators_source.rb +139 -0
- data/lib/quant/indicators_sources.rb +36 -10
- data/lib/quant/mixins/filters.rb +0 -3
- data/lib/quant/mixins/moving_averages.rb +0 -3
- data/lib/quant/pivots_source.rb +28 -0
- data/lib/quant/refinements/array.rb +14 -0
- data/lib/quant/series.rb +8 -19
- data/lib/quant/settings/indicators.rb +11 -0
- data/lib/quant/ticks/ohlc.rb +0 -2
- data/lib/quant/ticks/spot.rb +0 -2
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +21 -4
- data/possibilities.png +0 -0
- metadata +34 -5
- data/lib/quant/indicators_proxy.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cfda18120bf7e3432b359a53d19d633f70f66e3ea4468098673cfc89ac2f77e
|
4
|
+
data.tar.gz: 598dc565f6da96ce9565afee249f38bc909234177d7b484ce82cba226e2e8d60
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b2625954abe0e72a776de95da8d26af213440cb7e2d7beddd48529dfdb70d7247562066b09a3edd1dd3f171ad3ff8a32390ffe337729ca9712f65bdb147084e
|
7
|
+
data.tar.gz: 780f81a808f14d2d63b53aa81f8f30920b252979f17409d1ba48105ed5eadc91dadc253ada0cd75c6c21b8e6a8450cf39c75ce542adb450759fb097679899cce
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
quantitative (0.
|
4
|
+
quantitative (0.3.1)
|
5
5
|
oj (~> 3.10)
|
6
|
+
zeitwerk (~> 2.6)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
@@ -126,6 +127,7 @@ GEM
|
|
126
127
|
unicode-display_width (2.5.0)
|
127
128
|
vernier (0.5.1)
|
128
129
|
yard (0.9.34)
|
130
|
+
zeitwerk (2.6.15)
|
129
131
|
|
130
132
|
PLATFORMS
|
131
133
|
arm64-darwin-22
|
data/README.md
CHANGED
@@ -22,6 +22,11 @@ The information provided by this library should not be construed as an endorseme
|
|
22
22
|
|
23
23
|
Past performance is not necessarily indicative of future results. By using this library, you agree that the developers and contributors will not be liable for any losses or damages arising from your use of the library. Use at your own risk.
|
24
24
|
|
25
|
+
## Possibilties
|
26
|
+
While charting and automated trading systems are not part of this library, Quantitative goes a long ways towards giving you powerful tools to build such a system. It is extracted from an automated trading system built in Ruby on Rails and has been used to generate considerable net profits over the years.
|
27
|
+
|
28
|
+
![Possibilities](https://github.com/mwlang/quantitative/blob/main/possibilities.png)
|
29
|
+
|
25
30
|
## Installation
|
26
31
|
|
27
32
|
Install the gem and add to the application's Gemfile by executing:
|
data/lib/quant/asset.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "asset_class"
|
4
|
-
|
5
3
|
module Quant
|
6
4
|
# A {Quant::Asset} is a representation of a financial instrument such as a stock, option, future, or currency.
|
7
5
|
# It is used to represent the instrument that is being traded, analyzed, or managed.
|
@@ -1,6 +1,4 @@
|
|
1
1
|
|
2
|
-
require_relative 'indicators_proxy'
|
3
|
-
|
4
2
|
module Quant
|
5
3
|
# Dominant Cycles measure the primary cycle within a given range. By default, the library
|
6
4
|
# is wired to look for cycles between 10 and 48 bars. These values can be adjusted by setting
|
@@ -16,7 +14,11 @@ module Quant
|
|
16
14
|
# The purpose of these indicators is to compute the dominant cycle and underpin the various
|
17
15
|
# indicators that would otherwise be setting an arbitrary lookback period. This makes the
|
18
16
|
# indicators adaptive and auto-tuning to the market dynamics. Or so the theory goes!
|
19
|
-
class
|
17
|
+
class DominantCyclesSource
|
18
|
+
def initialize(indicator_source:)
|
19
|
+
@indicator_source = indicator_source
|
20
|
+
end
|
21
|
+
|
20
22
|
# Auto-Correlation Reversals is a method of computing the dominant cycle
|
21
23
|
# by correlating the data stream with itself delayed by a lag.
|
22
24
|
def acr; indicator(Indicators::DominantCycles::Acr) end
|
@@ -28,11 +30,6 @@ module Quant
|
|
28
30
|
# and this is the `period` of the dominant cycle.
|
29
31
|
def band_pass; indicator(Indicators::DominantCycles::BandPass) end
|
30
32
|
|
31
|
-
# Homodyne means the signal is multiplied by itself. More precisely,
|
32
|
-
# we want to multiply the signal of the current bar with the complex
|
33
|
-
# value of the signal one bar ago
|
34
|
-
def homodyne; indicator(Indicators::DominantCycles::Homodyne) end
|
35
|
-
|
36
33
|
# The Dual Differentiator algorithm computes the phase angle from the
|
37
34
|
# analytic signal as the arctangent of the ratio of the imaginary
|
38
35
|
# component to the real component. Further, the angular frequency
|
@@ -40,13 +37,24 @@ module Quant
|
|
40
37
|
# derive the cycle period.
|
41
38
|
def differential; indicator(Indicators::DominantCycles::Differential) end
|
42
39
|
|
40
|
+
# Static, arbitrarily set period.
|
41
|
+
def half_period; indicator(Indicators::DominantCycles::HalfPeriod) end
|
42
|
+
|
43
|
+
# Homodyne means the signal is multiplied by itself. More precisely,
|
44
|
+
# we want to multiply the signal of the current bar with the complex
|
45
|
+
# value of the signal one bar ago
|
46
|
+
def homodyne; indicator(Indicators::DominantCycles::Homodyne) end
|
47
|
+
|
43
48
|
# The phase accumulation method of computing the dominant cycle measures
|
44
49
|
# the phase at each sample by taking the arctangent of the ratio of the
|
45
50
|
# quadrature component to the in-phase component. The phase is then
|
46
51
|
# accumulated and the period is derived from the phase.
|
47
52
|
def phase_accumulator; indicator(Indicators::DominantCycles::PhaseAccumulator) end
|
48
53
|
|
49
|
-
|
50
|
-
|
54
|
+
private
|
55
|
+
|
56
|
+
def indicator(indicator_class)
|
57
|
+
@indicator_source[indicator_class]
|
58
|
+
end
|
51
59
|
end
|
52
60
|
end
|
data/lib/quant/experimental.rb
CHANGED
@@ -1,14 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
+
# {Quant::Experimental} is an alert emitter for experimental code paths.
|
5
|
+
# It will typically be used for new indicators or computations that are not yet
|
6
|
+
# fully vetted or tested.
|
4
7
|
module Experimental
|
5
8
|
def self.tracker
|
6
9
|
@tracker ||= {}
|
7
10
|
end
|
11
|
+
|
12
|
+
def self.rspec_defined?
|
13
|
+
defined?("RSpec")
|
14
|
+
end
|
8
15
|
end
|
9
16
|
|
10
|
-
|
11
|
-
|
17
|
+
module_function
|
18
|
+
|
19
|
+
def experimental(message)
|
20
|
+
return if Experimental.rspec_defined?
|
12
21
|
return if Experimental.tracker[caller.first]
|
13
22
|
|
14
23
|
Experimental.tracker[caller.first] = message
|
data/lib/quant/indicators/adx.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "indicator_point"
|
4
|
-
require_relative "indicator"
|
5
|
-
|
6
3
|
module Quant
|
7
|
-
|
4
|
+
module Indicators
|
8
5
|
class AdxPoint < IndicatorPoint
|
9
6
|
attribute :dmu, default: 0.0
|
10
7
|
attribute :dmd, default: 0.0
|
@@ -24,6 +21,8 @@ module Quant
|
|
24
21
|
end
|
25
22
|
|
26
23
|
class Adx < Indicator
|
24
|
+
depends_on Indicators::Atr
|
25
|
+
|
27
26
|
def alpha
|
28
27
|
bars_to_alpha(dc_period)
|
29
28
|
end
|
data/lib/quant/indicators/atr.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "indicator_point"
|
4
|
-
require_relative "indicator"
|
5
|
-
|
6
3
|
module Quant
|
7
|
-
|
4
|
+
module Indicators
|
8
5
|
class AtrPoint < IndicatorPoint
|
9
6
|
attribute :tr, default: 0.0
|
10
7
|
attribute :period, default: :min_period
|
data/lib/quant/indicators/cci.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
-
|
4
|
+
module Indicators
|
5
5
|
# The decycler oscillator can be useful for determining the transition be- tween uptrends and downtrends by the crossing of the zero
|
6
6
|
# line. Alternatively, the changes of slope of the decycler oscillator are easier to identify than the changes in slope of the
|
7
7
|
# original decycler. Optimum cutoff periods can easily be found by experimentation.
|
@@ -14,25 +14,13 @@ module Quant
|
|
14
14
|
# output of another high-pass filter having a longer cutoff period.
|
15
15
|
# 5. A decycler oscillator shows transitions between uptrends and down-trends at the zero crossings.
|
16
16
|
class DecyclerPoint < IndicatorPoint
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
"osc" => osc,
|
25
|
-
}
|
26
|
-
end
|
27
|
-
|
28
|
-
def initialize_data_points(indicator:)
|
29
|
-
@decycle = oc2
|
30
|
-
@hp1 = 0.0
|
31
|
-
@hp2 = 0.0
|
32
|
-
@osc = 0.0
|
33
|
-
@peak = 0.0
|
34
|
-
@agc = 0.0
|
35
|
-
end
|
17
|
+
attribute :decycle, default: :input
|
18
|
+
attribute :hp1, default: 0.0
|
19
|
+
attribute :hp2, default: 0.0
|
20
|
+
attribute :osc, default: 0.0
|
21
|
+
attribute :peak, default: 0.0
|
22
|
+
attribute :agc, default: 0.0
|
23
|
+
attribute :ift, default: 0.0
|
36
24
|
end
|
37
25
|
|
38
26
|
class Decycler < Indicator
|
@@ -40,13 +28,9 @@ module Quant
|
|
40
28
|
dc_period
|
41
29
|
end
|
42
30
|
|
43
|
-
def min_period
|
44
|
-
settings.min_period
|
45
|
-
end
|
46
|
-
|
47
31
|
def compute_decycler
|
48
32
|
alpha = period_to_alpha(max_period)
|
49
|
-
p0.decycle = (alpha / 2) * (p0.
|
33
|
+
p0.decycle = (alpha / 2) * (p0.input + p1.input) + (1.0 - alpha) * p1.decycle
|
50
34
|
end
|
51
35
|
|
52
36
|
# alpha1 = (Cosine(.707*360 / HPPeriod1) + Sine (.707*360 / HPPeriod1) - 1) / Cosine(.707*360 / HPPeriod1);
|
@@ -56,7 +40,7 @@ module Quant
|
|
56
40
|
c = Math.cos(0.707 * radians / period)
|
57
41
|
s = Math.sin(0.707 * radians / period)
|
58
42
|
alpha = (c + s - 1) / c
|
59
|
-
(1 - alpha / 2)**2 * (p0.
|
43
|
+
(1 - alpha / 2)**2 * (p0.input - 2 * p1.input + p2.input) + 2 * (1 - alpha) * p1.send(hp) - (1 - alpha) * (1 - alpha) * p2.send(hp)
|
60
44
|
end
|
61
45
|
|
62
46
|
def compute_oscillator
|
@@ -65,20 +49,22 @@ module Quant
|
|
65
49
|
p0.osc = p0.hp2 - p0.hp1
|
66
50
|
end
|
67
51
|
|
68
|
-
|
52
|
+
# AGC is constrained to -1.0 to 1.0
|
53
|
+
# The peak decays at a rate of 0.991 per bar
|
54
|
+
def compute_automatic_gain_control
|
69
55
|
p0.peak = [p0.osc.abs, 0.991 * p1.peak].max
|
70
56
|
p0.agc = p0.peak.zero? ? p0.osc : p0.osc / p0.peak
|
71
57
|
end
|
72
58
|
|
73
|
-
def
|
74
|
-
p0.ift =
|
59
|
+
def compute_inverse_fisher_transform
|
60
|
+
p0.ift = inverse_fisher_transform(p0.agc, scale_factor: 5.0)
|
75
61
|
end
|
76
62
|
|
77
63
|
def compute
|
78
64
|
compute_decycler
|
79
65
|
compute_oscillator
|
80
|
-
|
81
|
-
|
66
|
+
compute_automatic_gain_control
|
67
|
+
compute_inverse_fisher_transform
|
82
68
|
end
|
83
69
|
end
|
84
70
|
end
|
@@ -1,12 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../indicator_point"
|
4
|
-
require_relative "dominant_cycle"
|
5
|
-
|
6
3
|
module Quant
|
7
|
-
|
8
|
-
|
9
|
-
class AcrPoint <
|
4
|
+
module Indicators
|
5
|
+
module DominantCycles
|
6
|
+
class AcrPoint < Quant::Indicators::IndicatorPoint
|
10
7
|
attribute :hp, default: 0.0
|
11
8
|
attribute :filter, default: 0.0
|
12
9
|
attribute :interim_period, default: 0.0
|
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "dominant_cycle"
|
4
|
-
|
5
3
|
module Quant
|
6
|
-
|
7
|
-
|
4
|
+
module Indicators
|
5
|
+
module DominantCycles
|
8
6
|
class BandPassPoint < Quant::Indicators::IndicatorPoint
|
9
7
|
attribute :hp, default: 0.0
|
10
8
|
attribute :bp, default: 0.0
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
-
|
5
|
-
|
4
|
+
module Indicators
|
5
|
+
module DominantCycles
|
6
6
|
# The Dual Differentiator algorithm computes the phase angle from the
|
7
7
|
# analytic signal as the arctangent of the ratio of the imaginary
|
8
8
|
# component to the real component. Further, the angular frequency
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../indicator"
|
4
|
-
|
5
3
|
module Quant
|
6
|
-
|
4
|
+
module Indicators
|
7
5
|
# Dominant Cycles measure the primary cycle within a given range. By default, the library
|
8
6
|
# is wired to look for cycles between 10 and 48 bars. These values can be adjusted by setting
|
9
7
|
# the `min_period` and `max_period` configuration values in {Quant::Config}.
|
@@ -18,7 +16,7 @@ module Quant
|
|
18
16
|
# The purpose of these indicators is to compute the dominant cycle and underpin the various
|
19
17
|
# indicators that would otherwise be setting an arbitrary lookback period. This makes the
|
20
18
|
# indicators adaptive and auto-tuning to the market dynamics. Or so the theory goes!
|
21
|
-
|
19
|
+
module DominantCycles
|
22
20
|
class DominantCyclePoint < Quant::Indicators::IndicatorPoint
|
23
21
|
attribute :smooth, default: 0.0
|
24
22
|
attribute :detrend, default: 0.0
|
@@ -44,6 +42,15 @@ module Quant
|
|
44
42
|
end
|
45
43
|
|
46
44
|
class DominantCycle < Indicators::Indicator
|
45
|
+
def priority
|
46
|
+
DOMINANT_CYCLES_PRIORITY
|
47
|
+
end
|
48
|
+
|
49
|
+
# Dominant Cycle Indicators should not themselves have a dominant cycle indicator
|
50
|
+
def dominant_cycle_indicator_class
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
47
54
|
def points_class
|
48
55
|
Object.const_get "Quant::Indicators::DominantCycles::#{indicator_name}Point"
|
49
56
|
rescue NameError
|
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "dominant_cycle"
|
4
|
-
|
5
3
|
module Quant
|
6
|
-
|
7
|
-
|
4
|
+
module Indicators
|
5
|
+
module DominantCycles
|
8
6
|
# This dominant cycle indicator is based on the half period
|
9
7
|
# that is the midpoint of the `min_period` and `max_period`
|
10
8
|
# configured in the `Quant.config.indicators` object.
|
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../indicator_point"
|
4
|
-
require_relative "dominant_cycle"
|
5
|
-
|
6
3
|
module Quant
|
7
|
-
|
8
|
-
|
4
|
+
module Indicators
|
5
|
+
module DominantCycles
|
9
6
|
# Homodyne means the signal is multiplied by itself. More precisely,
|
10
7
|
# we want to multiply the signal of the current bar with the complex
|
11
8
|
# value of the signal one bar ago
|
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "dominant_cycle"
|
4
|
-
|
5
3
|
module Quant
|
6
|
-
|
7
|
-
|
4
|
+
module Indicators
|
5
|
+
module DominantCycles
|
8
6
|
# The phase accumulation method of computing the dominant cycle is perhaps
|
9
7
|
# the easiest to comprehend. In this technique, we measure the phase
|
10
8
|
# at each sample by taking the arctangent of the ratio of the quadrature
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
-
|
4
|
+
module Indicators
|
5
5
|
class FramaPoint < IndicatorPoint
|
6
6
|
attribute :frama, default: :input
|
7
|
+
attribute :dimension, default: 0.0
|
8
|
+
attribute :alpha, default: 0.0
|
7
9
|
end
|
8
10
|
|
9
11
|
# FRAMA (FRactal Adaptive Moving Average). A nonlinear moving average
|
@@ -23,11 +25,6 @@ module Quant
|
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
|
-
# def max_period
|
27
|
-
# mp = dc_period
|
28
|
-
# mp.even? ? mp : mp + 1
|
29
|
-
# end
|
30
|
-
|
31
28
|
def half_period
|
32
29
|
max_period / 2
|
33
30
|
end
|
@@ -44,9 +41,9 @@ module Quant
|
|
44
41
|
ppn1 = pp.last(half_period)
|
45
42
|
n1 = (ppn1.maximum - ppn1.minimum) / half_period
|
46
43
|
|
47
|
-
dimension = (Math.log(n1 + n2) - Math.log(n3)) / Math.log(2)
|
48
|
-
alpha = Math.exp(-4.6 * (dimension - 1.0)).clamp(0.01, 1.0)
|
49
|
-
p0.frama = (alpha * p0.input) + ((1 - alpha) * p1.frama)
|
44
|
+
p0.dimension = (Math.log(n1 + n2) - Math.log(n3)) / Math.log(2)
|
45
|
+
p0.alpha = Math.exp(-4.6 * (p0.dimension - 1.0)).clamp(0.01, 1.0)
|
46
|
+
p0.frama = (p0.alpha * p0.input) + ((1 - p0.alpha) * p1.frama)
|
50
47
|
end
|
51
48
|
end
|
52
49
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
-
|
4
|
+
module Indicators
|
5
|
+
# The {Quant::Indicators::Indicator} class is the abstract ancestor for all Indicators.
|
6
|
+
#
|
5
7
|
class Indicator
|
6
8
|
include Enumerable
|
7
9
|
include Mixins::Functions
|
@@ -13,16 +15,57 @@ module Quant
|
|
13
15
|
include Mixins::FisherTransform
|
14
16
|
# include Mixins::Direction
|
15
17
|
|
18
|
+
# Provides a registry of dependent indicators for each indicator class.
|
19
|
+
# NOTE: Internal use only.
|
20
|
+
def self.dependent_indicator_classes
|
21
|
+
@dependent_indicator_classes ||= Set.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Use the {depends_on} method to declare dependencies for an indicator.
|
25
|
+
# @param indicator_classes [Array<Class>] The classes of the indicators to depend on.
|
26
|
+
# @example
|
27
|
+
# class BarIndicator < Indicator
|
28
|
+
# depends_on FooIndicator
|
29
|
+
# end
|
30
|
+
def self.depends_on(*indicator_classes)
|
31
|
+
Array(indicator_classes).each{ |dependency| dependent_indicator_classes << dependency }
|
32
|
+
end
|
33
|
+
|
16
34
|
attr_reader :source, :series
|
17
35
|
|
18
36
|
def initialize(series:, source:)
|
19
37
|
@series = series
|
20
38
|
@source = source
|
21
39
|
@points = {}
|
22
|
-
series.new_indicator(self)
|
23
40
|
series.each { |tick| self << tick }
|
24
41
|
end
|
25
42
|
|
43
|
+
def dominant_cycle_indicator_class
|
44
|
+
Quant.config.indicators.dominant_cycle_indicator_class
|
45
|
+
end
|
46
|
+
|
47
|
+
# The priority drives the order of computations when iterating over each tick
|
48
|
+
# in a series. Generally speaking, indicators that feed values to another indicator
|
49
|
+
# must have a lower priority value than the indicator that consumes the values.
|
50
|
+
# * Most indicators will have a default priority of 1000.
|
51
|
+
# * Dominant Cycle indicators will have a priority of 100.
|
52
|
+
# * Some indicators will have a "high priority" of 500.
|
53
|
+
# Priority values are arbitrary and purposefully gapping so that new indicators
|
54
|
+
# introduced outside the core library can be slotted in between.
|
55
|
+
#
|
56
|
+
# NOTE: Priority is well-managed by the library and should not require overriding
|
57
|
+
# for a custom indicator developed outside the library. If you find yourself
|
58
|
+
# needing to override this method, please open an issue on the library's GitHub page.
|
59
|
+
PRIORITIES = [
|
60
|
+
DOMINANT_CYCLES_PRIORITY = 100,
|
61
|
+
DEPENDENCY_PRIORITY = 500,
|
62
|
+
DEFAULT_PRIORITY = 1000
|
63
|
+
].freeze
|
64
|
+
|
65
|
+
def priority
|
66
|
+
DEFAULT_PRIORITY
|
67
|
+
end
|
68
|
+
|
26
69
|
def min_period
|
27
70
|
Quant.config.indicators.min_period
|
28
71
|
end
|
@@ -48,7 +91,7 @@ module Quant
|
|
48
91
|
end
|
49
92
|
|
50
93
|
def dominant_cycle
|
51
|
-
series.indicators[source]
|
94
|
+
series.indicators[source][dominant_cycle_indicator_class]
|
52
95
|
end
|
53
96
|
|
54
97
|
def dc_period
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Quant
|
4
|
-
|
4
|
+
module Indicators
|
5
5
|
# A simple point used primarily to test the indicator system in unit tests.
|
6
6
|
# It has a simple computation that just sets the pong value to the input value
|
7
7
|
# and increments the compute_count by 1 each time compute is called.
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Quant
|
2
|
+
module Indicators
|
3
|
+
class PivotPoint < IndicatorPoint
|
4
|
+
attribute :high_price
|
5
|
+
attribute :avg_high, default: :high_price
|
6
|
+
attribute :highest, default: :input
|
7
|
+
|
8
|
+
attribute :low_price
|
9
|
+
attribute :avg_low, default: :low_price
|
10
|
+
attribute :lowest, default: :input
|
11
|
+
|
12
|
+
attribute :range, default: 0.0
|
13
|
+
attribute :avg_range, default: 0.0
|
14
|
+
attribute :std_dev, default: 0.0
|
15
|
+
|
16
|
+
def bands
|
17
|
+
@bands ||= { 0 => input }
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](band)
|
21
|
+
bands[band]
|
22
|
+
end
|
23
|
+
|
24
|
+
def []=(band, value)
|
25
|
+
bands[band] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def key?(band)
|
29
|
+
bands.key?(band)
|
30
|
+
end
|
31
|
+
|
32
|
+
def midpoint
|
33
|
+
bands[0]
|
34
|
+
end
|
35
|
+
alias :h0 :midpoint
|
36
|
+
alias :l0 :midpoint
|
37
|
+
|
38
|
+
def midpoint=(value)
|
39
|
+
bands[0] = value
|
40
|
+
end
|
41
|
+
alias :h0= :midpoint=
|
42
|
+
alias :l0= :midpoint=
|
43
|
+
|
44
|
+
(1..8).each do |band|
|
45
|
+
define_method("h#{band}") { bands[band] }
|
46
|
+
define_method("h#{band}=") { |value| bands[band] = value }
|
47
|
+
|
48
|
+
define_method("l#{band}") { bands[-band] }
|
49
|
+
define_method("l#{band}=") { |value| bands[-band] = value }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class Pivot < Indicator
|
54
|
+
def points_class
|
55
|
+
Quant::Indicators::PivotPoint
|
56
|
+
end
|
57
|
+
|
58
|
+
def band?(band)
|
59
|
+
p0.key?(band)
|
60
|
+
end
|
61
|
+
|
62
|
+
def period
|
63
|
+
dc_period
|
64
|
+
end
|
65
|
+
|
66
|
+
def averaging_period
|
67
|
+
min_period
|
68
|
+
end
|
69
|
+
|
70
|
+
def period_midpoints
|
71
|
+
period_points(period).map(&:midpoint)
|
72
|
+
end
|
73
|
+
|
74
|
+
def compute
|
75
|
+
compute_extents
|
76
|
+
compute_value
|
77
|
+
compute_midpoint
|
78
|
+
compute_bands
|
79
|
+
end
|
80
|
+
|
81
|
+
def compute_midpoint
|
82
|
+
p0.midpoint = p0.input
|
83
|
+
end
|
84
|
+
|
85
|
+
def compute_value
|
86
|
+
# No-op -- override in subclasses
|
87
|
+
end
|
88
|
+
|
89
|
+
def compute_bands
|
90
|
+
# No-op -- override in subclasses
|
91
|
+
end
|
92
|
+
|
93
|
+
def compute_extents
|
94
|
+
period_midpoints.tap do |midpoints|
|
95
|
+
p0.high_price = t0.high_price
|
96
|
+
p0.low_price = t0.low_price
|
97
|
+
p0.highest = midpoints.max
|
98
|
+
p0.lowest = midpoints.min
|
99
|
+
p0.range = p0.high_price - p0.low_price
|
100
|
+
p0.avg_low = super_smoother(:low_price, previous: :avg_low, period: averaging_period)
|
101
|
+
p0.avg_high = super_smoother(:high_price, previous: :avg_high, period: averaging_period)
|
102
|
+
p0.avg_range = super_smoother(:range, previous: :avg_range, period: averaging_period)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|