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 +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile +2 -0
- data/VERSION +1 -1
- data/lib/cotcube-helpers/constants.rb +6 -0
- data/lib/cotcube-helpers/data_client.rb +177 -0
- data/lib/cotcube-helpers/get_id_set.rb +3 -2
- data/lib/cotcube-helpers/ib_contracts.rb +69 -0
- data/lib/cotcube-helpers/symbols.rb +5 -3
- data/lib/cotcube-helpers.rb +7 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1228015a21f3920ec5b1d87e3c5918e3c69e0a577746b6a18579ae51e1528ce2
|
4
|
+
data.tar.gz: 431798647c5d4e9d5a7a32816636bd768c4e0e9c70d789dd491c374fde75f768
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
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[:
|
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,
|
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.
|
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
|
data/lib/cotcube-helpers.rb
CHANGED
@@ -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.
|
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-
|
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
|