cotcube-helpers 0.2.0 → 0.2.1.1

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