Class: Mpex::Mpex

Inherits:
Object
  • Object
show all
Defined in:
lib/mpex/mpex.rb

Constant Summary collapse

CONFIG_FILE_PATH =
File.join(Dir.home, ".mpex", "config.yaml")
LOGFILE_PATH =
File.join(Dir.home, ".mpex", "response.log")
TRADES_LOGFILE_PATH =
File.join(Dir.home, ".mpex", "trades.log")

Instance Method Summary collapse

Constructor Details

#initializeMpex

Returns a new instance of Mpex.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/mpex/mpex.rb', line 16

def initialize
  @crypto = GPGME::Crypto.new(:armor => true)
  unless File.exist?(File.expand_path(LOGFILE_PATH))
    dirname = File.dirname(File.expand_path(LOGFILE_PATH))
    Dir.mkdir(dirname) unless Dir.exist?(dirname)
  end
  @logger = Logger.new(LOGFILE_PATH, 'daily')
  @logger.formatter = proc do |severity, datetime, progname, msg|
    "#{severity} Log entry @ #{datetime}:\n#{msg}\n\n"
  end
  @trades_log = Logger.new(TRADES_LOGFILE_PATH, 'daily')
  @trades_log.formatter = proc do |severity, datetime, progname, msg|
    "#{msg}\n"
  end
end

Instance Method Details

#book_formatted(stat) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/mpex/mpex.rb', line 302

def book_formatted(stat)
  book = ""
  stat["Book"].sort_by { |o| o.keys.first == "md5Checksum" ? -1 : o[o.keys.first]["Price"].to_i }.each do |o|
    order_number = o.keys.first
    unless order_number == "md5Checksum"
      book << "  #{o[order_number]["MPSIC"]}: #{o[order_number]["BS"]}\t"
      book << "#{o[order_number]["Quantity"]}\t@"
      book << "#{Converter.satoshi_to_btc(o[order_number]['Price'])}"
      book << "\t(order ##{order_number})\n"
    end
  end
  book
end

#create_configfile_unless_existsObject



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# File 'lib/mpex/mpex.rb', line 387

def create_configfile_unless_exists
  return if File.exist?(File.expand_path(CONFIG_FILE_PATH))
  begin
    current_dir = File.dirname(File.expand_path(__FILE__))

    sample_config_file = File.expand_path(File.join(current_dir, "..", "..", "config.yaml.sample"))
    
    unless File.exist?(File.expand_path(CONFIG_FILE_PATH))
      mpex_dir = File.dirname(File.expand_path(CONFIG_FILE_PATH))
      Dir.mkdir(mpex_dir) unless Dir.exist?(mpex_dir)
    end
    
    FileUtils.cp sample_config_file, File.expand_path(CONFIG_FILE_PATH)
    return YAML.load_file(File.expand_path(CONFIG_FILE_PATH))
  rescue
    puts "WARN: no sample config file found!"
  end
end

#cumulated_amounts(stat) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/mpex/mpex.rb', line 161

def cumulated_amounts(stat)
  cumulated_amounts = {}
  stat["Book"].each do |order|
    mpsic = order[order.keys.first]["MPSIC"]
    if (mpsic and order[order.keys.first]["BS"] == "S")
      cumulated_amounts[mpsic] = cumulated_amounts[mpsic].to_i + order[order.keys.first]["Quantity"].to_i
    elsif (mpsic and order[order.keys.first]["BS"] == "B")
      cumulated_amounts["CxBTC"] = cumulated_amounts["CxBTC"].to_i + order[order.keys.first]["Quantity"].to_i * order[order.keys.first]["Price"].to_i
    end
  end
  stat["Holdings"].each do |h|
    mpsic = h.keys.first
    cumulated_amounts[mpsic] = cumulated_amounts[mpsic].to_i + h[mpsic].to_i if (mpsic and mpsic!="md5Checksum")
  end
  cumulated_amounts
end

#cumulated_amounts_formatted(stat) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
# File 'lib/mpex/mpex.rb', line 178

def cumulated_amounts_formatted(stat)
  formatted = ""
  cumulated_amounts(stat).each do |mpsic, amount|
    if (mpsic == "CxBTC")
      formatted << "  #{mpsic}:\t#{Converter.satoshi_to_btc(amount)}\n"
    else
      formatted << "  #{mpsic}:\t#{amount}\n"
    end
  end
  formatted
end

#decrypt(encrypted_data, opts) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/mpex/mpex.rb', line 56

