bitbot-trader 0.0.1

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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.devtools +55 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +28 -0
  10. data/Rakefile +4 -0
  11. data/bitbot-trader.gemspec +22 -0
  12. data/config/devtools.yml +4 -0
  13. data/config/flay.yml +3 -0
  14. data/config/flog.yml +2 -0
  15. data/config/mutant.yml +3 -0
  16. data/config/reek.yml +103 -0
  17. data/config/yardstick.yml +2 -0
  18. data/examples/account_info.rb +16 -0
  19. data/examples/open_orders.rb +16 -0
  20. data/lib/bitbot/trader.rb +32 -0
  21. data/lib/bitbot/trader/account.rb +29 -0
  22. data/lib/bitbot/trader/amount.rb +15 -0
  23. data/lib/bitbot/trader/api_methods.rb +33 -0
  24. data/lib/bitbot/trader/open_order.rb +29 -0
  25. data/lib/bitbot/trader/price.rb +15 -0
  26. data/lib/bitbot/trader/provider.rb +19 -0
  27. data/lib/bitbot/trader/providers/bitstamp.rb +29 -0
  28. data/lib/bitbot/trader/providers/bitstamp/http_client.rb +101 -0
  29. data/lib/bitbot/trader/providers/bitstamp/open_orders_parser.rb +33 -0
  30. data/lib/bitbot/trader/providers/bitstamp/open_orders_request.rb +25 -0
  31. data/lib/bitbot/trader/providers/mt_gox.rb +29 -0
  32. data/lib/bitbot/trader/providers/mt_gox/account_info_parser.rb +45 -0
  33. data/lib/bitbot/trader/providers/mt_gox/account_info_request.rb +24 -0
  34. data/lib/bitbot/trader/providers/mt_gox/http_client.rb +98 -0
  35. data/lib/bitbot/trader/providers/mt_gox/http_client/hmac_middleware.rb +118 -0
  36. data/lib/bitbot/trader/providers/mt_gox/open_orders_parser.rb +35 -0
  37. data/lib/bitbot/trader/providers/mt_gox/open_orders_request.rb +25 -0
  38. data/lib/bitbot/trader/providers/mt_gox/value_with_currency_coercer.rb +28 -0
  39. data/lib/bitbot/trader/request.rb +41 -0
  40. data/lib/bitbot/trader/utils/nonce_generator.rb +21 -0
  41. data/lib/bitbot/trader/version.rb +5 -0
  42. data/lib/bitbot/trader/wallet.rb +25 -0
  43. data/spec/integration/bitstamp/open_orders_spec.rb +28 -0
  44. data/spec/integration/mt_gox/account_spec.rb +28 -0
  45. data/spec/integration/mt_gox/open_orders_spec.rb +29 -0
  46. data/spec/spec_helper.rb +18 -0
  47. data/spec/support/big_decimal_matcher.rb +5 -0
  48. data/spec/support/http_connection_helpers.rb +15 -0
  49. data/spec/support/http_request_mock.rb +7 -0
  50. data/spec/support/provider_mock.rb +5 -0
  51. data/spec/unit/bitbot/trader/account_spec.rb +28 -0
  52. data/spec/unit/bitbot/trader/api_methods_spec.rb +43 -0
  53. data/spec/unit/bitbot/trader/open_order_spec.rb +19 -0
  54. data/spec/unit/bitbot/trader/provider_spec.rb +18 -0
  55. data/spec/unit/bitbot/trader/providers/bitstamp/http_client_spec.rb +75 -0
  56. data/spec/unit/bitbot/trader/providers/bitstamp/open_order_parser_spec.rb +69 -0
  57. data/spec/unit/bitbot/trader/providers/bitstamp/open_orders_request_spec.rb +24 -0
  58. data/spec/unit/bitbot/trader/providers/bitstamp_spec.rb +45 -0
  59. data/spec/unit/bitbot/trader/providers/mt_gox/account_info_parser_spec.rb +58 -0
  60. data/spec/unit/bitbot/trader/providers/mt_gox/account_info_request_spec.rb +24 -0
  61. data/spec/unit/bitbot/trader/providers/mt_gox/http_client/hmac_middleware_spec.rb +55 -0
  62. data/spec/unit/bitbot/trader/providers/mt_gox/http_client_spec.rb +100 -0
  63. data/spec/unit/bitbot/trader/providers/mt_gox/open_order_parser_spec.rb +95 -0
  64. data/spec/unit/bitbot/trader/providers/mt_gox/open_orders_request_spec.rb +24 -0
  65. data/spec/unit/bitbot/trader/providers/mt_gox/value_with_currency_coercer_spec.rb +21 -0
  66. data/spec/unit/bitbot/trader/providers/mt_gox_spec.rb +26 -0
  67. data/spec/unit/bitbot/trader/request_spec.rb +39 -0
  68. data/spec/unit/bitbot/trader/utils/nonce_generator_spec.rb +19 -0
  69. metadata +166 -0
