cotcube-helpers 0.2.0 → 0.2.1.1

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: 38df15256f996b15dfeb1755f7464e932a98541e62aa43efda0a264be58bafb2
4
- data.tar.gz: cd336b6a91442a6d3bbace66ec8a25049ae979ab78eef03d1c8b44a548da8023
3
+ metadata.gz: 1228015a21f3920ec5b1d87e3c5918e3c69e0a577746b6a18579ae51e1528ce2
4
+ data.tar.gz: 431798647c5d4e9d5a7a32816636bd768c4e0e9c70d789dd491c374fde75f768
5
5
  SHA512:
6
- metadata.gz: bcfc562ab118939e7c23dc37b89fee881871552b6b7b17ec7066cb4a2028fa2661e50cd5512725da28332dd1b983d6817d416d31ba05059dd15d824a0e101f7c
7
- data.tar.gz: 9ca3305120eb630283e303206b764cf9d8dd533ceaf0a2aa044f702fa2f2a01055ff70a4331ec865d854580b1dcf367f365c5addbb988605537c0ee9b9b6fad9
6
+ metadata.gz: b931c2dad6f54a1f2de64590e703c1be03ed2330691a299c2e4d4a64f5cb5d20d3be9ba44584866db66116160c2d01c87cd17f7d84f5ae82ae2668f5a8cf0bae
7
+ data.tar.gz: 18b50ba7237d373dec19d3144814ad55c5ec3d36274213c5749041bbf66ce008461d2be89cda0240245529e29ed383110352d945af4a82cb761d53a6d702f09a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 0.2.1.1 (November 10, 2021)
2
+ - Bump version to 0.2.1.
3
+ - Bump version to 0.2.1.
4
+
5
+ ## 0.2.1 (November 10, 2021)
6
+ - added new class 'dataclient' for communication with dataproxy
7
+ - added .translate_ib_contract
8
+
1
9
  ## 0.2.0 (November 07, 2021)
2
10
  - added module Candlestick_Recognition
3
11
  - added instance_inspect method to 'scan' objects for contents of instance variables
data/Gemfile CHANGED
@@ -4,3 +4,5 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in bitangent.gemspec
6
6
  gemspec
7
+ gem 'parallel'
8
+ gem 'bunny'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1.1
@@ -33,6 +33,12 @@ module Cotcube
33
33
 
34
34
  DATE_FMT = '%Y-%m-%d'
35
35
 
36
+ # Simple mapper to get from MONTH to LETTER
37
+ LETTERS = { "JAN"=> "F", "FEB"=> "G", "MAR"=> "H",
38
+ "APR"=> "J", "MAY"=> "K", "JUN"=> "M",
39
+ "JUL"=> "N", "AUG"=> "Q", "SEP"=> "U",
40
+ "OCT"=> "V", "NOV"=> "X", "DEC"=> "Z" }
41
+
36
42
  end
37
43
  end
