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