quantitative 0.1.2 → 0.1.4
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/config.rb +32 -0
- data/lib/quant/errors.rb +1 -0
- data/lib/quant/indicators/indicator.rb +136 -0
- data/lib/quant/indicators/indicator_point.rb +36 -0
- data/lib/quant/indicators/ma.rb +46 -0
- data/lib/quant/indicators.rb +29 -0
- data/lib/quant/security.rb +80 -0
- data/lib/quant/security_class.rb +116 -0
- data/lib/quant/series.rb +108 -0
- data/lib/quant/settings/indicators.rb +78 -0
- data/lib/quant/settings.rb +48 -0
- data/lib/quant/ticks/ohlc.rb +13 -0
- data/lib/quant/ticks/serializers/ohlc.rb +1 -1
- data/lib/quant/ticks/serializers/tick.rb +1 -1
- data/lib/quant/ticks/spot.rb +2 -1
- data/lib/quant/version.rb +1 -1
- data/lib/quantitative.rb +2 -2
- metadata +13 -4
- data/lib/quant/ticks/core.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 266a7aa51d29b809224fa76091d26c52d58f6e42959aeeb4ca5ffb6376828773
|
4
|
+
data.tar.gz: 5b3ac92706338a77a098e5dee72f3ecf57f2c4032f946533225e6061d07082d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 624d6945e690d51b5afef5ebc7939eb3f444fd7d4d79a31e73df5a42e0f6c351f8d4ac451a455a657c96a07448a0c724ba1481156d2e859ca720c8994328547a
|
7
|
+
data.tar.gz: 51b9fd16424fe1fd1b48c72f350c89cfb9e26e0a082eb750ae864c436b51eeab2142c64744deee93b91aaafe119354f19a4159ecda84b066d75f1d242945a9a2
|
data/Gemfile.lock
CHANGED
data/lib/quant/config.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Config
|
5
|
+
class Config
|
6
|
+
attr_reader :indicators
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@indicators = Settings::Indicators.defaults
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply_indicator_settings(**settings)
|
13
|
+
@indicators.apply_settings(**settings)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.config
|
18
|
+
@config ||= Config.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function
|
23
|
+
|
24
|
+
def config
|
25
|
+
Config.config
|
26
|
+
end
|
27
|
+
|
28
|
+
def configure_indicators(**settings)
|
29
|
+
config.apply_indicator_settings(**settings)
|
30
|
+
yield config.indicators if block_given?
|
31
|
+
end
|
32
|
+
end
|
data/lib/quant/errors.rb
CHANGED
@@ -0,0 +1,136 @@
|
|
1
|
+
module Quant
|
2
|
+
class Indicators
|
3
|
+
class Indicator
|
4
|
+
# include Enumerable
|
5
|
+
|
6
|
+
# # include Mixins::TrendMethods
|
7
|
+
# include Mixins::Trig
|
8
|
+
# include Mixins::WeightedAverage
|
9
|
+
# include Mixins::HilbertTransform
|
10
|
+
# include Mixins::SuperSmoother
|
11
|
+
# include Mixins::Stochastic
|
12
|
+
# include Mixins::FisherTransform
|
13
|
+
# include Mixins::HighPassFilter
|
14
|
+
# include Mixins::Direction
|
15
|
+
# include Mixins::Filters
|
16
|
+
|
17
|
+
# def inspect
|
18
|
+
# "#<#{self.class.name} #{symbol} #{interval} #{points.size} points>"
|
19
|
+
# end
|
20
|
+
|
21
|
+
# def compute
|
22
|
+
# raise NotImplementedError
|
23
|
+
# end
|
24
|
+
|
25
|
+
# def [](index)
|
26
|
+
# points[index]
|
27
|
+
# end
|
28
|
+
|
29
|
+
# def after_append
|
30
|
+
# # NoOp
|
31
|
+
# end
|
32
|
+
|
33
|
+
# def points_class
|
34
|
+
# "Quant::Indicators::#{indicator_name}Point".constantize
|
35
|
+
# end
|
36
|
+
|
37
|
+
# def indicator_name
|
38
|
+
# self.class.name.demodulize
|
39
|
+
# end
|
40
|
+
|
41
|
+
# def warmed_up?
|
42
|
+
# true
|
43
|
+
# end
|
44
|
+
|
45
|
+
# def initial_max_size
|
46
|
+
# value = [series.size, series.max_size].max
|
47
|
+
# value.zero? ? settings.initial_max_size : value
|
48
|
+
# end
|
49
|
+
|
50
|
+
attr_reader :series #, :settings, :max_size, :points, :dc_period
|
51
|
+
# delegate :p0, :p1, :p2, :p3, :prev, :iteration, to: :points
|
52
|
+
# delegate :each, :size, :[], :last, :first, to: :points
|
53
|
+
# delegate :oc2, :high_price, :low_price, :open_price, :close_price, :volume, to: :p0
|
54
|
+
|
55
|
+
def initialize(series:) # settings: Settings::Indicators.defaults, cloning: false)
|
56
|
+
@series = series
|
57
|
+
# @settings = settings
|
58
|
+
# @max_size = initial_max_size
|
59
|
+
# @points = Points.new(indicator: self)
|
60
|
+
# return if cloning
|
61
|
+
|
62
|
+
# after_initialization
|
63
|
+
# parent_series.each { |ohlc| append ohlc }
|
64
|
+
# @points_for_cache = {}
|
65
|
+
# @dc_period = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# def points_for(series:)
|
69
|
+
# @points_for_cache[series] ||= self.class.new(series:, settings:, cloning: true).tap do |indicator|
|
70
|
+
# series.ticks.each { |tick| indicator.points.push(tick.indicators[self]) }
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
|
74
|
+
# # Ticks belong to the first series they're associated with always
|
75
|
+
# # NOTE: No provisions for series merging their ticks to one series!
|
76
|
+
# def parent_series
|
77
|
+
# series.ticks.empty? ? series : series.ticks.first.series
|
78
|
+
# end
|
79
|
+
|
80
|
+
# def after_initialization
|
81
|
+
# # NoOp
|
82
|
+
# end
|
83
|
+
|
84
|
+
# # Returns the last point of the current indicator rather than the entire series
|
85
|
+
# # This is used for indicators that depend on dominant cycle or other indicators
|
86
|
+
# # to compute their data points.
|
87
|
+
# def current_point
|
88
|
+
# points.size - 1
|
89
|
+
# end
|
90
|
+
|
91
|
+
# def dominant_cycles
|
92
|
+
# parent_series.indicators.dominant_cycles
|
93
|
+
# end
|
94
|
+
|
95
|
+
# # Override this method to change source of dominant cycle computation for an indicator
|
96
|
+
# def dominant_cycle_indicator
|
97
|
+
# @dominant_cycle_indicator ||= dominant_cycles.band_pass
|
98
|
+
# end
|
99
|
+
|
100
|
+
# def ensure_not_dominant_cycler_indicator
|
101
|
+
# return unless is_a? Quant::Indicators::DominantCycles::DominantCycle
|
102
|
+
|
103
|
+
# raise 'Dominant Cycle Indicators cannot use the thing they compute!'
|
104
|
+
# end
|
105
|
+
|
106
|
+
# # Sets the dominant cycle period for the current indicator's point
|
107
|
+
# # @dc_period gets set before each #compute call.
|
108
|
+
# def update_dc_period
|
109
|
+
# ensure_not_dominant_cycler_indicator
|
110
|
+
# @dc_period = current_dominant_cycle.period
|
111
|
+
# end
|
112
|
+
|
113
|
+
# # Returns the dominant cycle point for the current indicator's point
|
114
|
+
# def current_dominant_cycle
|
115
|
+
# dominant_cycle_indicator[current_point]
|
116
|
+
# end
|
117
|
+
|
118
|
+
# # Returns the atr point for the current indicator's point
|
119
|
+
# def atr_point
|
120
|
+
# parent_series.indicators.atr[current_point]
|
121
|
+
# end
|
122
|
+
|
123
|
+
# # def dc_period
|
124
|
+
# # dominant_cycle.period.round(0).to_i
|
125
|
+
# # end
|
126
|
+
|
127
|
+
# def <<(ohlc)
|
128
|
+
# points.append(ohlc)
|
129
|
+
# end
|
130
|
+
|
131
|
+
# def append(ohlc)
|
132
|
+
# points.append(ohlc)
|
133
|
+
# end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
class Indicators
|
5
|
+
class IndicatorPoint
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :tick, :source
|
9
|
+
|
10
|
+
def initialize(tick:, source:)
|
11
|
+
@tick = tick
|
12
|
+
@source = @tick.send(source)
|
13
|
+
end
|
14
|
+
|
15
|
+
def volume
|
16
|
+
@tick.base_volume
|
17
|
+
end
|
18
|
+
|
19
|
+
def timestamp
|
20
|
+
@tick.close_timestamp
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize_data_points(indicator:)
|
24
|
+
# NoOp
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_h
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_json(*args)
|
32
|
+
Oj.dump(to_h, *args)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Quant
|
2
|
+
class Indicators
|
3
|
+
class MaPoint < IndicatorPoint
|
4
|
+
attr_accessor :ss, :ema, :osc
|
5
|
+
|
6
|
+
def to_h
|
7
|
+
{
|
8
|
+
"ss" => ss,
|
9
|
+
"ema" => delta_phase,
|
10
|
+
"osc" => osc
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize_data_points(indicator:)
|
15
|
+
@ss = oc2
|
16
|
+
@ema = oc2
|
17
|
+
@osc = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Moving Averages
|
22
|
+
class Ma < Indicator
|
23
|
+
def self.indicator_key
|
24
|
+
"ma"
|
25
|
+
end
|
26
|
+
|
27
|
+
def alpha(period)
|
28
|
+
bars_to_alpha(period)
|
29
|
+
end
|
30
|
+
|
31
|
+
def min_period
|
32
|
+
settings.min_period
|
33
|
+
end
|
34
|
+
|
35
|
+
def period
|
36
|
+
settings.max_period
|
37
|
+
end
|
38
|
+
|
39
|
+
def compute
|
40
|
+
p0.ss = super_smoother p0.oc2, :ss, min_period
|
41
|
+
p0.ema = alpha(period) * p0.oc2 + (1 - alpha(period)) * p1.ema
|
42
|
+
p0.osc = p0.ss - p0.ema
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
# < IndicatorsAccessor
|
5
|
+
class Indicators
|
6
|
+
# def atr; indicator(Indicators::Atr) end
|
7
|
+
# def adx; indicator(Indicators::Adx) end
|
8
|
+
# def cci; indicator(Indicators::Cci) end
|
9
|
+
# def cdi; indicator(Indicators::Cdi) end
|
10
|
+
# def decycler; indicator(Indicators::Decycler) end
|
11
|
+
# def frema; indicator(Indicators::Frema) end
|
12
|
+
# def hilo; indicator(Indicators::HiLo) end
|
13
|
+
# def ma; indicator(Indicators::Ma) end
|
14
|
+
# def mama; indicator(Indicators::Mama) end
|
15
|
+
# def frama; indicator(Indicators::Frama) end
|
16
|
+
# def mesa; indicator(Indicators::Mesa) end
|
17
|
+
# def roofing; indicator(Indicators::Roofing) end
|
18
|
+
# def rsi; indicator(Indicators::Rsi) end
|
19
|
+
# def rrr; indicator(Indicators::Rrr) end
|
20
|
+
# def rrsi; indicator(Indicators::RocketRsi) end
|
21
|
+
# def samo; indicator(Indicators::Samo) end
|
22
|
+
# def snr; indicator(Indicators::Snr) end
|
23
|
+
# def ssf; indicator(Indicators::Ssf) end
|
24
|
+
# def volume; indicator(Indicators::VolumeSsf) end
|
25
|
+
# def vol; indicator(Indicators::Vol) end
|
26
|
+
# def vrsi; indicator(Indicators::VolumeRsi) end
|
27
|
+
# def weibull; indicator(Indicators::Weibull) end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "security_class"
|
4
|
+
|
5
|
+
module Quant
|
6
|
+
# A +Security+ is a representation of a financial instrument such as a stock, option, future, or currency.
|
7
|
+
# It is used to represent the instrument that is being traded, analyzed, or managed.
|
8
|
+
# @example
|
9
|
+
# security = Quant::Security.new(symbol: "AAPL", name: "Apple Inc.", security_class: :stock, exchange: "NASDAQ")
|
10
|
+
# security.symbol # => "AAPL"
|
11
|
+
# security.name # => "Apple Inc."
|
12
|
+
# security.stock? # => true
|
13
|
+
# security.option? # => false
|
14
|
+
# security.future? # => false
|
15
|
+
# security.currency? # => false
|
16
|
+
# security.exchange # => "NASDAQ"
|
17
|
+
# security.to_h # => { "s" => "AAPL", "n" => "Apple Inc.", "sc" => "stock", "x" => "NASDAQ" }
|
18
|
+
class Security
|
19
|
+
attr_reader :symbol, :name, :security_class, :id, :exchange, :source, :meta, :created_at, :updated_at
|
20
|
+
|
21
|
+
def initialize(
|
22
|
+
symbol:,
|
23
|
+
name: nil,
|
24
|
+
id: nil,
|
25
|
+
active: true,
|
26
|
+
tradeable: true,
|
27
|
+
exchange: nil,
|
28
|
+
source: nil,
|
29
|
+
security_class: nil,
|
30
|
+
created_at: Quant.current_time,
|
31
|
+
updated_at: Quant.current_time,
|
32
|
+
meta: {}
|
33
|
+
)
|
34
|
+
raise ArgumentError, "symbol is required" unless symbol
|
35
|
+
|
36
|
+
@symbol = symbol.to_s.upcase
|
37
|
+
@name = name
|
38
|
+
@id = id
|
39
|
+
@tradeable = tradeable
|
40
|
+
@active = active
|
41
|
+
@exchange = exchange
|
42
|
+
@source = source
|
43
|
+
@security_class = SecurityClass.new(security_class)
|
44
|
+
@created_at = created_at
|
45
|
+
@updated_at = updated_at
|
46
|
+
@meta = meta
|
47
|
+
end
|
48
|
+
|
49
|
+
def active?
|
50
|
+
!!@active
|
51
|
+
end
|
52
|
+
|
53
|
+
def tradeable?
|
54
|
+
!!@tradeable
|
55
|
+
end
|
56
|
+
|
57
|
+
SecurityClass::CLASSES.each do |class_name|
|
58
|
+
define_method("#{class_name}?") do
|
59
|
+
security_class == class_name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_h(full: false)
|
64
|
+
return { "s" => symbol } unless full
|
65
|
+
|
66
|
+
{ "s" => symbol,
|
67
|
+
"n" => name,
|
68
|
+
"id" => id,
|
69
|
+
"t" => tradeable?,
|
70
|
+
"a" => active?,
|
71
|
+
"x" => exchange,
|
72
|
+
"sc" => security_class.to_s,
|
73
|
+
"src" => source.to_s }
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_json(*args, full: false)
|
77
|
+
Oj.dump(to_h(full: full), *args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
# Stocks (Equities): Represent ownership in a company. Stockholders are entitled to a share
|
5
|
+
# of the company's profits and have voting rights at shareholder meetings.
|
6
|
+
|
7
|
+
# Bonds (Fixed-Income Securities): Debt instruments where an investor lends money to an entity
|
8
|
+
# (government or corporation) in exchange for periodic interest payments and the return of
|
9
|
+
# the principal amount at maturity.
|
10
|
+
|
11
|
+
# Mutual Funds: Pooled funds managed by an investment company. Investors buy shares in the mutual
|
12
|
+
# fund, and the fund invests in a diversified portfolio of stocks, bonds, or other securities.
|
13
|
+
|
14
|
+
# Exchange-Traded Funds (ETFs): Similar to mutual funds but traded on stock exchanges.
|
15
|
+
# ETFs can track an index, commodity, bonds, or a basket of assets.
|
16
|
+
|
17
|
+
# Options: Derivative securities that give the holder the right (but not the obligation) to buy
|
18
|
+
# or sell an underlying asset at a predetermined price before or at expiration.
|
19
|
+
|
20
|
+
# Futures: Contracts that obligate the buyer to purchase or the seller to sell an
|
21
|
+
# asset at a predetermined future date and price.
|
22
|
+
|
23
|
+
# Real Estate Investment Trusts (REITs): Companies that own, operate, or finance income-generating
|
24
|
+
# real estate. Investors can buy shares in a REIT, which provides them with a share of the
|
25
|
+
# income produced by the real estate.
|
26
|
+
|
27
|
+
# Cryptocurrencies: Digital or virtual currencies that use cryptography for security and operate on
|
28
|
+
# decentralized networks, typically based on blockchain technology. Examples include Bitcoin, Ethereum, and Ripple.
|
29
|
+
|
30
|
+
# Preferred Stock: A type of stock that has priority over common stock in terms of dividend
|
31
|
+
# payments and asset distribution in the event of liquidation.
|
32
|
+
|
33
|
+
# Treasury Securities: Issued by the government to raise funds. Types include Treasury bills (T-bills),
|
34
|
+
# Treasury notes (T-notes), and Treasury bonds (T-bonds).
|
35
|
+
|
36
|
+
# Mortgage-Backed Securities (MBS): Securities that represent an ownership interest in a pool of mortgage loans.
|
37
|
+
# Investors receive payments based on the interest and principal of the underlying loans.
|
38
|
+
|
39
|
+
# Commodities: Physical goods such as gold, silver, oil, or agricultural products, traded on commodity exchanges.
|
40
|
+
|
41
|
+
# Foreign Exchange (Forex): The market where currencies are traded. Investors can buy and sell currencies to
|
42
|
+
# profit from changes in exchange rates.
|
43
|
+
class SecurityClass
|
44
|
+
CLASSES = %i(
|
45
|
+
bond
|
46
|
+
commodity
|
47
|
+
cryptocurrency
|
48
|
+
etf
|
49
|
+
forex
|
50
|
+
future
|
51
|
+
mbs
|
52
|
+
mutual_fund
|
53
|
+
option
|
54
|
+
preferred_stock
|
55
|
+
reit
|
56
|
+
stock
|
57
|
+
treasury_note
|
58
|
+
).freeze
|
59
|
+
|
60
|
+
attr_reader :security_class
|
61
|
+
|
62
|
+
def initialize(name)
|
63
|
+
return if @security_class = from_standard(name)
|
64
|
+
|
65
|
+
@security_class = from_alternate(name.to_s.downcase.to_sym) unless name.nil?
|
66
|
+
raise_unknown_security_class_error(name) unless security_class
|
67
|
+
end
|
68
|
+
|
69
|
+
CLASSES.each do |class_name|
|
70
|
+
define_method("#{class_name}?") do
|
71
|
+
security_class == class_name
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def raise_unknown_security_class_error(name)
|
76
|
+
raise SecurityClassError, "Unknown security class: #{name.inspect}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_s
|
80
|
+
security_class.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_h
|
84
|
+
{ "sc" => security_class }
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_json(*args)
|
88
|
+
Oj.dump(to_h, *args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def ==(other)
|
92
|
+
case other
|
93
|
+
when String then from_alternate(other.to_sym) == security_class
|
94
|
+
when Symbol then from_alternate(other) == security_class
|
95
|
+
when SecurityClass then other.security_class == security_class
|
96
|
+
else
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
ALTERNATE_NAMES = {
|
104
|
+
us_equity: :stock,
|
105
|
+
crypto: :cryptocurrency,
|
106
|
+
}.freeze
|
107
|
+
|
108
|
+
def from_standard(name)
|
109
|
+
name if CLASSES.include?(name)
|
110
|
+
end
|
111
|
+
|
112
|
+
def from_alternate(alt_name)
|
113
|
+
from_standard(alt_name) || ALTERNATE_NAMES[alt_name]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/quant/series.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
# Ticks belong to the first series they're associated with always.
|
5
|
+
# There are no provisions for series merging their ticks to one series!
|
6
|
+
# Indicators will be computed against the parent series of a list of ticks, so we
|
7
|
+
# can safely work with subsets of a series and indicators will compute just once.
|
8
|
+
class Series
|
9
|
+
include Enumerable
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def self.from_file(filename:, symbol:, interval:, folder: nil)
|
13
|
+
symbol = symbol.to_s.upcase
|
14
|
+
interval = Interval[interval]
|
15
|
+
|
16
|
+
filename = Rails.root.join("historical", folder, "#{symbol.upcase}.txt") if filename.nil?
|
17
|
+
raise "File #{filename} does not exist" unless File.exist?(filename)
|
18
|
+
|
19
|
+
lines = File.read(filename).split("\n")
|
20
|
+
ticks = lines.map{ |line| Quant::Ticks::OHLC.from_json(line) }
|
21
|
+
|
22
|
+
from_ticks(symbol: symbol, interval: interval, ticks: ticks)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_json(symbol:, interval:, json:)
|
26
|
+
from_hash symbol: symbol, interval: interval, hash: Oj.load(json)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.from_hash(symbol:, interval:, hash:)
|
30
|
+
ticks = hash.map { |tick_hash| Quant::Ticks::OHLC.from(tick_hash) }
|
31
|
+
from_ticks(symbol: symbol, interval: interval, ticks: ticks)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.from_ticks(symbol:, interval:, ticks:)
|
35
|
+
ticks = ticks.sort_by(&:close_timestamp)
|
36
|
+
|
37
|
+
new(symbol: symbol, interval: interval).tap do |series|
|
38
|
+
ticks.each { |tick| series << tick }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :symbol, :interval, :ticks
|
43
|
+
|
44
|
+
def initialize(symbol:, interval:)
|
45
|
+
@symbol = symbol
|
46
|
+
@interval = interval
|
47
|
+
@ticks = []
|
48
|
+
end
|
49
|
+
|
50
|
+
def limit_iterations(start_iteration, stop_iteration)
|
51
|
+
selected_ticks = ticks[start_iteration..stop_iteration]
|
52
|
+
return self if selected_ticks.size == ticks.size
|
53
|
+
|
54
|
+
self.class.from_ticks(symbol: symbol, interval: interval, ticks: selected_ticks)
|
55
|
+
end
|
56
|
+
|
57
|
+
def limit(period)
|
58
|
+
selected_ticks = ticks.select{ |tick| period.cover?(tick.close_timestamp) }
|
59
|
+
return self if selected_ticks.size == ticks.size
|
60
|
+
|
61
|
+
self.class.from_ticks(symbol: symbol, interval: interval, ticks: selected_ticks)
|
62
|
+
end
|
63
|
+
|
64
|
+
def_delegator :@ticks, :[]
|
65
|
+
def_delegator :@ticks, :size
|
66
|
+
def_delegator :@ticks, :each
|
67
|
+
def_delegator :@ticks, :select
|
68
|
+
def_delegator :@ticks, :select!
|
69
|
+
def_delegator :@ticks, :reject
|
70
|
+
def_delegator :@ticks, :reject!
|
71
|
+
def_delegator :@ticks, :first
|
72
|
+
def_delegator :@ticks, :last
|
73
|
+
|
74
|
+
def highest
|
75
|
+
ticks.max_by(&:high_price)
|
76
|
+
end
|
77
|
+
|
78
|
+
def lowest
|
79
|
+
ticks.min_by(&:low_price)
|
80
|
+
end
|
81
|
+
|
82
|
+
def ==(other)
|
83
|
+
[symbol, interval, ticks] == [other.symbol, other.interval, other.ticks]
|
84
|
+
end
|
85
|
+
|
86
|
+
def dup
|
87
|
+
self.class.from_ticks(symbol: symbol, interval: interval, ticks: ticks)
|
88
|
+
end
|
89
|
+
|
90
|
+
def inspect
|
91
|
+
"#<#{self.class.name} symbol=#{symbol} interval=#{interval} ticks=#{ticks.size}>"
|
92
|
+
end
|
93
|
+
|
94
|
+
def <<(tick)
|
95
|
+
@ticks << tick.assign_series(self)
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_h
|
99
|
+
{ "symbol" => symbol,
|
100
|
+
"interval" => interval,
|
101
|
+
"ticks" => ticks.map(&:to_h) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def to_json(*args)
|
105
|
+
Oj.dump(to_h, *args)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Quant
|
4
|
+
module Settings
|
5
|
+
# Indicator settings provide a way to configure the default settings for indicators.
|
6
|
+
# Many of the indicators are built in adaptive measuring of the dominant cycle and these settings
|
7
|
+
# provide a way to configure your choices for the indicators. The default values come from various
|
8
|
+
# papers and books on the subject of technical analysis by John Ehlers where he variously suggests
|
9
|
+
# a minimum period of 8 or 10 and a max period of 48.
|
10
|
+
#
|
11
|
+
# The half period is the average of the max_period and min_period.
|
12
|
+
# The micro period comes from Ehler's writings on Swami charts and auto-correlation computations, which
|
13
|
+
# is a period of 3 bars. It is useful enough in various indicators to be its own setting.
|
14
|
+
#
|
15
|
+
# The dominant cycle kind is the kind of dominant cycle to use in the indicator. The default is +:settings+
|
16
|
+
# which means the dominant cycle is whatever the +max_period+ is set to. It is not adaptive when configured
|
17
|
+
# this way. The other kinds are adaptive and are computed from the series data. The choices are:
|
18
|
+
# * +:settings+ - the max_period is the dominant cycle and is not adaptive
|
19
|
+
# * +:band_pass+ - The zero crossings of the band pass filter are used to compute the dominant cycle
|
20
|
+
# * +:auto_correlation_reversal+ - The dominant cycle is computed from the auto-correlation of the series.
|
21
|
+
# * +:homodyne+ - The dominant cycle is computed from the homodyne discriminator.
|
22
|
+
# * +:differential+ - The dominant cycle is computed from the differential discriminator.
|
23
|
+
# * +:phase_accumulator+ - The dominant cycle is computed from the phase accumulator.
|
24
|
+
#
|
25
|
+
# All of the above are adaptive and are computed from the series data and are described in John Ehlers' books
|
26
|
+
# and published papers.
|
27
|
+
#
|
28
|
+
# Pivot kinds are started as the classic pivot points and then expanded to include other kinds of bands that
|
29
|
+
# follow along with price action such as Donchian channels, Fibonacci bands, Bollinger bands, Keltner bands,
|
30
|
+
# etc. The choices are as follows:
|
31
|
+
# * +:pivot+ - Classic pivot points
|
32
|
+
# * +:donchian+ - Donchian channels
|
33
|
+
# * +:fibbonacci+ - Fibonacci bands
|
34
|
+
# * +:woodie+ - Woodie's pivot points
|
35
|
+
# * +:classic+ - Classic pivot points
|
36
|
+
# * +:camarilla+ - Camarilla pivot points
|
37
|
+
# * +:demark+ - Demark pivot points
|
38
|
+
# * +:murrey+ - Murrey math pivot points
|
39
|
+
# * +:keltner+ - Keltner bands
|
40
|
+
# * +:bollinger+ - Bollinger bands
|
41
|
+
# * +:guppy+ - Guppy bands
|
42
|
+
# * +:atr+ - ATR bands
|
43
|
+
#
|
44
|
+
class Indicators
|
45
|
+
# Returns an instance of the settings for indicators configured with defaults derived from
|
46
|
+
# defined constants in the +Quant::Settings+ module.
|
47
|
+
def self.defaults
|
48
|
+
new
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_accessor :max_period, :min_period, :half_period, :micro_period
|
52
|
+
attr_accessor :dominant_cycle_kind, :pivot_kind
|
53
|
+
|
54
|
+
def initialize(**settings)
|
55
|
+
@max_period = settings[:max_period] || Settings::MAX_PERIOD
|
56
|
+
@min_period = settings[:min_period] || Settings::MIN_PERIOD
|
57
|
+
@half_period = settings[:half_period] || compute_half_period
|
58
|
+
@micro_period = settings[:micro_period] || Settings::MICRO_PERIOD
|
59
|
+
|
60
|
+
@dominant_cycle_kind = settings[:dominant_cycle_kind] || Settings::DOMINANT_CYCLE_KINDS.first
|
61
|
+
@pivot_kind = settings[:pivot_kind] || Settings::PIVOT_KINDS.first
|
62
|
+
end
|
63
|
+
|
64
|
+
def apply_settings(**settings)
|
65
|
+
@max_period = settings.fetch(:max_period, @max_period)
|
66
|
+
@min_period = settings.fetch(:min_period, @min_period)
|
67
|
+
@half_period = settings.fetch(:half_period, @half_period || compute_half_period)
|
68
|
+
@micro_period = settings.fetch(:micro_period, @micro_period)
|
69
|
+
@dominant_cycle_kind = settings.fetch(:dominant_cycle_kind, @dominant_cycle_kind)
|
70
|
+
@pivot_kind = settings.fetch(:pivot_kind, @pivot_kind)
|
71
|
+
end
|
72
|
+
|
73
|
+
def compute_half_period
|
74
|
+
(max_period + min_period) / 2
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Quant
|
2
|
+
module Settings
|
3
|
+
MAX_PERIOD = 48
|
4
|
+
MIN_PERIOD = 10
|
5
|
+
HALF_PERIOD = (MAX_PERIOD + MIN_PERIOD) / 2
|
6
|
+
MICRO_PERIOD = 3
|
7
|
+
|
8
|
+
PIVOT_KINDS = %i(
|
9
|
+
pivot
|
10
|
+
donchian
|
11
|
+
fibbonacci
|
12
|
+
woodie
|
13
|
+
classic
|
14
|
+
camarilla
|
15
|
+
demark
|
16
|
+
murrey
|
17
|
+
keltner
|
18
|
+
bollinger
|
19
|
+
guppy
|
20
|
+
atr
|
21
|
+
).freeze
|
22
|
+
|
23
|
+
DOMINANT_CYCLE_KINDS = %i(
|
24
|
+
settings
|
25
|
+
band_pass
|
26
|
+
auto_correlation_reversal
|
27
|
+
homodyne
|
28
|
+
differential
|
29
|
+
phase_accumulator
|
30
|
+
).freeze
|
31
|
+
|
32
|
+
# ---- Risk Management Ratio Settings ----
|
33
|
+
# Risk Reward Breakeven Win Rate %
|
34
|
+
# 50 1 98%
|
35
|
+
# 10 1 91%
|
36
|
+
# 5 1 83%
|
37
|
+
# 3 1 75%
|
38
|
+
# 2 1 67%
|
39
|
+
# 1 1 50%
|
40
|
+
# 1 2 33%
|
41
|
+
# 1 3 25%
|
42
|
+
# 1 5 17%
|
43
|
+
# 1 10 9%
|
44
|
+
# 1 50 2%
|
45
|
+
PROFIT_TARGET_PCT = 0.03
|
46
|
+
STOP_LOSS_PCT = 0.01
|
47
|
+
end
|
48
|
+
end
|
data/lib/quant/ticks/ohlc.rb
CHANGED
@@ -4,6 +4,10 @@ require_relative "tick"
|
|
4
4
|
|
5
5
|
module Quant
|
6
6
|
module Ticks
|
7
|
+
# An +OHLC+ is a bar or candle for a point in time that has an open, high, low, and close price.
|
8
|
+
# It is the most common form of a +Tick+ and is usually used to representa time period such as a
|
9
|
+
# minute, hour, day, week, or month. The +OHLC+ is used to represent the price action of an asset
|
10
|
+
# The interval of the +OHLC+ is the time period that the +OHLC+ represents, such has hourly, daily, weekly, etc.
|
7
11
|
class OHLC < Tick
|
8
12
|
include TimeMethods
|
9
13
|
|
@@ -67,6 +71,11 @@ module Quant
|
|
67
71
|
[open_timestamp, close_timestamp] == [other.open_timestamp, other.close_timestamp]
|
68
72
|
end
|
69
73
|
|
74
|
+
# Two OHLC ticks are equal if their interval, close_timestamp, and close_price are equal.
|
75
|
+
def ==(other)
|
76
|
+
[interval, close_timestamp, close_price] == [other.interval, other.close_timestamp, other.close_price]
|
77
|
+
end
|
78
|
+
|
70
79
|
# Returns the percent daily price change from open_price to close_price, ranging from 0.0 to 1.0.
|
71
80
|
# A positive value means the price increased, and a negative value means the price decreased.
|
72
81
|
# A value of 0.0 means no change.
|
@@ -120,6 +129,10 @@ module Quant
|
|
120
129
|
|
121
130
|
body_ratio < 0.025 && head_ratio > 1.0 && tail_ratio > 1.0
|
122
131
|
end
|
132
|
+
|
133
|
+
def inspect
|
134
|
+
"#<#{self.class.name} #{interval} ct=#{close_timestamp.iso8601} o=#{open_price} h=#{high_price} l=#{low_price} c=#{close_price} v=#{volume}>"
|
135
|
+
end
|
123
136
|
end
|
124
137
|
end
|
125
138
|
end
|
data/lib/quant/ticks/spot.rb
CHANGED
@@ -60,6 +60,7 @@ module Quant
|
|
60
60
|
alias delta close_price
|
61
61
|
alias volume base_volume
|
62
62
|
|
63
|
+
# Two ticks are equal if they have the same close price and close timestamp.
|
63
64
|
def ==(other)
|
64
65
|
[close_price, close_timestamp] == [other.close_price, other.close_timestamp]
|
65
66
|
end
|
@@ -72,7 +73,7 @@ module Quant
|
|
72
73
|
end
|
73
74
|
|
74
75
|
def inspect
|
75
|
-
"#<#{self.class.name}
|
76
|
+
"#<#{self.class.name} #{interval} ct=#{close_timestamp} c=#{close_price.to_f} v=#{volume}>"
|
76
77
|
end
|
77
78
|
end
|
78
79
|
end
|
data/lib/quant/version.rb
CHANGED
data/lib/quantitative.rb
CHANGED
@@ -12,6 +12,6 @@ quant_folder = File.join(lib_folder, "quant")
|
|
12
12
|
Dir.glob(File.join(quant_folder, "*.rb")).each { |fn| require fn }
|
13
13
|
|
14
14
|
# require sub-folders and their sub-folders
|
15
|
-
%w(refinements ticks).each do |sub_folder|
|
15
|
+
%w(refinements settings ticks indicators).each do |sub_folder|
|
16
16
|
Dir.glob(File.join(quant_folder, sub_folder, "**/*.rb")).each { |fn| require fn }
|
17
|
-
end
|
17
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quantitative
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Lang
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -44,7 +44,12 @@ files:
|
|
44
44
|
- LICENSE
|
45
45
|
- README.md
|
46
46
|
- Rakefile
|
47
|
+
- lib/quant/config.rb
|
47
48
|
- lib/quant/errors.rb
|
49
|
+
- lib/quant/indicators.rb
|
50
|
+
- lib/quant/indicators/indicator.rb
|
51
|
+
- lib/quant/indicators/indicator_point.rb
|
52
|
+
- lib/quant/indicators/ma.rb
|
48
53
|
- lib/quant/interval.rb
|
49
54
|
- lib/quant/mixins/direction.rb
|
50
55
|
- lib/quant/mixins/filters.rb
|
@@ -56,7 +61,11 @@ files:
|
|
56
61
|
- lib/quant/mixins/trig.rb
|
57
62
|
- lib/quant/mixins/weighted_average.rb
|
58
63
|
- lib/quant/refinements/array.rb
|
59
|
-
- lib/quant/
|
64
|
+
- lib/quant/security.rb
|
65
|
+
- lib/quant/security_class.rb
|
66
|
+
- lib/quant/series.rb
|
67
|
+
- lib/quant/settings.rb
|
68
|
+
- lib/quant/settings/indicators.rb
|
60
69
|
- lib/quant/ticks/ohlc.rb
|
61
70
|
- lib/quant/ticks/serializers/ohlc.rb
|
62
71
|
- lib/quant/ticks/serializers/spot.rb
|
@@ -90,7 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
99
|
- !ruby/object:Gem::Version
|
91
100
|
version: '0'
|
92
101
|
requirements: []
|
93
|
-
rubygems_version: 3.5.
|
102
|
+
rubygems_version: 3.5.6
|
94
103
|
signing_key:
|
95
104
|
specification_version: 4
|
96
105
|
summary: Quantitative and statistical tools written for Ruby 3.x for trading and finance.
|