@@ -0,0 +1,7 @@
1
+ class HttpRequestMock
2
+ attr_reader :path, :params
3
+
4
+ def post(path, params = {})
5
+ {success: true, path: path, params: params}
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ def provider_double
2
+ Struct.new(:client) {
3
+ include ApiMethods
4
+ }
5
+ end
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ describe Account do
4
+ describe "#wallet" do
5
+ subject { described_class.new(wallets: wallets).wallet(currency) }
6
+
7
+ let(:wallets) {
8
+ [
9
+ {currency: "EUR", value: 123},
10
+ {currency: "BTC", value: 43}
11
+ ]
12
+ }
13
+
14
+ context "when EUR" do
15
+ let(:currency) { "EUR" }
16
+
17
+ it { should be_a(Wallet) }
18
+ its(:balance) { should eq(123) }
19
+ end
20
+
21
+ context "when BTC" do
22
+ let(:currency) { "BTC" }
23
+
24
+ it { should be_a(Wallet) }
25
+ its(:balance) { should eq(43) }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+
3
+ describe ApiMethods do
4
+ describe "#account" do
5
+ subject { object.account }
6
+
7
+ let(:object) { provider_double.new(client) }
8
+ let(:client) { double("client") }
9
+
10
+ let(:request_class) { Class.new }
11
+ let(:request) { double("request") }
12
+
13
+ before do
14
+ object.class.const_set("AccountInfoRequest", request_class)
15
+ request_class.stub(:new).with(client) { request }
16
+ end
17
+
18
+ it "calls request class" do
19
+ request.should_receive(:call)
20
+ subject
21
+ end
22
+ end
23
+
24
+ describe "#open_orders" do
25
+ subject { object.open_orders }
26
+
27
+ let(:object) { provider_double.new(client) }
28
+ let(:client) { double("client") }
29
+
30
+ let(:request_class) { Class.new }
31
+ let(:request) { double("request") }
32
+
33
+ before do
34
+ object.class.const_set("OpenOrdersRequest", request_class)
35
+ request_class.stub(:new).with(client) { request }
36
+ end
37
+
38
+ it "calls request class" do
39
+ request.should_receive(:call)
40
+ subject
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ require "spec_helper"
2
+
3
+ describe OpenOrder do
4
+ describe "ask?" do
5
+ subject { described_class.new(options).ask? }
6
+
7
+ context "when bid" do
8
+ let(:options) { {bid: true} }
9
+
10
+ it { should be(false) }
11
+ end
12
+
13
+ context "when ask" do
14
+ let(:options) { {bid: false} }
15
+
16
+ it { should be(true) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+
3
+ describe Provider do
4
+ describe "#client" do
5
+ subject { klass.new(client).client }
6
+
7
+ let(:klass) {
8
+ Class.new(described_class) {
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+ }
13
+ }
14
+ let(:client) { double }
15
+
16
+ it { should be(client) }
17
+ end
18
+ end
@@ -0,0 +1,75 @@
1
+ require "spec_helper"
2
+
3
+ describe Providers::Bitstamp::HttpClient do
4
+ describe ".build" do
5
+ subject { described_class.build(options) }
6
+
7
+ let(:options) { {username: "double", password: "double"} }
8
+ let(:connection) { double }
9
+
10
+ before do
11
+ described_class.should_receive(:make_connection).
12
+ and_return(connection)
13
+ end
14
+
15
+ it { should be_a(described_class) }
16
+ its(:username) { should eq("double") }
17
+ its(:password) { should eq("double") }
18
+ its(:connection) { should be(connection) }
19
+ end
20
+
21
+ describe ".make_connection" do
22
+ subject(:connection) { described_class.make_connection }
23
+
24
+ it { should be_a(Faraday::Connection) }
25
+
26
+ it "has correct url_prefix" do
27
+ expect(subject.url_prefix.to_s + "/").to eq(described_class::HOST)
28
+ end
29
+
30
+ context "request handlers" do
31
+ subject { connection.builder.handlers }
32
+
33
+ it { should eq([
34
+ Faraday::Request::UrlEncoded,
35
+ Faraday::Adapter::NetHttp])
36
+ }
37
+ end
38
+ end
39
+
40
+ describe "#post" do
41
+ let(:client) { described_class.new(connection, "username", "password") }
42
+ let(:connection) { double("connection") }
43
+ let(:path) { "some_action" }
44
+
45
+ context "with default options" do
46
+ subject { client.post(path) }
47
+
48
+ let(:options) { {param: "value"} }
49
+
50
+ before do
51
+ connection.should_receive(:post).
52
+ with("some_action/", {user: "username", password: "password"}).
53
+ and_return(double(body: '{"result": "yey"}'))
54
+ end
55
+
56
+ it { should eq({"result" => "yey"}) }
57
+ end
58
+
59
+ context "with custom options" do
60
+ subject { client.post(path, options) }
61
+
62
+ let(:options) { {param: "value"} }
63
+
64
+ before do
65
+ connection.should_receive(:post).
66
+ with("some_action/", {
67
+ user: "username", password: "password", param: "value"
68
+ }).
69
+ and_return(double(body: '{"result": "yey"}'))
70
+ end
71
+
72
+ it { should eq({"result" => "yey"}) }
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,69 @@
1
+ require "spec_helper"
2
+
3
+ describe Providers::Bitstamp::OpenOrderParser do
4
+ describe"#parse" do
5
+ subject(:open_order) { parser.parse }
6
+
7
+ let(:parser) { described_class.new(data) }
8
+
9
+ context "when bid" do
10
+ let(:data) {
11
+ {
12
+ "id" => 2826860,
13
+ "type" => 0,
14
+ "price" => "20.10",
15
+ "amount" => "10.00000000",
16
+ "datetime" => "2013-04-20 12:12:14"
17
+ }
18
+ }
19
+
20
+ it { should be_a(OpenOrder) }
21
+ it { should be_bid }
22
+ its(:id) { should eq("2826860") }
23
+
24
+ context "amount" do
25
+ subject { open_order.amount }
26
+ it { should be_a(Amount) }
27
+ its(:value) { should eq(10) }
28
+ its(:currency) { should eq("BTC") }
29
+ end
30
+
31
+ context "price" do
32
+ subject { open_order.price }
33
+ it { should be_a(Price) }
34
+ its(:value) { should eq(20.1) }
35
+ its(:currency) { should eq("USD") }
36
+ end
37
+ end
38
+
39
+ context "when ask" do
40
+ let(:data) {
41
+ {
42
+ "id" => 2851404,
43
+ "type" => 1,
44
+ "price" => "200.00",
45
+ "amount" => "6.00000000",
46
+ "datetime" => "2013-04-22 17:47:43"
47
+ }
48
+ }
49
+
50
+ it { should be_a(OpenOrder) }
51
+ it { should be_ask }
52
+ its(:id) { should eq("2851404") }
53
+
54
+ context "amount" do
55
+ subject { open_order.amount }
56
+ it { should be_a(Amount) }
57
+ its(:value) { should eq(6) }
58
+ its(:currency) { should eq("BTC") }
59
+ end
60
+
61
+ context "price" do
62
+ subject { open_order.price }
63
+ it { should be_a(Price) }
64
+ its(:value) { should eq(200) }
65
+ its(:currency) { should eq("USD") }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe Providers::Bitstamp::OpenOrdersRequest do
4
+ describe "#call" do
5
+ subject { request.call }
6
+
7
+ let(:request) { described_class.new(client) }
8
+ let(:client) { HttpRequestMock.new }
9
+
10
+ let(:raw_open_orders) { [raw_open_order] }
11
+ let(:raw_open_order) { double("raw open order") }
12
+
13
+ before do
14
+ client.stub(:post).with("open_orders").and_return(raw_open_orders)
15
+ end
16
+
17
+ it "parses open orders" do
18
+ Providers::Bitstamp::OpenOrderParser.should_receive(:new).
19
+ with(raw_open_order).
20
+ and_return(double(parse: "result"))
21
+ expect(subject).to eq(["result"])
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ require "spec_helper"
2
+
3
+ describe Providers::Bitstamp do
4
+ describe "#initialize" do
5
+ let(:options) { double("options") }
6
+ let(:username) { "23443" }
7
+ let(:password) { "TopSecret" }
8
+
9
+ context "when with custom http client" do
10
+ subject { described_class.new(options, http_client) }
11
+
12
+ let(:http_client) { double("HttpClient") }
13
+
14
+ it { should be_a(described_class) }
15
+ its(:client) { should be(http_client) }
16
+ end
17
+
18
+ context "when with default http client class" do
19
+ subject { described_class.new(options) }
20
+
21
+ before do
22
+ described_class::HttpClient.should_receive(:build).with(options)
23
+ end
24
+
25
+ it { should be_a(described_class) }
26
+ end
27
+ end
28
+
29
+ describe "#open_orders" do
30
+ subject { provider.open_orders }
31
+
32
+ let(:provider) { described_class.new(options, client) }
33
+ let(:options) { { username: "double", password: "double" } }
34
+ let(:client) { double.as_null_object }
35
+
36
+ let(:request) { double("request", call: open_orders) }
37
+ let(:open_orders) { double("open orders") }
38
+
39
+ before do
40
+ described_class::OpenOrdersRequest.stub(:new).with(client) { request }
41
+ end
42
+
43
+ it { should be(open_orders) }
44
+ end
45
+ end
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+
3
+ describe Providers::MtGox::AccountInfoParser do
4
+ describe ".parse_wallets" do
5
+ subject { described_class.parse_wallets(raw_wallets) }
6
+
7
+ let(:raw_wallets) {
8
+ {"EUR" => {"Balance" => balance}}
9
+ }
10
+ let(:balance) { double }
11
+
12
+ it "delegates each wallet balance to ValueWithCurrency" do
13
+ Providers::MtGox::ValueWithCurrencyCoercer.
14
+ should_receive(:call).
15
+ with(balance)
16
+ subject
17
+ end
18
+ end
19
+
20
+ describe "#parse" do
21
+ subject(:account) { described_class.new(data).parse }
22
+
23
+ let(:data) {
24
+ {
25
+ "Wallets" => {
26
+ "BTC" => {
27
+ "Balance" => {"value_int" => "99470000", "currency" => "BTC"}
28
+ },
29
+ "EUR" => {
30
+ "Balance" => {"value_int" => "12314", "currency" => "EUR"},
31
+ },
32
+ "USD" => {
33
+ "Balance" => {"value_int" => "353302728", "currency" => "USD"},
34
+ },
35
+ },
36
+ "Trade_Fee" => 0.53
37
+ }
38
+ }
39
+
40
+ it { should be_a(Account) }
41
+ its(:fee) { should eq(0.53) }
42
+
43
+ context "btc wallet" do
44
+ subject { account.wallet("BTC") }
45
+ its(:balance) { should eq(0.9947) }
46
+ end
47
+
48
+ context "eur wallet" do
49
+ subject { account.wallet("EUR") }
50
+ its(:balance) { should eq(0.12314) }
51
+ end
52
+
53
+ context "usd wallet" do
54
+ subject { account.wallet("USD") }
55
+ its(:balance) { should eq(3533.02728) }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe Providers::MtGox::AccountInfoRequest do
4
+ describe "#call" do
5
+ subject { request.call }
6
+
7
+ let(:request) { described_class.new(client) }
8
+ let(:client) { HttpRequestMock.new }
9
+
10
+ let(:result) { {"data" => raw_account} }
11
+ let(:raw_account) { double }
12
+
13
+ before do
14
+ client.stub(:post).with("money/info").and_return(result)
15
+ end
16
+
17
+ it "parses open orders" do
18
+ Providers::MtGox::AccountInfoParser.should_receive(:new).
19
+ with(raw_account).
20
+ and_return(double(parse: "result"))
21
+ expect(subject).to eq("result")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,55 @@
1
+ require "spec_helper"
2
+
3
+ describe Providers::MtGox::HttpClient::HmacMiddleware do
4
+ describe ".extract_params" do
5
+ subject { described_class.extract_params(env) }
6
+
7
+ let(:env) { {request_headers: headers, url: url, body: body} }
8
+
9
+ let(:headers) { {} }
10
+ let(:url) { double(to_s: "https://data.mtgox.com/api/2/money/orders") }
11
+ let(:body) { {} }
12
+
13
+ its([0]) { should eql(headers) }
14
+ its([1]) { should eq("money/orders") }
15
+ its([2]) { should eql(body) }
16
+ end
17
+
18
+ describe "#call" do
19
+ subject { described_class.new(app, options).call(env) }
20
+
21
+ let(:app) { double }
22
+ let(:options) { {key: "KEY", secret: "c2VjcmV0" } }
23
+ let(:env) { double }
24
+
25
+ let(:headers) { {} }
26
+ let(:path) { "money/orders" }
27
+ let(:body) { "nonce=123" }
28
+
29
+ before do
30
+ described_class.stub(:extract_params).with(env).
31
+ and_return([headers, path, body])
32
+ app.stub(:call)
33
+ end
34
+
35
+ it "forwards environment to app" do
36
+ app.should_receive(:call).with(env)
37
+ subject
38
+ end
39
+
40
+ it "adds Rest-Key to headers" do
41
+ subject
42
+ expect(headers["Rest-Key"]).to eq("KEY")
43
+ end
44
+
45
+ it "adds Rest-Sign to headers" do
46
+ subject
47
+ expect(headers["Rest-Sign"]).to eq("Jd1OnkDAy0mkSCq1+w3Aiyeotw+lGF0GOR3tp5Ch1DMkyRW9QJ4FrmyFZIoee5itfAOnq9sRslqPd3n3UKw64A==")
48
+ end
49
+
50
+ it "adds User-Agent to headers" do
51
+ subject
52
+ expect(headers["User-Agent"]).to eq("bitbot-trader")
53
+ end
54
+ end
55
+ end