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 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