mpex 0.5.0 → 0.6.0

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
  SHA1:
3
- metadata.gz: 658fe66b10c2a22bcfa1eada43373cad3bd62f74
4
- data.tar.gz: a99ab6a7c1cfcd058f4e4f6430c651286d8ea6d6
3
+ metadata.gz: cd9551c09a979bed1c63ec2de3ec1361a8991d33
4
+ data.tar.gz: a6db4d8d13748dcc459fdf3f848936734b4fdf93
5
5
  SHA512:
6
- metadata.gz: cdfbe7c29b3622eb97223f26847b7ac06d1a012b7e9e91c5c2eb207287d3b2ea7463e75b96dc21c01a74ca955cba3d665fc5931c370e07af382e760fe47a31f1
7
- data.tar.gz: 25cba8ee8577f4b39a51a0629467ff3f635e3e0f2c5b9f24f2561ce6ba860fe43ed86d4dd5a62f4992ba9ba652e0efaa87b9e80b6f08aabc2200f98974a16671
6
+ metadata.gz: d60556f322dfac6963e4b6d8ba5654d55d5bed58d6c4000e10b0e9e66b8de38419051cdfb720339ac935eea70df8e287d13e91fbcd5d944baa665e6e12a79fdc
7
+ data.tar.gz: c44f83886cccbfc6dd350facf76c5e7b2a68e7be47ba56ede007da63be1dc281a599cdb76216dc62512a93eb54797994afe7dc108eb500e5dc6c1e632069c3c3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mpex (0.5.0)
4
+ mpex (0.6.0)
5
5
  cri (~> 2.2)
6
6
  gpgme
7
7
  highline
