cotcube-helpers 0.1.9.2 → 0.2.2.3

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: 2bfb21e47e19a7dfd33125d42d4d049d8654fbf84e36edfe081a3878e751439e
4
- data.tar.gz: 847d6eb7b98df85dc11aadc294147542071fdb4ff83d86faac3e0ef14d204d47
3
+ metadata.gz: 54c97a5746e0e03b971b9421be0636eb2c259beb90840ff34404f8049114019b
4
+ data.tar.gz: 6d565f00ed620b2458122ad686b1b6c60bb26e815f248474f746b38c10902dc6
5
5
  SHA512:
6
- metadata.gz: b2a0020f5c8277b6882dbe7a00e2c885c1e86eb56b22529236b086554b7b69b398afaa1b013bb02b8f4d23f7a7444000dac77e283c7c92ecaa8e2e255e44c6f9
7
- data.tar.gz: b8638126b407e5c4e74a0866fa7497d1c3f29af6f143b94fbca775987de78f350df75b239fd68dcc339aaa4858254b5957c3b9a1e4a4aff40d8c3b487a84a5b4
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
@@ -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.1.9.2
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: 'ET', ticksize: 0.25, power: 1.25, months: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'S&P 500 MICRO' },
7
- { id: '209747', symbol: 'NM', ticksize: 0.25, power: 0.5, monhts: 'HMUZ', bcf: 1.0, reports: 'LF', format: '8.2f', name: 'NASDAQ 100 MICRO' }
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
- 1 => 'F', 2 => 'G', 3 => 'H',
22
- 4 => 'J', 5 => 'K', 6 => 'M',
23
- 7 => 'N', 8 => 'Q', 9 => 'U',
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 == 5)
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.select { |s| s[:symbol] == symbol.to_s.upcase }.first
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.select { |s| s[:id] == id.to_s }.first
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
+
@@ -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