mpex 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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: {}