data/README.md CHANGED
@@ -11,11 +11,11 @@ MPEx.rb is a commandline client for [MPEx](http://mpex.co), a Bitcoin security e
11
11
 
12
12
  ## Usage
13
13
 
14
- $ mpex help
14
+ * mpex help
15
15
 
16
16
  If you want to use MPEx via IRC, use the interactive mode:
17
17
 
18
- $ mpex -i
18
+ * mpex -i
19
19
 
20
20
  ## License
21
21
 
@@ -0,0 +1,4 @@
1
+ url: http://mpex.co/
2
+ keyid: Default
3
+ mpexkeyid: CFE0F3E1
4
+ interactive: true
data/lib/mpex/cli.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  require 'cri'
2
2
  require 'json'
3
+ require 'highline/import'
3
4
 
4
5
  module Mpex::CLI
5
6
 
6
7
  @cmd = Cri::Command.define do
7
8
  name 'mpex'
8
9
  usage 'mpex [options] [command] [options]'
9
- summary 'commandline interface for MPEX trading'
10
+ summary 'commandline interface for MPEx trading'
10
11
  description <<-DESC
11
12
  To change default settings edit ~/.mpex/config.yaml
12
13
 
@@ -17,12 +18,14 @@ module Mpex::CLI
17
18
  exit 0
18
19
  end
19
20
 
20
- option :s, :url, 'URL to MPEX (defaults to mpex.co)'
21
+ option :s, :url, 'URL to MPEx (defaults to http://mpex.co)'
21
22
  option :u, :keyid, 'key id of your gnupg key to use'
22
- option :m, :mpexkeyid, 'mpex public key id'
23
+ option :m, :mpexkeyid, 'MPEx public key id (see FAQ#8)'
23
24
  option :p, :password, 'passphrase of your private key (unless provided you\'ll be asked for it)'
24
25
 
25
- opt :i, :interactive, "start alternative interactive mode to mpex, useful for irc" do
26
+ option "0", :isinteractive, "internal flag set automatically in interactive mode"
27
+
28
+ opt :i, :interactive, "Start interactive mode to MPEx.rb. Required for irc. (think: an MPEx shell)" do
26
29
  Mpex::Interactive.run
27
30
  exit 0
28
31
  end
@@ -42,12 +45,12 @@ Mpex::Mpex.new.create_configfile_unless_exists # makes sure config file is prese
42
45
  name 'stat'
43
46
  aliases [:STAT, :status]
44
47
  usage 'stat [options]'
45
- summary 'old style standard mpex STAT'
48
+ summary 'Formatted MPEx STATJSON'
46
49
 
47
50
  run do |opts, args|
48
51
  mpex = Mpex::Mpex.new
49
- mpex.send_plain('STAT', opts) do |answer|
50
- puts answer
52
+ mpex.statjson(opts) do |stat|
53
+ puts mpex.format_stat(stat)
51
54
  end
52
55
  end
53
56
  end
@@ -60,7 +63,7 @@ end
60
63
 
61
64
  run do |opts, args|
62
65
  mpex = Mpex::Mpex.new
63
- mpex.send_plain('STATJSON', opts) do |answer|
66
+ mpex.statjson(opts, false) do |answer|
64
67
  puts answer
65
68
  end
66
69
  end
@@ -70,7 +73,7 @@ end
70
73
  name 'plain'
71
74
  aliases :p
72
75
  usage "plain [options] 'MPX|FOO|BAR'"
73
- summary "send string as is signed/encrypted to mpex"
76
+ summary "send string as is signed/encrypted to MPEx"
74
77
 
75
78
  run do |opts, args|
76
79
  mpex = Mpex::Mpex.new
@@ -88,11 +91,14 @@ end
88
91
  run do |opts, args|
89
92
  mpex = Mpex::Mpex.new
90
93
  mpsic = mpex.validate_mpsic(args[0])
91
- if args[2].match(/\./)
94
+ amount = args[1]
95
+ price = args[2]
96
+ if price.match(/\./)
92
97
  puts "Provide price in Satoshis!"
93
98
  exit 0
94
99
  end
95
- order_string = "BUY|#{mpsic}|#{args[1]}|#{args[2]}"
100
+ say "<%= color('Buy #{amount} #{mpsic} @ #{Mpex::Converter.satoshi_to_btc(price.to_i)} BTC = #{Mpex::Converter.satoshi_to_btc(amount.to_i*price.to_i)} total BTC', :bold) %>"
101
+ order_string = "BUY|#{mpsic}|#{amount}|#{price}"
96
102
  mpex.send_plain(order_string, opts) do |answer|
97
103
  puts answer
98
104
  end
@@ -107,11 +113,14 @@ end
107
113
  run do |opts, args|
108
114
  mpex = Mpex::Mpex.new
109
115
  mpsic = mpex.validate_mpsic(args[0])
110
- if args[2].match(/\./)
116
+ amount = args[1]
117
+ price = args[2]
118
+ if price.match(/\./)
111
119
  puts "Provide price in Satoshis!"
112
120
  exit 0
113
121
  end
114
- order_string = "SELL|#{mpsic}|#{args[1]}|#{args[2]}"
122
+ say "<%= color('Sell #{amount} #{mpsic} @ #{Mpex::Converter.satoshi_to_btc(price.to_i)} BTC = #{Mpex::Converter.satoshi_to_btc(amount.to_i*price.to_i)} total BTC', :bold) %>"
123
+ order_string = "SELL|#{mpsic}|#{amount}|#{price}"
115
124
  mpex.send_plain(order_string, opts) do |answer|
116
125
  puts answer
117
126
  end
@@ -152,7 +161,7 @@ end
152
161
  usage "withdraw [options] [address] [amount in satoshi]"
153
162
 
154
163
  run do |opts, args|
155
- puts .run('help'); exit 0 unless args.length == 2
164
+ #puts .run('help'); exit 0 unless args.length == 2
156
165
  mpex = Mpex::Mpex.new
157
166
  order_string = "WITHDRAW|#{args[0]}|#{args[1]}"
158
167
  mpex.send_plain(order_string, opts) do |answer|
@@ -168,8 +177,8 @@ end
168
177
 
169
178
  run do |opts, args|
170
179
  mpex = Mpex::Mpex.new
171
- mpex.send_plain('STATJSON', opts) do |stat|
172
- mpex.portfolio(opts, JSON.parse(stat)) do |portfolio|
180
+ mpex.statjson(opts) do |stat|
181
+ mpex.portfolio(opts, stat) do |portfolio|
173
182
  puts portfolio
174
183
  end if stat
175
184
  end
@@ -195,8 +204,13 @@ end
195
204
  summary 'connects an irc bot to freenode to talk to mpex via its bots'
196
205
 
197
206
  run do |opts, args|
198
- $IRC_LEAK = Mpex::Irc.new
199
- $IRC_LEAK.connect
207
+ if opts[:isinteractive]
208
+ $IRC_LEAK = Mpex::Irc.new
209
+ $IRC_LEAK.connect
210
+ else
211
+ puts "Connecting to IRC works in interactive mode only. Run 'mpex -i' to start MPEx.rb in interactive shell mode."
212
+ exit 0
213
+ end
200
214
  end
201
215
  end
202
216
 
@@ -209,7 +223,7 @@ end
209
223
  mpex = Mpex::Mpex.new
210
224
  mpex.list_proxies do |proxies|
211
225
  puts proxies
212
- puts "To use a proxy, edit ~/.mpex/config.yaml and restart mpex cli"
226
+ puts "To use a proxy, edit ~/.mpex/config.yaml and restart mpex -i"
213
227
  end
214
228
  end
215
229
  end
@@ -16,10 +16,10 @@ module Mpex
16
16
 
17
17
  def self.satoshi_to_btc(satoshi)
18
18
  satoshiStr = satoshi.to_s;
19
-
19
+
20
20
  satoshiPart = "";
21
21
  btcPart = "0";
22
-
22
+
23
23
  denominator = "";
24
24
 
25
25
  if (satoshiStr.size > 8)
@@ -38,13 +38,8 @@ module Mpex
38
38
  end
39
39
  end
40
40
 
41
- # cut trailing zeros
42
- while (satoshiPart.end_with?("0")) do
43
- satoshiPart = satoshiPart.chomp("0");
44
- end
45
-
46
41
  return denominator + btcPart + "." + satoshiPart;
47
42
  end
48
-
43
+
49
44
  end
50
- end
45
+ end
@@ -4,15 +4,17 @@ require 'highline/import'
4
4
  module Mpex::Interactive
5
5
 
6
6
  def self.run
7
- puts "Welcome to MPEX repl. Type 'help' and 'help [command]' to get a help or 'quit' to exit."
7
+ puts "Welcome to MPEx.rb shell. Type 'help' and 'help [command]' to get a help or 'quit' to exit."
8
8
  puts "Donations welcome: 1DrqwLjksrXZHdSzzieaNhQuhrnbNTeiQr"
9
9
  puts "Type 'irc' to connect to Freenode to use MPEx IRC bots"
10
10
  loop do
11
11
  line = ask("mpex>> ") {|q| q.readline = true }
12
12
 
13
+ next if line.empty?
14
+
13
15
  exit 0 if line =~ /^(exit|quit|q)$/
14
16
 
15
- args = line.split(" ")
17
+ args = line.split(" ") + ["--isinteractive"]
16
18
 
17
19
  begin
18
20
  Mpex::CLI.command.run(args) unless args.empty?
data/lib/mpex/irc.rb CHANGED
@@ -14,7 +14,7 @@ module Mpex
14
14
  @irc = Net::YAIL.new(
15
15
  :address => 'irc.freenode.net',
16
16
  :username => 'mp_rb_client',
17
- :realname => 'mpex ruby irc client',
17
+ :realname => 'MPEx.rb irc client',
18
18
  :nicknames => ["mp_rb_client#{Random.rand(42..4096)}", "mp_rb_client#{Random.rand(42..4096)}"]
19
19
  )
20
20
  log = Logger.new(STDOUT)
data/lib/mpex/mpex.rb CHANGED
@@ -11,6 +11,7 @@ module Mpex
11
11
 
12
12
  CONFIG_FILE_PATH = File.join(Dir.home, ".mpex", "config.yaml")
13
13
  LOGFILE_PATH = File.join(Dir.home, ".mpex", "response.log")
14
+ TRADES_LOGFILE_PATH = File.join(Dir.home, ".mpex", "trades.log")
14
15
 
15
16
  def initialize
16
17
  @crypto = GPGME::Crypto.new(:armor => true)
@@ -18,7 +19,14 @@ module Mpex
18
19
  dirname = File.dirname(File.expand_path(LOGFILE_PATH))
19
20
  Dir.mkdir(dirname) unless Dir.exist?(dirname)
20
21
  end
21
- @logger = Logger.new(LOGFILE_PATH)
22
+ @logger = Logger.new(LOGFILE_PATH, 'daily')
23
+ @logger.formatter = proc do |severity, datetime, progname, msg|
24
+ "#{severity} Log entry @ #{datetime}:\n#{msg}\n\n"
25
+ end
26
+ @trades_log = Logger.new(TRADES_LOGFILE_PATH, 'daily')
27
+ @trades_log.formatter = proc do |severity, datetime, progname, msg|
28
+ "#{msg}\n"
29
+ end
22
30
  end
23
31
 
24
32
  def sign(msg, opts)
@@ -29,7 +37,7 @@ module Mpex
29
37
  end
30
38
 
31
39
  def verify(msg)
32
- @crypto.verify(msg) do |signature|
40
+ verified_plain_msg = @crypto.verify(msg) do |signature|
33
41
  if signature.valid?
34
42
  say("<%= color('#{signature}', :green) %>")
35
43
  else
@@ -37,16 +45,19 @@ module Mpex
37
45
  raise "WARNING: Invalid signature! Don't trust!"
38
46
  end
39
47
  end
48
+ verified_plain_msg.to_s
40
49
  end
41
50
 
42
51
  def encrypt(signed_msg, opts)
43
- @crypto.encrypt(signed_msg, :recipients => opts[:mpexkeyid])
52
+ encrypted = @crypto.encrypt(signed_msg, :recipients => opts[:mpexkeyid])
53
+ encrypted.to_s
44
54
  end
45
55
 
46
56
  def decrypt(encrypted_data, opts)
47
- @crypto.decrypt(encrypted_data, :password => opts[:password]) do |signature|
57
+ decrypted = @crypto.decrypt(encrypted_data, :password => opts[:password]) do |signature|
48
58
  raise "Signature could not be verified" unless signature.valid?
49
59
  end
60
+ decrypted.to_s
50
61
  end
51
62
 
52
63
  def send_plain(cleartext_command, opts, &block)
@@ -72,12 +83,58 @@ module Mpex
72
83
  end
73
84
 
74
85
  def handle_answer(encrypted_answer, opts)
75
- decrypted_response = decrypt(encrypted_answer.to_s, opts)
86
+ decrypted_response = decrypt(encrypted_answer, opts)
76
87
 
77
- @logger.info(decrypted_response.to_s)
88
+ @logger.info(decrypted_response)
78
89
 
79
90
  verified_response = verify(decrypted_response)
80
- verified_response.to_s
91
+ verified_response
92
+ end
93
+
94
+ def statjson(opts, parsed=true, &block)
95
+ send_plain('STATJSON', opts) do |statjson|
96
+ stat = JSON.parse(statjson)
97
+ log_trade_histroy(stat)
98
+ if parsed
99
+ yield stat
100
+ else
101
+ yield statjson
102
+ end
103
+ end
104
+ end
105
+
106
+ def log_trade_histroy(stat)
107
+ stat["TradeHistory"].each do |t|
108
+ unixtime = t.keys.first
109
+ unless unixtime == "md5Checksum"
110
+ @trades_log.info(t)
111
+ end
112
+ end
113
+ end
114
+
115
+ def format_stat(stat)
116
+ header = {}
117
+ stat["Header"].map { |h| header[h.keys.first] = h[h.keys.first] }
118
+ <<-STAT
119
+ Stats for #{header["Name"]} (fingerprint #{header["Fingerprint"]})
120
+ Issued at #{header["DateTime"]} (#{header["Microtime"]})
121
+
122
+ Holdings:
123
+ #{holdings_formatted(stat)}
124
+ To which add orders in the book fully paid in advance:
125
+ #{book_formatted(stat)}
126
+ Options Cover:
127
+ #{stat["OptionsCover"].size > 1 ? stat["OptionsCover"] : ""}
128
+ Futures Cover:
129
+ #{stat["IMMCover"].size > 1 ? stat["IMMCover"] : ""}
130
+ Excercises:
131
+ #{stat["Exercises"].size > 1 ? stat["Exercises"] : ""}
132
+ Your transactions since 1 hour before your last STAT:
133
+ #{trade_history_formatted(stat)}
134
+ Dividends:
135
+ #{stat["Dividends"].size > 1 ? stat["Dividends"] : ""}
136
+ Formatted STATJSON. If you want the original run 'plain STAT'. Logs can be found here: #{LOGFILE_PATH}.
137
+ STAT
81
138
  end
82
139
 
83
140
  def validate_mpsic(mpsic)
@@ -100,13 +157,42 @@ module Mpex
100
157
  orders_value
101
158
  end
102
159
 
160
+ def cumulated_amounts(stat)
161
+ cumulated_amounts = {}
162
+ stat["Book"].each do |order|
163
+ mpsic = order[order.keys.first]["MPSIC"]
164
+ if (mpsic and order[order.keys.first]["BS"] == "S")
165
+ cumulated_amounts[mpsic] = cumulated_amounts[mpsic].to_i + order[order.keys.first]["Quantity"].to_i
166
+ elsif (mpsic and order[order.keys.first]["BS"] == "B")
167
+ cumulated_amounts["CxBTC"] = cumulated_amounts["CxBTC"].to_i + order[order.keys.first]["Quantity"].to_i * order[order.keys.first]["Price"].to_i
168
+ end
169
+ end
170
+ stat["Holdings"].each do |h|
171
+ mpsic = h.keys.first
172
+ cumulated_amounts[mpsic] = cumulated_amounts[mpsic].to_i + h[mpsic].to_i if (mpsic and mpsic!="md5Checksum")
173
+ end
174
+ cumulated_amounts
175
+ end
176
+
177
+ def cumulated_amounts_formatted(stat)
178
+ formatted = ""
179
+ cumulated_amounts(stat).each do |mpsic, amount|
180
+ if (mpsic == "CxBTC")
181
+ formatted << " #{mpsic}:\t#{Converter.satoshi_to_btc(amount)}\n"
182
+ else
183
+ formatted << " #{mpsic}:\t#{amount}\n"
184
+ end
185
+ end
186
+ formatted
187
+ end
188
+
103
189
  def orders_vwap_sum(stat, vwaps)
104
190
  orders_value = stat["Book"].map do |order|
105
191
  if order[order.keys.first]["BS"] == "B"
106
192
  order[order.keys.first]["Price"].to_i * order[order.keys.first]["Quantity"].to_i
107
193
  elsif order[order.keys.first]["BS"] == "S"
108
194
  mpsic = order[order.keys.first]["MPSIC"]
109
- vwaps[mpsic]["1d"]["max"].to_i * order[order.keys.first]["Quantity"].to_i
195
+ vwaps[mpsic] ? vwaps[mpsic]["1d"]["max"].to_i * order[order.keys.first]["Quantity"].to_i : 0
110
196
  else
111
197
  0
112
198
  end
@@ -178,13 +264,42 @@ module Mpex
178
264
  Holdings:
179
265
  #{holdings_formatted(stat)}
180
266
  Totals:
181
- Your optimistic valuation: #{Converter.satoshi_to_btc(optimistic_value)}"
267
+ Your optimistic valuation: #{Converter.satoshi_to_btc(optimistic_value)}
182
268
  VWAP valuation: #{Converter.satoshi_to_btc(vwap_valuation)}
269
+
270
+ Holdings including those stuck in open orders:
271
+ #{cumulated_amounts_formatted(stat)}
183
272
  PORTFOLIO
184
273
  yield portfolio
185
274
  end
186
275
  end
187
276
 
277
+ def trade_history_formatted(stat)
278
+ history = ""
279
+ stat["TradeHistory"].each do |t|
280
+ unixtime = t.keys.first
281
+ total = t[unixtime]["Quantity"].to_i * t[unixtime]["Price"].to_i
282
+ unless unixtime == "md5Checksum"
283
+ 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"
284
+ end
285
+ end
286
+ history
287
+ end
288
+
289
+ def book_formatted(stat)
290
+ book = ""
291
+ stat["Book"].sort_by { |o| o.keys.first == "md5Checksum" ? -1 : o[o.keys.first]["Price"].to_i }.each do |o|
292
+ order_number = o.keys.first
293
+ unless order_number == "md5Checksum"
294
+ book << " #{o[order_number]["MPSIC"]}: #{o[order_number]["BS"]}\t"
295
+ book << "#{o[order_number]["Quantity"]}\t@"
296
+ book << "#{Converter.satoshi_to_btc(o[order_number]['Price'])}"
297
+ book << "\t(order ##{order_number})\n"
298
+ end
299
+ end
300
+ book
301
+ end
302
+
188
303
  def holdings_formatted(stat)
189
304
  holdings = ""
190
305
  stat["Holdings"].each do |h|
@@ -203,9 +318,11 @@ Totals:
203
318
  elsif h["S.MPOE"]
204
319
  h["S.MPOE"].to_i*vwaps["S.MPOE"]["1d"]["avg"].to_i
205
320
  elsif h["S.DICE"]
206
- h["S.DICE"].to_i*vwaps["S.BBET"]["1d"]["avg"].to_i
321
+ h["S.DICE"].to_i*vwaps["S.DICE"]["1d"]["avg"].to_i
207
322
  elsif h["S.BBET"]
208
323
  h["S.BBET"].to_i*vwaps["S.BBET"]["1d"]["avg"].to_i
324
+ elsif h["S.MG"]
325
+ h["S.MG"].to_i*vwaps["S.MG"]["1d"]["avg"].to_i
209
326
  else
210
327
  0
211
328
  end
data/lib/mpex/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mpex
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/mpex.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.summary = %q{MPEx.rb: a commandline client for MPEx}
12
12
  s.description = %q{MPEx.rb is a commandline client for "MPEx":http://mpex.co a Bitcoin security exchange. Make sure to carefully read its "FAQ":http://mpex.co/faq.html before using it.}
13
13
 
14
- s.files = Dir['[A-Z]*'] + Dir['{bin,lib,tasks,test}/**/*'] + [ 'mpex.gemspec' ]
14
+ s.files = Dir['[A-Z]*'] + Dir['{bin,lib,tasks,test}/**/*'] + [ 'mpex.gemspec', 'config.yaml.sample', 'FAQ.html' ]
15
15
  s.extra_rdoc_files = ['README.md']
16
16
  s.rdoc_options = [ '--main', 'README.md' ]
17
17
  s.executables = [ 'mpex' ]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mpex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fa Wuxi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-03-11 00:00:00.000000000 Z
11
+ date: 2013-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cri
@@ -148,6 +148,7 @@ files:
148
148
  - lib/mpex/version.rb
149
149
  - lib/mpex.rb
150
150
  - mpex.gemspec
151
+ - config.yaml.sample
151
152
  homepage: https://github.com/fawuxi/mpex
152
153
  licenses: []
153
154
  metadata: {}