38
44
 
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env ruby
2
+ require 'bunny'
3
+ require 'json'
4
+
5
+ module Cotcube
6
+ module Helpers
7
+ class DataClient
8
+
9
+ def initialize
10
+ @connection = Bunny.new(automatically_recover: true)
11
+ @connection.start
12
+
13
+ @channel = connection.create_channel
14
+ @exchange = channel.direct('dataproxy_commands', auto_delete: true)
15
+ @requests = {}
16
+ @persistent = { depth: {}, realtimebars: {}, ticks: {} }
17
+ @response = nil
18
+
19
+ setup_reply_queue
20
+ end
21
+
22
+ def help
23
+ puts "The following commands are available:\n\n"\
24
+ "\tcontracts = client.get_contracts(symbol:)\n"\
25
+ "\tbars = client.get_historical(contract:, duration:, interval:, before: nil)"\
26
+ "\trequest_id = client.start_realtimebars(contract: )\n"\
27
+ "\t client.stop_realtimebars(request_id: )\n"\
28
+ "\trequest_id = client.start_ticks(contract: )\n"\
29
+ "\t client.stop_ticks(request_id: )\n"
30
+ end
31
+
32
+ def send_command(command, timeout: 5)
33
+ command = { command: command.to_s } unless command.is_a? Hash
34
+ command[:timestamp] ||= (Time.now.to_f * 1000).to_i
35
+ request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
36
+ requests[request_id] = { request: command, id: request_id }
37
+
38
+ exchange.publish(command.to_json,
39
+ content_type: 'application/json',
40
+ routing_key: 'dataproxy_commands',
41
+ correlation_id: request_id,
42
+ reply_to: reply_queue.name)
43
+
44
+ # wait for the signal to continue the execution
45
+ lock.synchronize {
46
+ condition.wait(lock, timeout)
47
+ }
48
+
49
+ response
50
+ end
51
+
52
+ def stop
53
+ channel.close
54
+ connection.close
55
+ end
56
+
57
+
58
+ def get_contracts(symbol: )
59
+ send_command( { command: :get_contracts, symbol: symbol } )
60
+ end
61
+
62
+ def get_historical(contract:, interval:, duration: nil, before: nil, rth_only: false, based_on: :trades)
63
+ # rth.true? means data outside of rth is skipped
64
+ rth_only = rth_only ? 1 : 0
65
+ default_durations = {
66
+ sec1: '30_M',
67
+ sec5: '2_H',
68
+ sec15: '6_H',
69
+ sec30: '12_H',
70
+ min1: '1_D',
71
+ min2: '2_D',
72
+ min5: '5_D',
73
+ min15: '1_W',
74
+ min30: '1_W',
75
+ hour1: '1_W',
76
+ day1: '1_Y'
77
+ }
78
+ raise "Invalid interval '#{interval}', should be in '#{default_durations.keys}'." unless default_durations.keys.include? interval
79
+ # TODO: Check for valid duration specification
80
+ duration ||= default_durations[interval]
81
+ send_command( {
82
+ command: :historical,
83
+ contract: contract,
84
+ interval: interval,
85
+ duration: duration,
86
+ based_on: based_on.to_s.upcase,
87
+ rth_only: rth_only,
88
+ before: nil
89
+ }, timeout: 20 )
90
+ end
91
+
92
+ def start_persistent(contract:, type: :realtimebars, &block)
93
+ unless %i[ depth ticks realtimebars].include? type.to_sym
94
+ puts "ERROR: Inappropriate type in stop_realtimebars with #{type}"
95
+ return false
96
+ end
97
+
98
+ ib_contract = Cotcube::Helpers.get_ib_contract(contract)
99
+ exchange = channel.fanout( "dataproxy_#{type.to_s}_#{contract}", auto_delete: true)
100
+ queue = channel.queue('', exclusive: true, auto_delete: true)
101
+ queue.bind(exchange)
102
+ block ||= ->(bar){ puts "#{bar}" }
103
+ queue.subscribe do |_delivery_info, properties, payload|
104
+ block.call(JSON.parse(payload, symbolize_names: true))
105
+ end
106
+ command = { command: type, contract: contract, con_id: ib_contract[:con_id], delivery: queue.name, exchange: exchange.name }
107
+ persistent[type][queue.name] = command
108
+ persistent[type][queue.name][:queue] = queue
109
+ send_command(command)
110
+ end
111
+
112
+ def stop_persistent(contract:, type: :realtimebars )
113
+ unless %i[ depth ticks realtimebars].include? type.to_sym
114
+ puts "ERROR: Inappropriate type in stop_realtimebars with #{type}"
115
+ return false
116
+ end
117
+ ib_contract = Cotcube::Helpers.get_ib_contract(contract)
118
+ command = { command: "stop_#{type}", contract: contract, con_id: ib_contract[:con_id] }
119
+ send_command(command)
120
+ end
121
+
122
+ attr_accessor :response
123
+ attr_reader :lock, :condition
124
+
125
+ private
126
+ attr_reader :call_id, :connection, :requests, :persistent,
127
+ :channel, :server_queue_name, :reply_queue, :exchange
128
+
129
+
130
+ def setup_reply_queue
131
+ @lock = Mutex.new
132
+ @condition = ConditionVariable.new
133
+ that = self
134
+ @reply_queue = channel.queue('', exclusive: true, auto_delete: true)
135
+ @reply_queue.bind(channel.exchange('dataproxy_replies', auto_delete: true), routing_key: @reply_queue.name)
136
+
137
+ reply_queue.subscribe do |delivery_info, properties, payload|
138
+
139
+ __id__ = properties[:correlation_id]
140
+
141
+ if __id__.nil?
142
+ puts "Received without __id__: #{delivery_info.map{|k,v| "#{k}\t#{v}"}.join("\n")
143
+ }\n\n#{properties .map{|k,v| "#{k}\t#{v}"}.join("\n")
144
+ }\n\n#{JSON.parse(payload).map{|k,v| "#{k}\t#{v}"}.join("\n")}"
145
+
146
+ elsif requests[__id__].nil?
147
+ puts "Received non-matching response: \n\n#{_delivery_info}\n\n#{properties}\n\n#{payload}\n."
148
+ else
149
+ that.response = payload
150
+
151
+ # sends the signal to continue the execution of #call
152
+ requests.delete(__id__)
153
+ that.lock.synchronize { that.condition.signal }
154
+ end
155
+ end
156
+ end
157
+
158
+ end
159
+ end
160
+ end
161
+
162
+ __END__
163
+ begin
164
+ client = DataClient.new
165
+ reply = client.send_command( { command: 'ping' } ) #{ command: :hist, contract: 'A6Z21', con_id: 259130514, interval: :min15 } )
166
+ puts reply.nil? ? 'nil' : JSON.parse(reply)
167
+ reply = client.get_historical( contract: 'A6Z21', con_id: 259130514, interval: :min15 , rth_only: false)
168
+ JSON.parse(reply, symbolize_names: true)[:result].map{|z|
169
+ z[:datetime] = Cotcube::Helpers::CHICAGO.parse(z[:time]).strftime('%Y-%m-%d %H:%M:%S')
170
+ z.delete(:created_at)
171
+ z.delete(:time)
172
+ p z.slice(*%i[datetime open high low close volume]).values
173
+
174
+ }
175
+ ensure
176
+ client.stop
177
+ e,nd
@@ -20,10 +20,10 @@ module Cotcube
20
20
  end
