quantitative 0.1.2 → 0.1.3

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: 5bfe61d98618c7cfb83052426689976be38c45e83fce9217a74a4f0392897b60
4
+ data.tar.gz: 061d9a0179eb913c5f5f5d9f1016998c2cbc0d65da9a774620a3b591f18a1f49
5
5
  SHA512:
6
- metadata.gz: 9ce1d965a8e68f6fae76143dfc619ed98c8a5af559e9f26c67dd7685e60bf9b4cfa3ecf5f43908f01597862cb6d6389cebd40cf5897afed560a9e54ed79ea91e
7
- data.tar.gz: ee8527e43aa4670175d20a20c6037af11227071ff0fe738344c4b9b91d2391335d210a9e5b305153e78edc75ca9acb802b56b2502223107a84366ae40ba55b7f
6
+ metadata.gz: 4154a9e589bcffd15b3415ace9c1ccc6b8d29ba5ecc0180e48259c649a746a157f58bd89aeabad4ffc4ac5edefe6f1892c94adcd001ba5d1f039a5bb46a8a9d0
7
+ data.tar.gz: 7ac2b4661e6c4b0e02fda9389f73b8d007b92e754e8b4fa821bb55dca1d085dfd7e2e3eb6608d520a2e2e6008eb837aa75e7c07c05addc28af577b92fe98f8c3
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.3)
5
5
  oj (~> 3.10)
6
6
 
7
7
  GEM
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,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
@@ -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.3"
5
5
  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.3
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-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -56,7 +56,9 @@ files:
56
56
  - lib/quant/mixins/trig.rb
57
57
  - lib/quant/mixins/weighted_average.rb
58
58
  - lib/quant/refinements/array.rb
59
- - lib/quant/ticks/core.rb
59
+ - lib/quant/security.rb
60
+ - lib/quant/security_class.rb
61
+ - lib/quant/series.rb
60
62
  - lib/quant/ticks/ohlc.rb
61
63
  - lib/quant/ticks/serializers/ohlc.rb
62
64
  - lib/quant/ticks/serializers/spot.rb
@@ -90,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
92
  - !ruby/object:Gem::Version
91
93
  version: '0'
92
94
  requirements: []
93
- rubygems_version: 3.5.5
95
+ rubygems_version: 3.5.6
94
96
  signing_key:
95
97
  specification_version: 4
96
98
  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