quantitative 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|