21
21
 
22
22
  unless symbol.nil?
23
- sym = symbols(symbol: symbol)
23
+ sym = symbols(symbol: symbol).presence || micros(symbol: symbol)
24
24
  if sym.nil? || sym[:id].nil?
25
25
  raise ArgumentError,
26
- "Could not find match in #{config[:symbols_file]} for given symbol #{symbol}"
26
+ "Could not find match in #{config[:symbols_file]} or #{config[:micros_file]} for given symbol #{symbol}"
27
27
  end
28
28
  raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if (not id.nil?) && (sym[:id] != id)
29
29
 
@@ -39,6 +39,7 @@ module Cotcube
39
39
  end
40
40
  raise ArgumentError, 'Need :id, :symbol or valid :contract '
41
41
  end
42
+
42
43
  end
43
44
  end
44
45
 
@@ -0,0 +1,69 @@
1
+ module Cotcube
2
+ module Helpers
3
+ def get_ib_contract(contract)
4
+ symbol = contract[..1]
5
+ # TODO consider file location to be found in configfile
6
+ filepath = '/etc/cotcube/ibsymbols/'
7
+ result = YAML.load(File.read( "#{filepath}/#{symbol}.yml"))[contract].transform_keys(&:to_sym) rescue nil
8
+ result.nil? ? update_ib_contracts(symbol: contract[..1]) : (return result)
9
+ YAML.load(File.read( "#{filepath}/#{symbol}.yml"))[contract].transform_keys(&:to_sym) rescue nil
10
+ end
11
+
12
+ def update_ib_contracts(symbol: nil)
13
+ begin
14
+ client = DataClient.new
15
+ (Cotcube::Helpers.symbols + Cotcube::Helpers.micros).each do |sym|
16
+
17
+ # TODO: consider file location to be located in config
18
+ file = "/etc/cotcube/ibsymbols/#{sym[:symbol]}.yml"
19
+
20
+ # TODO: VI publishes weekly options which dont match, the 3 others need multiplier enabled to work
21
+ next if %w[ DY TM SI VI ].include? sym[:symbol]
22
+ next if symbol and sym[:symbol] != symbol
23
+ begin
24
+ if File.exist? file
25
+ next if Time.now - File.mtime(file) < 5.days
26
+ data = nil
27
+ data = YAML.load(File.read(file))
28
+ else
29
+ data = {}
30
+ end
31
+ p file
32
+ %w[ symbol sec_type exchange multiplier ticksize power internal ].each {|z| data.delete z}
33
+ raw = client.get_contracts(symbol: sym[:symbol])
34
+ reply = JSON.parse(raw)['result']
35
+ reply.each do |set|
36
+ contract = translate_ib_contract set['local_symbol']
37
+ data[contract] ||= set
38
+ end
39
+ keys = data.keys.sort_by{|z| z[2]}.sort_by{|z| z[-2..] }.select{|z| z[..1] == sym[:symbol] }
40
+ data = data.slice(*keys)
41
+ File.open(file, 'w'){|f| f.write(data.to_yaml) }
42
+ rescue Exception => e
43
+ puts e.full_message
44
+ p sym
45
+ binding.irb
46
+ end
47
+ end
48
+ ensure
49
+ client.stop
50
+ true
51
+ end
52
+ end
53
+
54
+ def translate_ib_contract(contract)
55
+ short = contract.split(" ").size == 1
56
+ sym_a = contract.split(short ? '' : ' ')
57
+ year = sym_a.pop.to_i + (short ? 20 : 0)
58
+ if short and sym_a[-1].to_i > 0
59
+ year = year - 20 + sym_a.pop.to_i * 10
60
+ end
61
+ month = short ? sym_a.pop : LETTERS[sym_a.pop]
62
+ sym = Cotcube::Helpers.symbols(internal: sym_a.join)[:symbol] rescue nil
63
+ sym ||= Cotcube::Helpers.micros(internal: sym_a.join)[:symbol] rescue nil
64
+ sym.nil? ? false : "#{sym}#{month}#{year}"
65
+ end
66
+
67
+ end
68
+ end
69
+
@@ -17,7 +17,7 @@ module Cotcube
17
17
  [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f}