def decrypt(encrypted_data, opts)
  return encrypted_data unless (encrypted_data.start_with?("-----BEGIN PGP MESSAGE-----"))
  decrypted = @crypto.decrypt(encrypted_data, { :passphrase_callback => method(:passphrase_callback) }) do |signature|
    raise "Signature could not be verified" unless signature.valid?
  end
  decrypted.to_s
end

#encrypt(signed_msg, opts) ⇒ Object



51
52
53
54
# File 'lib/mpex/mpex.rb', line 51

def encrypt(signed_msg, opts)
  encrypted = @crypto.encrypt(signed_msg, :recipients => opts[:mpexkeyid])
  encrypted.to_s
end

#fetch_mpex_vwaps(url = nil, opts = nil) {|vwaps| ... } ⇒ Object

Yields:

  • (vwaps)


216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/mpex/mpex.rb', line 216

def fetch_mpex_vwaps(url=nil, opts=nil, &block)
  vwaps = ""
  if $IRC_LEAK && $IRC_LEAK.connected?
    $IRC_LEAK.vwap do |resp|
      vwaps = JSON.parse(resp)
    end
  else
    url = url ? url : verify_opts_present(opts, [ :url ])[:url]
    vwaps_raw = Http.get(url, "/mpex-vwap.php")
    vwaps = JSON.parse(vwaps_raw)
  end
  yield vwaps
end

#fetch_orderbook(opts = {}, &block) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/mpex/mpex.rb', line 230

def fetch_orderbook(opts = {}, &block)
  orderbook = []
  if $IRC_LEAK && $IRC_LEAK.connected?
    $IRC_LEAK.depth do |depth|
      orderbook = JSON.parse depth
    end
  else
    opts = verify_opts_present(opts, [ :url ])
    orderbook_res = Http.get(opts[:url], "/mpex-mktdepth.php")
    orderbook_res = orderbook_res.start_with?("JurovP") ? orderbook_res.match(/JurovP\((.+)\)/)[1] : orderbook_res
    orderbook = JSON.parse(orderbook_res)
  end
  orderbook.each do |s|
    puts s.first
    s.last["S"].sort_by { |price, amount| -price }.each do |o|
      puts "SELL price: #{Converter.satoshi_to_btc(o.first)} amount: #{o.last}"
    end
    s.last["B"].sort_by { |price, amount| price }.each do |o|
      puts "BUY price: #{Converter.satoshi_to_btc(o.first)} amount: #{o.last}"
    end
  end
end

#format_stat(stat) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/mpex/mpex.rb', line 116

def format_stat(stat)
  header = {}
  stat["Header"].map { |h| header[h.keys.first] = h[h.keys.first] }
<<-STAT
Stats for #{header["Name"]} (fingerprint #{header["Fingerprint"]})
Issued at #{header["DateTime"]} (#{header["Microtime"]})

Holdings:
#{holdings_formatted(stat)}
To which add orders in the book fully paid in advance:
#{book_formatted(stat)}
Options Cover:
  #{stat["OptionsCover"].size > 1 ? stat["OptionsCover"] : ""}
Futures Cover:
  #{stat["IMMCover"].size > 1 ? stat["IMMCover"] : ""}
Excercises:
  #{stat["Exercises"].size > 1 ? stat["Exercises"] : ""}
Your transactions since 1 hour before your last STAT:
#{trade_history_formatted(stat)}
Dividends:
  #{stat["Dividends"].size > 1 ? stat["Dividends"] : ""}
Formatted STATJSON. If you want the original run 'plain STAT'. Logs can be found here: #{LOGFILE_PATH}.
STAT
end

#handle_answer(encrypted_answer, opts) ⇒ Object



86
87
88
89
90
91
92
93
# File 'lib/mpex/mpex.rb', line 86

def handle_answer(encrypted_answer, opts)
  decrypted_response = decrypt(encrypted_answer, opts)

  @logger.info(decrypted_response)

  verified_response = verify(decrypted_response)
  verified_response
end

#holdings_avg_value(stat, vwaps) ⇒ Object



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/mpex/mpex.rb', line 326

def holdings_avg_value(stat, vwaps)
  holdings_value = stat["Holdings"].map do |h|
    mpsic = h.keys.first
    if h["CxBTC"]
      h["CxBTC"].to_i
    elsif h[mpsic] && vwaps[mpsic]
      amount = h[mpsic].to_i
      avg_price = 0
      if vwaps[mpsic]["1d"]["avg"].to_i > 0
        avg_price = vwaps[mpsic]["1d"]["avg"].to_i
      elsif vwaps[mpsic]["7d"]["avg"].to_i > 0
        avg_price = vwaps[mpsic]["7d"]["avg"].to_i
      elsif vwaps[mpsic]["30d"]["avg"].to_i > 0
        avg_price = vwaps[mpsic]["30d"]["avg"].to_i
      end
      amount * avg_price
    else
      0
    end
  end.inject(:+)
