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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19fe65cd5c50b0b503abaa0e7c5be3949a284cf5a2b537d79b9cbdd69448041d
4
- data.tar.gz: fe03ed6b2ddef6609cee31547cd84c1eac948bb8d10efb8c24c316673c693a62
3
+ metadata.gz: 266a7aa51d29b809224fa76091d26c52d58f6e42959aeeb4ca5ffb6376828773
4
+ data.tar.gz: 5b3ac92706338a77a098e5dee72f3ecf57f2c4032f946533225e6061d07082d5
5
5
  SHA512:
6
- metadata.gz: 9ce1d965a8e68f6fae76143dfc619ed98c8a5af559e9f26c67dd7685e60bf9b4cfa3ecf5f43908f01597862cb6d6389cebd40cf5897afed560a9e54ed79ea91e
7
- data.tar.gz: ee8527e43aa4670175d20a20c6037af11227071ff0fe738344c4b9b91d2391335d210a9e5b305153e78edc75ca9acb802b56b2502223107a84366ae40ba55b7f
6
+ metadata.gz: 624d6945e690d51b5afef5ebc7939eb3f444fd7d4d79a31e73df5a42e0f6c351f8d4ac451a455a657c96a07448a0c724ba1481156d2e859ca720c8994328547a
7
+ data.tar.gz: 51b9fd16424fe1fd1b48c72f350c89cfb9e26e0a082eb750ae864c436b51eeab2142c64744deee93b91aaafe119354f19a4159ecda84b066d75f1d242945a9a2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- quantitative (0.1.2)
4
+ quantitative (0.1.4)
5
5
  oj (~> 3.10)
6
6
 
7
7
  GEM
@@ -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
@@ -5,4 +5,5 @@ module Quant
5
5
  class InvalidInterval < Error; end
6
6
  class InvalidResolution < Error; end
7
7
  class ArrayMaxSizeError < Error; end
8
+ class SecurityClassError < Error; end
8
9
  end
@@ -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
@@ -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
@@ -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
@@ -3,7 +3,7 @@
3
3
  module Quant
4
4
  module Ticks
5
5
  module Serializers
6
- class OHLC < Tick
6
+ module OHLC
7
7
  # Returns a +Quant::Ticks::Tick+ from a valid JSON +String+.
8
8
  # @param json [String]
9
9
  # @param tick_class [Quant::Ticks::Tick]
@@ -39,7 +39,7 @@ module Quant
39
39
  Oj.dump to_h(tick)
40
40
  end
41
41
 
42
- # Returns a Ruby +Hash+ comprised of the tick's key properties.
42
+ # Returns a Ruby +Hash+ comprised of the key properties for the tick.
43
43
  # @param tick [Quant::Ticks::Tick]
44
44
  # @return [Hash]
45
45
  # @example
@@ -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} cp=#{close_price.to_f} ct=#{close_timestamp.strftime("%Y-%m-%d")} iv=#{interval.to_s} v=#{volume}>"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Quant
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.4"
5
5
  end
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.2
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-13 00:00:00.000000000 Z
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/ticks/core.rb
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.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.
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Quant
4
- module Ticks
5
- class Core
6
- end
7
- end
8
- end