cotcube-helpers 0.1.9.2 → 0.2.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/Gemfile +2 -0
- data/VERSION +1 -1
- data/lib/cotcube-helpers/constants.rb +16 -5
- data/lib/cotcube-helpers/data_client.rb +230 -0
- data/lib/cotcube-helpers/expiration.rb +31 -0
- data/lib/cotcube-helpers/get_id_set.rb +9 -7
- data/lib/cotcube-helpers/ib_contracts.rb +69 -0
- data/lib/cotcube-helpers/init.rb +2 -1
- data/lib/cotcube-helpers/numeric_ext.rb +8 -0
- data/lib/cotcube-helpers/orderclient.rb +203 -0
- data/lib/cotcube-helpers/output.rb +20 -0
- data/lib/cotcube-helpers/recognition.rb +509 -0
- data/lib/cotcube-helpers/subpattern.rb +4 -4
- data/lib/cotcube-helpers/symbols.rb +52 -5
- data/lib/cotcube-helpers.rb +13 -0
- data/scripts/collect_market_depth +103 -0
- data/scripts/cron_ruby_wrapper.sh +25 -0
- data/scripts/symbols +13 -0
- metadata +13 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54c97a5746e0e03b971b9421be0636eb2c259beb90840ff34404f8049114019b
|
4
|
+
data.tar.gz: 6d565f00ed620b2458122ad686b1b6c60bb26e815f248474f746b38c10902dc6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9a71ad40a61b1caf44162851d2c323c1b762501705459fbbca85d38c8a6bf66e261eb920093fa01f3ea6ddd07b48dbe7aa4f46e5010a39e0e5bb3045c9941d1
|
7
|
+
data.tar.gz: a862cb81820c8459a015788d273c1cd159078f11705f2c5f8db2847e355a6e5f6cf54b4532a43efef246804a1679053ca807b76c79c580881a46e3ea09fb0f01
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,41 @@
|
|
1
|
+
## 0.2.2.3 (November 28, 2021)
|
2
|
+
- data_client: fixing troublesome output in .command
|
3
|
+
|
4
|
+
## 0.2.2.2 (November 28, 2021)
|
5
|
+
- dataclient fixing = for false ==
|
6
|
+
- solving merge conflicts
|
7
|
+
- Bump version to 0.2.2.
|
8
|
+
|
9
|
+
## 0.2.2.1 (November 28, 2021)
|
10
|
+
- Bump version to 0.2.2.1
|
11
|
+
|
12
|
+
## 0.2.2 (November 13, 2021)
|
13
|
+
- some further improvements to DataClient
|
14
|
+
- some fixes related to ib_contracts
|
15
|
+
- some fixes related to DataClient
|
16
|
+
|
17
|
+
## 0.2.1.1 (November 10, 2021)
|
18
|
+
- Bump version to 0.2.1.
|
19
|
+
|
20
|
+
## 0.2.1 (November 10, 2021)
|
21
|
+
- added new class 'dataclient' for communication with dataproxy
|
22
|
+
- added .translate_ib_contract
|
23
|
+
|
24
|
+
## 0.2.0 (November 07, 2021)
|
25
|
+
- added module Candlestick_Recognition
|
26
|
+
- added instance_inspect method to 'scan' objects for contents of instance variables
|
27
|
+
- symbols: made selection of symbols more versatile by key
|
28
|
+
- added headers (:ib_symbol, :internal, :exchange, :currency) to symbol headers as well as symbol examples
|
29
|
+
- added scripts/symbols to list (and filter) symbols from command line (put to PATH!)
|
30
|
+
|
31
|
+
## 0.1.10 (October 28, 2021)
|
32
|
+
- added script cron_ruby_wrapper.sh (linkable as /usr/local/bin/cruw.sh)
|
33
|
+
- added numeric ext .with_delimiter to support printing like 123_456_789.00121
|
34
|
+
- added micros to module
|
35
|
+
- added Helpers.micros to symbols.rb
|
36
|
+
- subpattern: excaping regex pattern to avoid ESC errors
|
37
|
+
- minor change
|
38
|
+
|
1
39
|
## 0.1.9.2 (July 24, 2021)
|
2
40
|
- added missing module_functions
|
3
41
|
- init: minor fix
|
data/Gemfile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.2.3
|
@@ -3,8 +3,13 @@
|
|
3
3
|
module Cotcube
|
4
4
|
module Helpers
|
5
5
|
SYMBOL_EXAMPLES = [
|
6
|
-
{ id: '13874U', symbol: '
|
7
|
-
{ id: '209747', symbol: '
|
6
|
+
{ id: '13874U', symbol: 'ES', ib_symbol: 'ES', internal: 'ES', exchange: 'GLOBEX', currency: 'USD', ticksize: 0.25, power: 12.5, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'S&P 500 MICRO' },
|
7
|
+
{ id: '209747', symbol: 'NQ', ib_symbol: 'NQ', internal: 'NQ', exchange: 'GLOBEx', currency: 'USD', ticksize: 0.25, power: 5.0, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'NASDAQ 100 MICRO' }
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
MICRO_EXAMPLES = [
|
11
|
+
{ id: '13874U', symbol: 'ET', ib_symbol: 'MES', internal: 'MES', exchange: 'GLOBEX', currency: 'USD', ticksize: 0.25, power: 1.25, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'MICRO S&P 500 MICRO' },
|
12
|
+
{ id: '209747', symbol: 'NM', ib_symbol: 'MNQ', internal: 'MNQ', exchange: 'GLOBEX', currency: 'USD', ticksize: 0.25, power: 0.5, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'MICRO NASDAQ 100 MICRO' }
|
8
13
|
].freeze
|
9
14
|
|
10
15
|
COLORS = %i[light_red light_yellow light_green red yellow green cyan magenta blue].freeze
|
@@ -18,9 +23,9 @@ module Cotcube
|
|
18
23
|
'J' => 4, 'K' => 5, 'M' => 6,
|
19
24
|
'N' => 7, 'Q' => 8, 'U' => 9,
|
20
25
|
'V' => 10, 'X' => 11, 'Z' => 12,
|
21
|
-
|
22
|
-
|
23
|
-
|
26
|
+
1 => 'F', 2 => 'G', 3 => 'H',
|
27
|
+
4 => 'J', 5 => 'K', 6 => 'M',
|
28
|
+
7 => 'N', 8 => 'Q', 9 => 'U',
|
24
29
|
10 => 'V', 11 => 'X', 12 => 'Z' }.freeze
|
25
30
|
|
26
31
|
|
@@ -28,6 +33,12 @@ module Cotcube
|
|
28
33
|
|
29
34
|
DATE_FMT = '%Y-%m-%d'
|
30
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
|
+
|
31
42
|
end
|
32
43
|
end
|
33
44
|
|
@@ -0,0 +1,230 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bunny'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Cotcube
|
8
|
+
module Helpers
|
9
|
+
class DataClient
|
10
|
+
SECRETS_DEFAULT = {
|
11
|
+
'dataproxy_mq_proto' => 'http',
|
12
|
+
'dataproxy_mq_user' => 'guest',
|
13
|
+
'dataproxy_mq_password' => 'guest',
|
14
|
+
'dataproxy_mq_host' => 'localhost',
|
15
|
+
'dataproxy_mq_port' => '15672',
|
16
|
+
'dataproxy_mq_vhost' => '%2F'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
SECRETS = SECRETS_DEFAULT.merge(
|
20
|
+
lambda {
|
21
|
+
begin
|
22
|
+
YAML.safe_load(File.read(Cotcube::Helpers.init[:secrets_file]))
|
23
|
+
rescue StandardError
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
}.call
|
27
|
+
)
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@connection = Bunny.new(user: SECRETS['dataproxy_mq_user'],
|
31
|
+
password: SECRETS['dataproxy_mq_password'],
|
32
|
+
vhost: SECRETS['dataproxy_mq_vhost'])
|
33
|
+
@connection.start
|
34
|
+
|
35
|
+
@commands = connection.create_channel
|
36
|
+
@exchange = commands.direct('dataproxy_commands')
|
37
|
+
@requests = {}
|
38
|
+
@persistent = { depth: {}, realtimebars: {}, ticks: {} }
|
39
|
+
@debug = false
|
40
|
+
setup_reply_queue
|
41
|
+
end
|
42
|
+
|
43
|
+
# command acts a synchronizer: it sends the command and waits for the response
|
44
|
+
# otherwise times out --- the counterpart here is the subscription within
|
45
|
+
# setup_reply_queue
|
46
|
+
#
|
47
|
+
def command(command, timeout: 5)
|
48
|
+
command = { command: command.to_s } unless command.is_a? Hash
|
49
|
+
command[:timestamp] ||= (Time.now.to_f * 1000).to_i
|
50
|
+
request_id = Digest::SHA256.hexdigest(command.to_json)[..6]
|
51
|
+
requests[request_id] = {
|
52
|
+
request: command,
|
53
|
+
id: request_id,
|
54
|
+
lock: Mutex.new,
|
55
|
+
condition: ConditionVariable.new
|
56
|
+
}
|
57
|
+
|
58
|
+
exchange.publish(command.to_json,
|
59
|
+
routing_key: 'dataproxy_commands',
|
60
|
+
correlation_id: request_id,
|
61
|
+
reply_to: reply_queue.name)
|
62
|
+
|
63
|
+
# wait for the signal to continue the execution
|
64
|
+
#
|
65
|
+
requests[request_id][:lock].synchronize do
|
66
|
+
requests[request_id][:condition].wait(requests[request_id][:lock], timeout)
|
67
|
+
end
|
68
|
+
|
69
|
+
# if we reached timeout, we will return nil, just for explicity
|
70
|
+
response = requests[request_id][:response].dup
|
71
|
+
requests.delete(request_id)
|
72
|
+
response
|
73
|
+
end
|
74
|
+
|
75
|
+
alias_method :send_command, :command
|
76
|
+
|
77
|
+
def stop
|
78
|
+
%i[depth ticks realtimebars].each do |type|
|
79
|
+
persistent[type].each do |local_key, obj|
|
80
|
+
puts "Cancelling #{local_key}"
|
81
|
+
obj[:subscription].cancel
|
82
|
+
end
|
83
|
+
end
|
84
|
+
commands.close
|
85
|
+
connection.close
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_contracts(symbol:)
|
89
|
+
send_command({ command: :get_contracts, symbol: symbol })
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_historical(contract:, interval:, duration: nil, before: nil, rth_only: false, based_on: :trades)
|
93
|
+
# rth.true? means data outside of rth is skipped
|
94
|
+
rth_only = rth_only ? 1 : 0
|
95
|
+
|
96
|
+
# interval most probably is given as ActiveSupport::Duration
|
97
|
+
if interval.is_a? ActiveSupport::Duration
|
98
|
+
interval = case interval
|
99
|
+
when 1; :sec1
|
100
|
+
when 5; :sec5
|
101
|
+
when 15; :sec15
|
102
|
+
when 30; :sec30
|
103
|
+
when 60; :min1
|
104
|
+
when 120; :min2
|
105
|
+
when 300; :min5
|
106
|
+
when 900; :min15
|
107
|
+
when 1800; :min30
|
108
|
+
when 3600; :hour1
|
109
|
+
when 86400; :day1
|
110
|
+
else; interval
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
default_durations = { sec1: '30_M', sec5: '2_H', sec15: '6_H', sec30: '12_H',
|
115
|
+
min1: '1_D', min2: '2_D', min5: '5_D', min15: '1_W',
|
116
|
+
min30: '1_W', hour1: '1_W', day1: '1_Y' }
|
117
|
+
|
118
|
+
unless default_durations.keys.include? interval
|
119
|
+
raise "Invalid interval '#{interval}', should be in '#{default_durations.keys}'."
|
120
|
+
end
|
121
|
+
|
122
|
+
# TODO: Check for valid duration specification
|
123
|
+
puts 'WARNING in get_historical: param :before ignored' unless before.nil?
|
124
|
+
duration ||= default_durations[interval]
|
125
|
+
send_command({
|
126
|
+
command: :historical,
|
127
|
+
contract: contract,
|
128
|
+
interval: interval,
|
129
|
+
duration: duration,
|
130
|
+
based_on: based_on.to_s.upcase,
|
131
|
+
rth_only: rth_only,
|
132
|
+
before: nil
|
133
|
+
}, timeout: 20)
|
134
|
+
end
|
135
|
+
|
136
|
+
def start_persistent(contract:, type: :realtimebars, local_id: 0, &block)
|
137
|
+
unless %i[depth ticks realtimebars].include? type.to_sym
|
138
|
+
puts "ERROR: Inappropriate type in stop_realtimebars with #{type}"
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
|
142
|
+
local_key = "#{contract}_#{local_id}"
|
143
|
+
|
144
|
+
channel = connection.create_channel
|
145
|
+
exchange = channel.fanout("dataproxy_#{type}_#{contract}")
|
146
|
+
queue = channel.queue('', exclusive: true, auto_delete: true)
|
147
|
+
queue.bind(exchange)
|
148
|
+
|
149
|
+
ib_contract = Cotcube::Helpers.get_ib_contract(contract)
|
150
|
+
|
151
|
+
command = { command: type, contract: contract, con_id: ib_contract[:con_id],
|
152
|
+
delivery: queue.name, exchange: exchange.name }
|
153
|
+
|
154
|
+
block ||= ->(bar) { puts bar.to_s }
|
155
|
+
|
156
|
+
subscription = queue.subscribe do |_delivery_info, _properties, payload|
|
157
|
+
block.call(JSON.parse(payload, symbolize_names: true))
|
158
|
+
end
|
159
|
+
persistent[type][local_key] = command.dup
|
160
|
+
persistent[type][local_key][:queue] = queue
|
161
|
+
persistent[type][local_key][:subscription] = subscription
|
162
|
+
persistent[type][local_key][:channel] = channel
|
163
|
+
send_command(command)
|
164
|
+
end
|
165
|
+
|
166
|
+
def stop_persistent(contract:, local_id: 0, type: :realtimebars)
|
167
|
+
unless %i[depth ticks realtimebars].include? type.to_sym
|
168
|
+
puts "ERROR: Inappropriate type in stop_realtimebars with #{type}"
|
169
|
+
return false
|
170
|
+
end
|
171
|
+
local_key = "#{contract}_#{local_id}"
|
172
|
+
p persistent[type][local_key][:subscription].cancel
|
173
|
+
p persistent[type][local_key][:channel].close
|
174
|
+
persistent[type].delete(local_key)
|
175
|
+
end
|
176
|
+
|
177
|
+
attr_accessor :response
|
178
|
+
attr_reader :lock, :condition
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
attr_reader :call_id, :connection, :requests, :persistent,
|
183
|
+
:commands, :server_queue_name, :reply_queue, :exchange
|
184
|
+
|
185
|
+
def setup_reply_queue
|
186
|
+
@reply_queue = commands.queue('', exclusive: true, auto_delete: true)
|
187
|
+
@reply_queue.bind(commands.exchange('dataproxy_replies'), routing_key: @reply_queue.name)
|
188
|
+
|
189
|
+
reply_queue.subscribe do |delivery_info, properties, payload|
|
190
|
+
__id__ = properties[:correlation_id]
|
191
|
+
|
192
|
+
if __id__.nil?
|
193
|
+
puts "Received without __id__: #{delivery_info.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
194
|
+
}\n\n#{properties.map { |k, v| "#{k}\t#{v}" }.join("\n")
|
195
|
+
}\n\n#{JSON.parse(payload).map { |k, v| "#{k}\t#{v}" }.join("\n")}" if @debug
|
196
|
+
|
197
|
+
elsif requests[__id__].nil?
|
198
|
+
puts "Received non-matching response, maybe previously timed out: \n\n#{delivery_info}\n\n#{properties}\n\n#{payload}\n."[..620].scan(/.{1,120}/).join(' '*30 + "\n") if @debug
|
199
|
+
else
|
200
|
+
# save the payload and send the signal to continue the execution of #command
|
201
|
+
# need to rescue the rare case, where lock and condition are destroyed right in parallel by timeout
|
202
|
+
begin
|
203
|
+
puts "Received result for #{__id__}" if @debug
|
204
|
+
requests[__id__][:response] = payload
|
205
|
+
requests[__id__][:lock].synchronize { requests[__id__][:condition].signal }
|
206
|
+
rescue nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
__END__
|
216
|
+
begin
|
217
|
+
client = DataClient.new
|
218
|
+
reply = client.send_command( { command: 'ping' } ) #{ command: :hist, contract: 'A6Z21', con_id: 259130514, interval: :min15 } )
|
219
|
+
puts reply.nil? ? 'nil' : JSON.parse(reply)
|
220
|
+
reply = client.get_historical( contract: 'A6Z21', con_id: 259130514, interval: :min15 , rth_only: false)
|
221
|
+
JSON.parse(reply, symbolize_names: true)[:result].map{|z|
|
222
|
+
z[:datetime] = Cotcube::Helpers::CHICAGO.parse(z[:time]).strftime('%Y-%m-%d %H:%M:%S')
|
223
|
+
z.delete(:created_at)
|
224
|
+
z.delete(:time)
|
225
|
+
p z.slice(*%i[datetime open high low close volume]).values
|
226
|
+
|
227
|
+
}
|
228
|
+
ensure
|
229
|
+
client.stop
|
230
|
+
e,nd
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cotcube
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
class ExpirationMonth
|
5
|
+
attr_accessor *%i[ asset month year holidays stencil ]
|
6
|
+
def initialize( contract: )
|
7
|
+
a,b,c,d,e = contract.chars
|
8
|
+
@asset = [ a, b ].join
|
9
|
+
if %w[ GG DL BJ GE VI ]
|
10
|
+
puts "Denying to calculate expiration for #{@asset}".light_red
|
11
|
+
return
|
12
|
+
end
|
13
|
+
@month = MONTHS[c] + offset
|
14
|
+
@month -= 1 if %w[ CL HO NG RB SB].include? @asset
|
15
|
+
@month += 1 if %w[ ].include? @asset
|
16
|
+
@month += 12 if month < 1
|
17
|
+
@month -= 12 if month > 12
|
18
|
+
@year = [ d, e ].join.to_i
|
19
|
+
@year += year > 61 ? 1900 : 2000
|
20
|
+
@holidays = CSV.read("/var/cotcube/bardata/holidays.csv").map{|x| DateTime.parse(x[0]).to_date}.select{|x| x.year == @year }
|
21
|
+
@stencil = [ Date.new(@year, @month, 1) ]
|
22
|
+
end_date = Date.new(@year, @month + 1, 1 )
|
23
|
+
while (next_date = @stencil.last + 1) < end_date
|
24
|
+
@stencil << next_date
|
25
|
+
end
|
26
|
+
@stencil.reject!{|x| [0,6].include?(x.wday) or @holidays.include? x}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -3,12 +3,13 @@
|
|
3
3
|
module Cotcube
|
4
4
|
module Helpers
|
5
5
|
|
6
|
-
def get_id_set(symbol: nil, id: nil, contract: nil, config: init)
|
6
|
+
def get_id_set(symbol: nil, id: nil, contract: nil, config: init, mini: false, micro: false)
|
7
|
+
micro = mini || micro
|
7
8
|
contract = contract.to_s.upcase if contract.is_a? Symbol
|
8
9
|
id = id.to_s.upcase if id.is_a? Symbol
|
9
10
|
symbol = symbol.to_s.upcase if symbol.is_a? Symbol
|
10
11
|
|
11
|
-
if contract.is_a?(String) && (contract.length
|
12
|
+
if contract.is_a?(String) && ([2,3,4,5].include? contract.length)
|
12
13
|
c_symbol = contract[0..1]
|
13
14
|
if (not symbol.nil?) && (symbol != c_symbol)
|
14
15
|
raise ArgumentError,
|
@@ -19,25 +20,26 @@ module Cotcube
|
|
19
20
|
end
|
20
21
|
|
21
22
|
unless symbol.nil?
|
22
|
-
sym = symbols.
|
23
|
+
sym = symbols(symbol: symbol).presence || micros(symbol: symbol)
|
23
24
|
if sym.nil? || sym[:id].nil?
|
24
25
|
raise ArgumentError,
|
25
|
-
"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}"
|
26
27
|
end
|
27
28
|
raise ArgumentError, "Mismatching symbol #{symbol} and given id #{id}" if (not id.nil?) && (sym[:id] != id)
|
28
29
|
|
29
|
-
return sym
|
30
|
+
return micro ? micros(id: sym[:id]) : sym
|
30
31
|
end
|
31
32
|
unless id.nil?
|
32
|
-
sym = symbols
|
33
|
+
sym = symbols(id: id)
|
33
34
|
if sym.nil? || sym[:id].nil?
|
34
35
|
raise ArgumentError,
|
35
36
|
"Could not find match in #{config[:symbols_file]} for given id #{id}"
|
36
37
|
end
|
37
|
-
return sym
|
38
|
+
return micro ? micros(id: sym[:id]) : sym
|
38
39
|
end
|
39
40
|
raise ArgumentError, 'Need :id, :symbol or valid :contract '
|
40
41
|
end
|
42
|
+
|
41
43
|
end
|
42
44
|
end
|
43
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
|
+
|
data/lib/cotcube-helpers/init.rb
CHANGED
@@ -23,10 +23,11 @@ module Cotcube
|
|
23
23
|
gem_name: nil,
|
24
24
|
debug: false)
|
25
25
|
gem_name ||= self.ancestors.first.to_s
|
26
|
-
config_file_name = "#{gem_name.downcase}.yml"
|
26
|
+
config_file_name = "#{gem_name.downcase.split('::').last}.yml"
|
27
27
|
config_file = config_path + "/#{config_file_name}"
|
28
28
|
|
29
29
|
if File.exist?(config_file)
|
30
|
+
require 'yaml'
|
30
31
|
config = YAML.load(File.read config_file).transform_keys(&:to_sym)
|
31
32
|
else
|
32
33
|
config = {}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Numeric
|
2
|
+
def with_delimiter(deli=nil)
|
3
|
+
raise ArgumentError, "Param delimiter can't be nil" if deli.nil?
|
4
|
+
pre, post = self.to_s.split('.')
|
5
|
+
pre = pre.chars.to_a.reverse.each_slice(3).map(&:join).join(deli).reverse
|
6
|
+
post.nil? ? pre : [pre,post].join('.')
|
7
|
+
end
|
8
|
+
end
|