end

#holdings_formatted(stat) ⇒ Object



316
317
318
319
320
321
322
323
324
# File 'lib/mpex/mpex.rb', line 316

def holdings_formatted(stat)
  holdings = ""
  stat["Holdings"].each do |h|
    mpsic = h.keys.first
    amount = mpsic == "CxBTC" ? Converter.satoshi_to_btc(h[h.keys.first]) : h[h.keys.first]
    holdings = holdings + "  #{mpsic}: #{amount}\n" unless h.keys.first == "md5Checksum"
  end
  holdings
end

#list_proxies(&block) ⇒ Object



253
254
255
256
257
258
259
260
261
# File 'lib/mpex/mpex.rb', line 253

def list_proxies(&block)
  if $IRC_LEAK && $IRC_LEAK.connected?
    $IRC_LEAK.list_proxies do |proxies|
      yield proxies
    end
  else
    puts "This command only works when connected to irc. Type 'irc' to connect."
  end
end

#log_trade_histroy(stat) ⇒ Object



107
108
109
110
111
112
113
114
# File 'lib/mpex/mpex.rb', line 107

def log_trade_histroy(stat)
  stat["TradeHistory"].each do |t|
    unixtime = t.keys.first
    unless unixtime == "md5Checksum"
      @trades_log.info(t)
    end
  end
end

#orders_sum(stat) ⇒ Object



154
155
156
157
158
159
# File 'lib/mpex/mpex.rb', line 154

def orders_sum(stat)
  orders_value = stat["Book"].map do |order|
    order[order.keys.first]["Price"].to_i * order[order.keys.first]["Quantity"].to_i
  end.inject(:+)
  orders_value
end

#orders_vwap_sum(stat, vwaps) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/mpex/mpex.rb', line 190

def orders_vwap_sum(stat, vwaps)
  orders_value = stat["Book"].map do |order|
    if order[order.keys.first]["BS"] == "B"
      order[order.keys.first]["Price"].to_i * order[order.keys.first]["Quantity"].to_i
    elsif order[order.keys.first]["BS"] == "S"
      mpsic = order[order.keys.first]["MPSIC"]
      if vwaps[mpsic]
        avg_price = 0
        if vwaps[mpsic]["1d"]["avg"].to_i > 0
          avg_price = vwaps[mpsic]["1d"]["avg"].to_i
        elsif vwaps[mpsic]["7d"]["avg"].to_i > 0
          avg_price = vwaps[mpsic]["7d"]["avg"].to_i
        elsif vwaps[mpsic]["30d"]["avg"].to_i > 0
          avg_price = vwaps[mpsic]["30d"]["avg"].to_i
        end
        avg_price * order[order.keys.first]["Quantity"].to_i
      else
        0
      end
    else
      0
    end
  end.inject(:+)
  orders_value
end

#passphrase_callback(hook, uid_hint, passphrase_info, prev_was_bad, fd) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/mpex/mpex.rb', line 363

def passphrase_callback(hook, uid_hint, passphrase_info, prev_was_bad, fd)
  $stderr.write("Passphrase for #{uid_hint}: ")
  $stderr.flush
  begin
    system('stty -echo')
    io = IO.for_fd(fd, 'w')
    io.puts(ask(''){|q| q.echo = false})
    io.flush
  ensure
    (0 ... $_.length).each do |i| $_[i] = ?0 end if $_
    system('stty echo')
  end
  $stderr.puts
end

#portfolio(opts, stat, &block) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/mpex/mpex.rb', line 263

def portfolio(opts, stat, &block)

  return unless stat

  opts = verify_opts_present(opts, [ :url, :keyid, :mpexkeyid ])

  fetch_mpex_vwaps(opts[:url]) do |vwaps|
    return unless vwaps
    holdings_value = holdings_avg_value(stat, vwaps)

    optimistic_value = holdings_value + orders_sum(stat)
    vwap_valuation = holdings_value + orders_vwap_sum(stat, vwaps)

    portfolio = <<-PORTFOLIO
