quantitative 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +9 -4
- data/Gemfile.lock +9 -1
- data/README.md +5 -0
- data/lib/quant/{indicators/dominant_cycle_indicators.rb → dominant_cycles_source.rb} +19 -8
- data/lib/quant/experimental.rb +8 -1
- data/lib/quant/indicators/adx.rb +83 -0
- data/lib/quant/indicators/atr.rb +79 -0
- data/lib/quant/indicators/cci.rb +63 -0
- data/lib/quant/indicators/decycler.rb +71 -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 +3 -1
- data/lib/quant/indicators/dominant_cycles/dominant_cycle.rb +12 -6
- data/lib/quant/indicators/dominant_cycles/half_period.rb +2 -0
- data/lib/quant/indicators/dominant_cycles/homodyne.rb +4 -2
- data/lib/quant/indicators/dominant_cycles/phase_accumulator.rb +8 -4
- data/lib/quant/indicators/frama.rb +50 -0
- data/lib/quant/indicators/indicator.rb +50 -2
- data/lib/quant/indicators/mama.rb +143 -0
- data/lib/quant/indicators/mesa.rb +86 -0
- 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 +34 -0
- data/lib/quant/indicators/pivots/traditional.rb +36 -0
- data/lib/quant/indicators/pivots/woodie.rb +59 -0
- data/lib/quant/indicators_source.rb +140 -0
- data/lib/quant/indicators_sources.rb +36 -10
- data/lib/quant/mixins/stochastic.rb +1 -1
- data/lib/quant/mixins/super_smoother.rb +11 -2
- 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/version.rb +1 -1
- data/possibilities.png +0 -0
- data/quantitative.gemspec +39 -0
- metadata +27 -5
- data/lib/quant/indicators.rb +0 -14
- 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: '06693d3511af25d58463fd872f004265889bb0c8d8344dd4fc8436bd4a66f63b'
|
4
|
+
data.tar.gz: b1e7db72820532999567608e1d3b864a7e4b1029b363de4c05c1032b177abc5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6dcc22eaa684535f9452287e1a486d2de3bc3d38b12be207130e62fb0264ea90087e20c9a9693952b8be3030738a3eebd8681813ccf2f0c2838aa9c33c9ad7b1
|
7
|
+
data.tar.gz: 05d9470a2c3426c46b6e64113b728672d841640dea56887d1efc9fcd9c7839b561f09cb2da4d6453bd339222bd07f48885bbae443e55646b90b855cd6bec716f
|
data/Gemfile
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
|
-
# Specify your gem
|
5
|
+
# Specify your gem"s dependencies in quantitative.gemspec
|
6
6
|
gemspec
|
7
7
|
|
8
8
|
gem "rake", "~> 13.0"
|
@@ -18,7 +18,12 @@ gem "guard-rspec", "~> 4.7"
|
|
18
18
|
gem "yard", "~> 0.9"
|
19
19
|
gem "benchmark-ips", "~> 2.9"
|
20
20
|
|
21
|
-
gem
|
22
|
-
gem 'simplecov'
|
23
|
-
gem 'simplecov-cobertura'
|
21
|
+
gem "rspec-github"
|
24
22
|
|
23
|
+
# Test coverage and profiling
|
24
|
+
gem "simplecov"
|
25
|
+
gem "simplecov-cobertura"
|
26
|
+
gem "stackprof", require: false
|
27
|
+
gem "test-prof", require: false
|
28
|
+
gem "vernier", require: false
|
29
|
+
gem "ruby-prof", require: false
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
quantitative (0.
|
4
|
+
quantitative (0.3.0)
|
5
5
|
oj (~> 3.10)
|
6
6
|
|
7
7
|
GEM
|
@@ -107,6 +107,7 @@ GEM
|
|
107
107
|
rubocop (~> 1.40)
|
108
108
|
rubocop-capybara (~> 2.17)
|
109
109
|
rubocop-factory_bot (~> 2.22)
|
110
|
+
ruby-prof (1.7.0)
|
110
111
|
ruby-progressbar (1.13.0)
|
111
112
|
shellany (0.0.1)
|
112
113
|
simplecov (0.22.0)
|
@@ -118,9 +119,12 @@ GEM
|
|
118
119
|
simplecov (~> 0.19)
|
119
120
|
simplecov-html (0.12.3)
|
120
121
|
simplecov_json_formatter (0.1.4)
|
122
|
+
stackprof (0.2.26)
|
121
123
|
stringio (3.1.0)
|
124
|
+
test-prof (1.3.2)
|
122
125
|
thor (1.3.0)
|
123
126
|
unicode-display_width (2.5.0)
|
127
|
+
vernier (0.5.1)
|
124
128
|
yard (0.9.34)
|
125
129
|
|
126
130
|
PLATFORMS
|
@@ -138,8 +142,12 @@ DEPENDENCIES
|
|
138
142
|
rspec-github
|
139
143
|
rubocop (~> 1.21)
|
140
144
|
rubocop-rspec
|
145
|
+
ruby-prof
|
141
146
|
simplecov
|
142
147
|
simplecov-cobertura
|
148
|
+
stackprof
|
149
|
+
test-prof
|
150
|
+
vernier
|
143
151
|
yard (~> 0.9)
|
144
152
|
|
145
153
|
BUNDLED WITH
|
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:
|
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
module Quant
|
2
3
|
# Dominant Cycles measure the primary cycle within a given range. By default, the library
|
3
4
|
# is wired to look for cycles between 10 and 48 bars. These values can be adjusted by setting
|
@@ -13,7 +14,11 @@ module Quant
|
|
13
14
|
# The purpose of these indicators is to compute the dominant cycle and underpin the various
|
14
15
|
# indicators that would otherwise be setting an arbitrary lookback period. This makes the
|
15
16
|
# indicators adaptive and auto-tuning to the market dynamics. Or so the theory goes!
|
16
|
-
class
|
17
|
+
class DominantCyclesSource
|
18
|
+
def initialize(indicator_source:)
|
19
|
+
@indicator_source = indicator_source
|
20
|
+
end
|
21
|
+
|
17
22
|
# Auto-Correlation Reversals is a method of computing the dominant cycle
|
18
23
|
# by correlating the data stream with itself delayed by a lag.
|
19
24
|
def acr; indicator(Indicators::DominantCycles::Acr) end
|
@@ -25,11 +30,6 @@ module Quant
|
|
25
30
|
# and this is the `period` of the dominant cycle.
|
26
31
|
def band_pass; indicator(Indicators::DominantCycles::BandPass) end
|
27
32
|
|
28
|
-
# Homodyne means the signal is multiplied by itself. More precisely,
|
29
|
-
# we want to multiply the signal of the current bar with the complex
|
30
|
-
# value of the signal one bar ago
|
31
|
-
def homodyne; indicator(Indicators::DominantCycles::Homodyne) end
|
32
|
-
|
33
33
|
# The Dual Differentiator algorithm computes the phase angle from the
|
34
34
|
# analytic signal as the arctangent of the ratio of the imaginary
|
35
35
|
# component to the real component. Further, the angular frequency
|
@@ -37,13 +37,24 @@ module Quant
|
|
37
37
|
# derive the cycle period.
|
38
38
|
def differential; indicator(Indicators::DominantCycles::Differential) end
|
39
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
|
+
|
40
48
|
# The phase accumulation method of computing the dominant cycle measures
|
41
49
|
# the phase at each sample by taking the arctangent of the ratio of the
|
42
50
|
# quadrature component to the in-phase component. The phase is then
|
43
51
|
# accumulated and the period is derived from the phase.
|
44
52
|
def phase_accumulator; indicator(Indicators::DominantCycles::PhaseAccumulator) end
|
45
53
|
|
46
|
-
|
47
|
-
|
54
|
+
private
|
55
|
+
|
56
|
+
def indicator(indicator_class)
|
57
|
+
@indicator_source[indicator_class]
|
58
|
+
end
|
48
59
|
end
|
49
60
|
end
|
data/lib/quant/experimental.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
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
17
|
def self.experimental(message)
|
11
|
-
return if
|
18
|
+
return if Experimental.rspec_defined?
|
12
19
|
return if Experimental.tracker[caller.first]
|
13
20
|
|
14
21
|
Experimental.tracker[caller.first] = message
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "indicator_point"
|
4
|
+
require_relative "indicator"
|
5
|
+
require_relative "atr"
|
6
|
+
|
7
|
+
module Quant
|
8
|
+
class Indicators
|
9
|
+
class AdxPoint < IndicatorPoint
|
10
|
+
attribute :dmu, default: 0.0
|
11
|
+
attribute :dmd, default: 0.0
|
12
|
+
attribute :dmu_ema, default: 0.0
|
13
|
+
attribute :dmd_ema, default: 0.0
|
14
|
+
attribute :diu, default: 0.0
|
15
|
+
attribute :did, default: 0.0
|
16
|
+
attribute :di, default: 0.0
|
17
|
+
attribute :di_ema, default: 0.0
|
18
|
+
attribute :value, default: 0.0
|
19
|
+
attribute :inst_stoch, default: 0.0
|
20
|
+
attribute :stoch, default: 0.0
|
21
|
+
attribute :stoch_up, default: false
|
22
|
+
attribute :stoch_turned, default: false
|
23
|
+
attribute :ssf, default: 0.0
|
24
|
+
attribute :hp, default: 0.0
|
25
|
+
end
|
26
|
+
|
27
|
+
class Adx < Indicator
|
28
|
+
depends_on Indicators::Atr
|
29
|
+
|
30
|
+
def alpha
|
31
|
+
bars_to_alpha(dc_period)
|
32
|
+
end
|
33
|
+
|
34
|
+
def scale
|
35
|
+
1.0
|
36
|
+
end
|
37
|
+
|
38
|
+
def period
|
39
|
+
dc_period
|
40
|
+
end
|
41
|
+
|
42
|
+
def atr_point
|
43
|
+
series.indicators[source].atr.points[t0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def compute
|
47
|
+
# To calculate the ADX, first determine the + and - directional movement, or DM.
|
48
|
+
# The +DM and -DM are found by calculating the "up-move," or current high minus
|
49
|
+
# the previous high, and "down-move," or current low minus the previous low.
|
50
|
+
# If the up-move is greater than the down-move and greater than zero, the +DM equals the up-move;
|
51
|
+
# otherwise, it equals zero. If the down-move is greater than the up-move and greater than zero,
|
52
|
+
# the -DM equals the down-move; otherwise, it equals zero.
|
53
|
+
dm_highs = [t0.high_price - t1.high_price, 0.0].max
|
54
|
+
dm_lows = [t0.low_price - t1.low_price, 0.0].max
|
55
|
+
|
56
|
+
p0.dmu = dm_highs > dm_lows ? 0.0 : dm_highs
|
57
|
+
p0.dmd = dm_lows > dm_highs ? 0.0 : dm_lows
|
58
|
+
|
59
|
+
p0.dmu_ema = three_pole_super_smooth :dmu, period:, previous: :dmu_ema
|
60
|
+
p0.dmd_ema = three_pole_super_smooth :dmd, period:, previous: :dmd_ema
|
61
|
+
|
62
|
+
atr_value = atr_point.fast * scale
|
63
|
+
return if atr_value == 0.0 || @points.size < period
|
64
|
+
|
65
|
+
# The positive directional indicator, or +DI, equals 100 times the EMA of +DM divided by the ATR
|
66
|
+
# over a given number of time periods. Welles usually used 14 periods.
|
67
|
+
# The negative directional indicator, or -DI, equals 100 times the EMA of -DM divided by the ATR.
|
68
|
+
p0.diu = (100.0 * p0.dmu_ema) / atr_value
|
69
|
+
p0.did = (100.0 * p0.dmd_ema) / atr_value
|
70
|
+
|
71
|
+
# The ADX indicator itself equals 100 times the EMA of the absolute value of (+DI minus -DI)
|
72
|
+
# divided by (+DI plus -DI).
|
73
|
+
delta = p0.diu + p0.did
|
74
|
+
p0.di = (p0.diu - p1.did).abs / delta
|
75
|
+
p0.di_ema = three_pole_super_smooth(:di, period:, previous: :di_ema).clamp(-10.0, 10.0)
|
76
|
+
|
77
|
+
p0.value = p0.di_ema
|
78
|
+
p0.inst_stoch = stochastic :di, period: dc_period
|
79
|
+
p0.stoch = three_pole_super_smooth :inst_stoch, period:, previous: :stoch
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "indicator_point"
|
4
|
+
require_relative "indicator"
|
5
|
+
|
6
|
+
module Quant
|
7
|
+
class Indicators
|
8
|
+
class AtrPoint < IndicatorPoint
|
9
|
+
attribute :tr, default: 0.0
|
10
|
+
attribute :period, default: :min_period
|
11
|
+
attribute :value, default: 0.0
|
12
|
+
attribute :slow, default: 0.0
|
13
|
+
attribute :fast, default: 0.0
|
14
|
+
attribute :inst_stoch, default: 0.0
|
15
|
+
attribute :stoch, default: 0.0
|
16
|
+
attribute :stoch_up, default: false
|
17
|
+
attribute :stoch_turned, default: false
|
18
|
+
attribute :osc, default: 0.0
|
19
|
+
attribute :crossed, default: :unchanged
|
20
|
+
|
21
|
+
def crossed_up?
|
22
|
+
@crossed == :up
|
23
|
+
end
|
24
|
+
|
25
|
+
def crossed_down?
|
26
|
+
@crossed == :down
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Atr < Indicator
|
31
|
+
attr_reader :points
|
32
|
+
|
33
|
+
def period
|
34
|
+
dc_period / 2
|
35
|
+
end
|
36
|
+
|
37
|
+
def fast_alpha
|
38
|
+
period_to_alpha(period)
|
39
|
+
end
|
40
|
+
|
41
|
+
def slow_alpha
|
42
|
+
period_to_alpha(2 * period)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Typically, the Average True Range (ATR) is based on 14 periods and can be calculated on an intraday, daily, weekly
|
46
|
+
# or monthly basis. For this example, the ATR will be based on daily data. Because there must be a beginning, the first
|
47
|
+
# TR value is simply the High minus the Low, and the first 14-day ATR is the average of the daily TR values for the
|
48
|
+
# last 14 days. After that, Wilder sought to smooth the data by incorporating the previous period's ATR value.
|
49
|
+
|
50
|
+
# Current ATR = [(Prior ATR x 13) + Current TR] / 14
|
51
|
+
|
52
|
+
# - Multiply the previous 14-day ATR by 13.
|
53
|
+
# - Add the most recent day's TR value.
|
54
|
+
# - Divide the total by 14
|
55
|
+
|
56
|
+
def compute
|
57
|
+
p0.period = period
|
58
|
+
p0.tr = (t1.high_price - t0.close_price).abs
|
59
|
+
|
60
|
+
p0.value = three_pole_super_smooth :tr, period:, previous: :value
|
61
|
+
|
62
|
+
p0.slow = (slow_alpha * p0.value) + ((1.0 - slow_alpha) * p1.slow)
|
63
|
+
p0.fast = (fast_alpha * p0.value) + ((1.0 - fast_alpha) * p1.fast)
|
64
|
+
|
65
|
+
p0.inst_stoch = stochastic :value, period:
|
66
|
+
p0.stoch = three_pole_super_smooth(:inst_stoch, previous: :stoch, period:).clamp(0, 100)
|
67
|
+
p0.stoch_up = p0.stoch >= 70
|
68
|
+
p0.stoch_turned = p0.stoch_up && !p1.stoch_up
|
69
|
+
compute_oscillator
|
70
|
+
end
|
71
|
+
|
72
|
+
def compute_oscillator
|
73
|
+
p0.osc = p0.value - wma(:value)
|
74
|
+
p0.crossed = :up if p0.osc >= 0 && p1.osc < 0
|
75
|
+
p0.crossed = :down if p0.osc <= 0 && p1.osc > 0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
class Indicators
|
5
|
+
class CciPoint < IndicatorPoint
|
6
|
+
attribute :hp, default: 0.0
|
7
|
+
attribute :real, default: 0.0
|
8
|
+
attribute :imag, default: 0.0
|
9
|
+
attribute :angle, default: 0.0
|
10
|
+
attribute :state, default: 0
|
11
|
+
end
|
12
|
+
|
13
|
+
# Correlation Cycle Index
|
14
|
+
# The very definition of a trend mode and a cycle mode makes it simple
|
15
|
+
# to create a state variable that identifies the market state. If the
|
16
|
+
# state is zero, the market is in a cycle mode. If the state is +1 the
|
17
|
+
# market is in a trend up. If the state is -1 the market is in a trend down.
|
18
|
+
#
|
19
|
+
# SOURCE: https://www.mesasoftware.com/papers/CORRELATION%20AS%20A%20CYCLE%20INDICATOR.pdf
|
20
|
+
class Cci < Indicator
|
21
|
+
def max_period
|
22
|
+
[min_period, dc_period].max
|
23
|
+
end
|
24
|
+
|
25
|
+
def compute_correlations
|
26
|
+
corr_real = Statistics::Correlation.new
|
27
|
+
corr_imag = Statistics::Correlation.new
|
28
|
+
arc = 2.0 * Math::PI / max_period.to_f
|
29
|
+
(0...max_period).each do |period|
|
30
|
+
radians = arc * period
|
31
|
+
prev_hp = p(period).hp
|
32
|
+
corr_real.add(prev_hp, Math.cos(radians))
|
33
|
+
corr_imag.add(prev_hp, -Math.sin(radians))
|
34
|
+
end
|
35
|
+
p0.real = corr_real.coefficient
|
36
|
+
p0.imag = corr_imag.coefficient
|
37
|
+
end
|
38
|
+
|
39
|
+
def compute_angle
|
40
|
+
# Compute the angle as an arctangent and resolve the quadrant
|
41
|
+
p0.angle = 90 + rad2deg(Math.atan(p0.real / p0.imag))
|
42
|
+
p0.angle -= 180 if p0.imag > 0
|
43
|
+
|
44
|
+
# Do not allow the rate change of angle to go negative
|
45
|
+
p0.angle = p1.angle if (p0.angle < p1.angle) && (p1.angle - p0.angle) < 270
|
46
|
+
end
|
47
|
+
|
48
|
+
def compute_state
|
49
|
+
return unless (p0.angle - p1.angle).abs < 9
|
50
|
+
|
51
|
+
p0.state = p0.angle < 0 ? -1 : 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def compute
|
55
|
+
p0.hp = two_pole_butterworth :input, previous: :hp, period: min_period
|
56
|
+
|
57
|
+
compute_correlations
|
58
|
+
compute_angle
|
59
|
+
compute_state
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
class Indicators
|
5
|
+
# The decycler oscillator can be useful for determining the transition be- tween uptrends and downtrends by the crossing of the zero
|
6
|
+
# line. Alternatively, the changes of slope of the decycler oscillator are easier to identify than the changes in slope of the
|
7
|
+
# original decycler. Optimum cutoff periods can easily be found by experimentation.
|
8
|
+
#
|
9
|
+
# 1. A decycler filter functions the same as a low-pass filter.
|
10
|
+
# 2. A decycler filter is created by subtracting the output of a high-pass filter from the input, thereby removing the
|
11
|
+
# high-frequency components by cancellation.
|
12
|
+
# 3. A decycler filter has very low lag.
|
13
|
+
# 4. A decycler oscillator is created by subtracting the output of a high-pass filter having a shorter cutoff period from the
|
14
|
+
# output of another high-pass filter having a longer cutoff period.
|
15
|
+
# 5. A decycler oscillator shows transitions between uptrends and down-trends at the zero crossings.
|
16
|
+
class DecyclerPoint < IndicatorPoint
|
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
|
24
|
+
end
|
25
|
+
|
26
|
+
class Decycler < Indicator
|
27
|
+
def max_period
|
28
|
+
dc_period
|
29
|
+
end
|
30
|
+
|
31
|
+
def compute_decycler
|
32
|
+
alpha = period_to_alpha(max_period)
|
33
|
+
p0.decycle = (alpha / 2) * (p0.input + p1.input) + (1.0 - alpha) * p1.decycle
|
34
|
+
end
|
35
|
+
|
36
|
+
# alpha1 = (Cosine(.707*360 / HPPeriod1) + Sine (.707*360 / HPPeriod1) - 1) / Cosine(.707*360 / HPPeriod1);
|
37
|
+
# HP1 = (1 - alpha1 / 2)*(1 - alpha1 / 2)*(Close - 2*Close[1] + Close[2]) + 2*(1 - alpha1)*HP1[1] - (1 - alpha1)*(1 - alpha1)*HP1[2];
|
38
|
+
def compute_hp(period, hp)
|
39
|
+
radians = deg2rad(360)
|
40
|
+
c = Math.cos(0.707 * radians / period)
|
41
|
+
s = Math.sin(0.707 * radians / period)
|
42
|
+
alpha = (c + s - 1) / c
|
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)
|
44
|
+
end
|
45
|
+
|
46
|
+
def compute_oscillator
|
47
|
+
p0.hp1 = compute_hp(min_period, :hp1)
|
48
|
+
p0.hp2 = compute_hp(max_period, :hp2)
|
49
|
+
p0.osc = p0.hp2 - p0.hp1
|
50
|
+
end
|
51
|
+
|
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
|
55
|
+
p0.peak = [p0.osc.abs, 0.991 * p1.peak].max
|
56
|
+
p0.agc = p0.peak.zero? ? p0.osc : p0.osc / p0.peak
|
57
|
+
end
|
58
|
+
|
59
|
+
def compute_inverse_fisher_transform
|
60
|
+
p0.ift = inverse_fisher_transform(p0.agc, scale_factor: 5.0)
|
61
|
+
end
|
62
|
+
|
63
|
+
def compute
|
64
|
+
compute_decycler
|
65
|
+
compute_oscillator
|
66
|
+
compute_automatic_gain_control
|
67
|
+
compute_inverse_fisher_transform
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../indicator"
|
2
4
|
|
3
5
|
module Quant
|
@@ -42,6 +44,15 @@ module Quant
|
|
42
44
|
end
|
43
45
|
|
44
46
|
class DominantCycle < Indicators::Indicator
|
47
|
+
def priority
|
48
|
+
DOMINANT_CYCLES_PRIORITY
|
49
|
+
end
|
50
|
+
|
51
|
+
# Dominant Cycle Indicators should not themselves have a dominant cycle indicator
|
52
|
+
def dominant_cycle_indicator_class
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
45
56
|
def points_class
|
46
57
|
Object.const_get "Quant::Indicators::DominantCycles::#{indicator_name}Point"
|
47
58
|
rescue NameError
|
@@ -76,11 +87,6 @@ module Quant
|
|
76
87
|
[p0.period.to_i, min_period].max
|
77
88
|
end
|
78
89
|
|
79
|
-
def period_points(max_period)
|
80
|
-
extent = [values.size, max_period].min
|
81
|
-
values[-extent, extent]
|
82
|
-
end
|
83
|
-
|
84
90
|
def compute
|
85
91
|
compute_input_data_points
|
86
92
|
compute_quadrature_components
|
@@ -141,4 +147,4 @@ module Quant
|
|
141
147
|
end
|
142
148
|
end
|
143
149
|
end
|
144
|
-
end
|
150
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "../indicator_point"
|
2
4
|
require_relative "dominant_cycle"
|
3
5
|
|
@@ -15,7 +17,7 @@ module Quant
|
|
15
17
|
p0.re = (0.2 * p0.re) + (0.8 * p1.re)
|
16
18
|
p0.im = (0.2 * p0.im) + (0.8 * p1.im)
|
17
19
|
|
18
|
-
p0.inst_period = 360.0 / rad2deg(Math.atan(p0.im/p0.re)) if (p0.im != 0) && (p0.re != 0)
|
20
|
+
p0.inst_period = 360.0 / rad2deg(Math.atan(p0.im / p0.re)) if (p0.im != 0) && (p0.re != 0)
|
19
21
|
|
20
22
|
constrain_period_magnitude_change
|
21
23
|
constrain_period_bars
|
@@ -25,4 +27,4 @@ module Quant
|
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
28
|
-
end
|
30
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "dominant_cycle"
|
2
4
|
|
3
5
|
module Quant
|
@@ -30,10 +32,12 @@ module Quant
|
|
30
32
|
|
31
33
|
p0.accumulator_phase = Math.atan(p0.q1 / p0.i1) unless p0.i1.zero?
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
if p0.i1 < 0 && p0.q1 > 0
|
36
|
+
p0.accumulator_phase = 180.0 - p0.accumulator_phase
|
37
|
+
elsif p0.i1 < 0 && p0.q1 < 0
|
38
|
+
p0.accumulator_phase = 180.0 + p0.accumulator_phase
|
39
|
+
elsif p0.i1 > 0 && p0.q1 < 0
|
40
|
+
p0.accumulator_phase = 360.0 - p0.accumulator_phase
|
37
41
|
end
|
38
42
|
|
39
43
|
p0.delta_phase = p1.accumulator_phase - p0.accumulator_phase
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
class Indicators
|
5
|
+
class FramaPoint < IndicatorPoint
|
6
|
+
attribute :frama, default: :input
|
7
|
+
attribute :dimension, default: 0.0
|
8
|
+
attribute :alpha, default: 0.0
|
9
|
+
end
|
10
|
+
|
11
|
+
# FRAMA (FRactal Adaptive Moving Average). A nonlinear moving average
|
12
|
+
# is derived using the Hurst exponent. It rapidly follows significant
|
13
|
+
# changes in price but becomes very flat in congestion zones so that
|
14
|
+
# bad whipsaw trades can be eliminated.
|
15
|
+
#
|
16
|
+
# SOURCE: http://www.mesasoftware.com/papers/FRAMA.pdf
|
17
|
+
class Frama < Indicator
|
18
|
+
using Quant
|
19
|
+
|
20
|
+
# The max_period is divided into two smaller, equal periods, so must be even
|
21
|
+
def max_period
|
22
|
+
@max_period ||= begin
|
23
|
+
mp = super
|
24
|
+
mp.even? ? mp : mp + 1
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def half_period
|
29
|
+
max_period / 2
|
30
|
+
end
|
31
|
+
|
32
|
+
def compute
|
33
|
+
pp = period_points(max_period).map(&:input)
|
34
|
+
return if pp.size < max_period
|
35
|
+
|
36
|
+
n3 = (pp.maximum - pp.minimum) / max_period
|
37
|
+
|
38
|
+
ppn2 = pp.first(half_period)
|
39
|
+
n2 = (ppn2.maximum - ppn2.minimum) / half_period
|
40
|
+
|
41
|
+
ppn1 = pp.last(half_period)
|
42
|
+
n1 = (ppn1.maximum - ppn1.minimum) / half_period
|
43
|
+
|
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)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|