18
18
  row[:format] = "%#{row[:format]}f"
19
19
  row[:currency] ||= 'USD'
20
- row[:multiplier] = (row[:ticksize] / row[:power]).round(8)
20
+ row[:multiplier] = (row[:power] / row[:ticksize]).round(8)
21
21
  row
22
22
  }
23
23
  .reject{|row| row[:id].nil? }
@@ -35,7 +35,7 @@ module Cotcube
35
35
  end
36
36
  end
37
37
 
38
- def micros(config: init, symbol: nil, id: nil)
38
+ def micros(config: init, **args)
39
39
  if config[:micros_file].nil?
40
40
  MICRO_EXAMPLES
41
41
  else
@@ -46,13 +46,14 @@ module Cotcube
46
46
  [ :ticksize, :power, :bcf ].each {|z| row[z] = row[z].to_f }
47
47
  row[:format] = "%#{row[:format]}f"
48
48
  row[:currency] ||= 'USD'
49
+ row[:multiplier] = (row[:power] / row[:ticksize]).round(8)
49
50
  row
50
51
  }
51
52
  .reject{|row| row[:id].nil? }
52
53
  .tap{ |all|
53
54
  args.keys.each { |header|
54
55
  unless SYMBOL_HEADERS.include? header
55
- puts "WARNING in Cotcube::Helpers.symbols: '#{header}' is not a valid symbol header. Skipping..."
56
+ puts "WARNING in Cotcube::Helpers.micros: '#{header}' is not a valid symbol header. Skipping..."
56
57
  next
57
58
  end
58
59
  all.select!{|x| x[header] == args[header]} unless args[header].nil?
@@ -62,6 +63,7 @@ module Cotcube
62
63
  }
63
64
  end
64
65
  end
66
+
65
67
  end
66
68
 
67
69
  end
@@ -7,6 +7,8 @@ require 'active_support/core_ext/time'
7
7
  require 'active_support/core_ext/numeric'
8
8
  require 'parallel'
9
9
  require 'csv'
10
+ require 'yaml'
11
+ require 'json'
10
12
 
11
13
  require_relative 'cotcube-helpers/array_ext'
12
14
  require_relative 'cotcube-helpers/enum_ext'
@@ -26,7 +28,9 @@ require_relative 'cotcube-helpers/reduce'
26
28
  require_relative 'cotcube-helpers/symbols'
27
29
  require_relative 'cotcube-helpers/init'
28
30
  require_relative 'cotcube-helpers/get_id_set'
31
+ require_relative 'cotcube-helpers/ib_contracts'
29
32
  require_relative 'cotcube-helpers/recognition'
33
+ require_relative 'cotcube-helpers/data_client'
30
34
 
31
35
  module Cotcube
32
36
  module Helpers
@@ -40,6 +44,9 @@ module Cotcube
40
44
  :symbols,
41
45
  :micros,
42
46
  :get_id_set,
47
+ :get_ib_contract,
48
+ :update_ib_contracts,
49
+ :translate_ib_contract,
43
50
  :init
44
51
 
45
52
  # please not that module_functions of source provided in private files must be published there
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cotcube-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin L. Tischendorf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-07 00:00:00.000000000 Z
11
+ date: 2021-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -97,10 +97,12 @@ files:
97
97
  - lib/cotcube-helpers.rb
98
98
  - lib/cotcube-helpers/array_ext.rb
99
99
  - lib/cotcube-helpers/constants.rb
100
+ - lib/cotcube-helpers/data_client.rb
100
101
  - lib/cotcube-helpers/datetime_ext.rb
101
102
  - lib/cotcube-helpers/enum_ext.rb
102
103
  - lib/cotcube-helpers/get_id_set.rb
103
104
  - lib/cotcube-helpers/hash_ext.rb
105
+ - lib/cotcube-helpers/ib_contracts.rb
104
106
  - lib/cotcube-helpers/init.rb
105
107
  - lib/cotcube-helpers/input.rb
106
108
  - lib/cotcube-helpers/numeric_ext.rb