Holdings:
#{holdings_formatted(stat)}
Totals:
  Your optimistic valuation: #{Converter.satoshi_to_btc(optimistic_value)}
  VWAP valuation: #{Converter.satoshi_to_btc(vwap_valuation)}

Holdings including those stuck in open orders:
#{cumulated_amounts_formatted(stat)}
    PORTFOLIO
    yield portfolio
  end
end

#read_configObject



378
379
380
381
382
383
384
385
# File 'lib/mpex/mpex.rb', line 378

def read_config
  begin
    return YAML.load_file(File.expand_path(CONFIG_FILE_PATH))
  rescue
    create_configfile_unless_exists
  end
  return {}
end

#send_plain(cleartext_command, opts, &block) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/mpex/mpex.rb', line 64

def send_plain(cleartext_command, opts, &block)

  puts "Sending order to MPEX: #{cleartext_command}"

  opts = verify_opts_present(opts, [ :url, :keyid, :mpexkeyid ])

  signed_msg = sign(cleartext_command, opts)
  say("<%= color('Track-ID: #{track_id(signed_msg)}', :blue) %>")
  encrypted_msg = encrypt(signed_msg, opts)

  if $IRC_LEAK && $IRC_LEAK.connected?
    $IRC_LEAK.send_encrypted(encrypted_msg.to_s) do |encr_answer|
      if encr_answer
        yield handle_answer(encr_answer, opts)
      end
    end
  else
    res = Http.post_form(opts[:url], { 'msg' => "#{encrypted_msg}" })
    yield handle_answer(res, opts)
  end
end

#sign(msg, opts) ⇒ Object



32
33
34
35
36
37
# File 'lib/mpex/mpex.rb', line 32

def sign(msg, opts)
  signed_message = @crypto.sign(msg, { :signer => opts[:keyid], :passphrase_callback => method(:passphrase_callback), :mode => GPGME::SIG_MODE_CLEAR } )

  verify(signed_message)
  signed_message.to_s
end

#statjson(opts, parsed = true, &block) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/mpex/mpex.rb', line 95

def statjson(opts, parsed=true, &block)
  send_plain('STATJSON', opts) do |statjson|
    stat = JSON.parse(statjson)
    log_trade_histroy(stat)
    if parsed
      yield stat
    else
      yield statjson
    end
  end
end

#track_id(signed_msg) ⇒ Object



149
150
151
152
# File 'lib/mpex/mpex.rb', line 149

def track_id(signed_msg)
  md5hex = Digest::MD5.hexdigest(signed_msg)
  md5hex[0..3]
end

#trade_history_formatted(stat) ⇒ Object



290
291
292
293
294
295
296
297
298
299
300
# File 'lib/mpex/mpex.rb', line 290

def trade_history_formatted(stat)
  history = ""
  stat["TradeHistory"].each do |t|
    unixtime = t.keys.first
    total = t[unixtime]["Quantity"].to_i * t[unixtime]["Price"].to_i
    unless unixtime == "md5Checksum"
      history << "  #{Time.at(unixtime.to_i)} #{t[unixtime]["MPSIC"]} - #{t[unixtime]["Quantity"]} #{t[unixtime]["BS"] == "S" ? "sold" : "bought"} @#{Converter.satoshi_to_btc(t[unixtime]["Price"])}, total: #{Converter.satoshi_to_btc(total)}\n"
    end
  end
  history
end

#validate_mpsic(mpsic) ⇒ Object



141
142
143
144
145
146
147
# File 'lib/mpex/mpex.rb', line 141

def validate_mpsic(mpsic)
  if mpsic.match(/^\w\./)
    return mpsic
  else
    raise "invalid MPSIC #{mpsic}"
  end
end

#verify(msg) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/mpex/mpex.rb', line 39

def verify(msg)
  verified_plain_msg = @crypto.verify(msg) do |signature|
    if signature.valid?
      say("<%= color('#{signature}', :green) %>")
    else
      say("<%= color('WARNING', :red) %>: Invalid signature! Don't trust!")
      raise "WARNING: Invalid signature! Don't trust!"
    end
  end
  verified_plain_msg.to_s
end

#verify_opts_present(opts, req_opts) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/mpex/mpex.rb', line 348

def verify_opts_present(opts, req_opts)
  config_opts = read_config
  req_opts.each do |r_opt|
    unless opts[r_opt]
      if config_opts[r_opt.to_s]
        opts[r_opt] = config_opts[r_opt.to_s]
      else
        $stderr.puts "--#{r_opt} option is required"
        exit 1
      end
    end
  end
  opts
end