rbtc_arbitrage_simple 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +23 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +156 -0
- data/Guardfile +8 -0
- data/LICENSE.txt +22 -0
- data/Procfile +1 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/bin/rbtc_simple +4 -0
- data/dummy_web_server.rb +9 -0
- data/lib/rbtc_arbitrage.rb +15 -0
- data/lib/rbtc_arbitrage/campbx.rb +98 -0
- data/lib/rbtc_arbitrage/cli.rb +19 -0
- data/lib/rbtc_arbitrage/client.rb +48 -0
- data/lib/rbtc_arbitrage/clients/btce_client.rb +62 -0
- data/lib/rbtc_arbitrage/clients/campbx_client.rb +48 -0
- data/lib/rbtc_arbitrage/clients/client.rb.example +46 -0
- data/lib/rbtc_arbitrage/clients/coinbase_client.rb +63 -0
- data/lib/rbtc_arbitrage/clients/mtgox_client.rb +56 -0
- data/lib/rbtc_arbitrage/trader.rb +123 -0
- data/lib/rbtc_arbitrage/version.rb +3 -0
- data/rbtc_arbitrage.gemspec +30 -0
- data/spec/cli_spec.rb +8 -0
- data/spec/client_spec.rb +33 -0
- data/spec/clients/btce_client_spec.rb +93 -0
- data/spec/clients/campbx_client_spec.rb +66 -0
- data/spec/clients/coinbase_client_spec.rb +66 -0
- data/spec/clients/mtgox_client_spec.rb +53 -0
- data/spec/rbtc_arbitrage_spec.rb +11 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BitstampClient/_balance/fetches_the_balance_correctly.yml +96 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BitstampClient/_price/fetches_price_for_buy_correctly.yml +52 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BitstampClient/_price/fetches_price_for_sell_correctly.yml +52 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BitstampClient/_price/fetches_prices_correctly.yml +44 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BtceClient/_balance/should_raise_if_bad_API_keys.yml +53 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BtceClient/_price/calls_btc-e.yml +91 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BtceClient/_price/fetches_price_for_buy_correctly.yml +47 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_BtceClient/_price/fetches_price_for_sell_correctly.yml +47 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_CampbxClient/_balance/fetches_the_balance_correctly.yml +95 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_CampbxClient/_price/fetches_price_for_buy_correctly.yml +49 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_CampbxClient/_price/fetches_price_for_sell_correctly.yml +49 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_CoinbaseClient/_balance/fetches_the_balance_correctly.yml +109 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_CoinbaseClient/_price/calls_coinbase.yml +215 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_CoinbaseClient/_price/fetches_price_for_buy_correctly.yml +56 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_CoinbaseClient/_price/fetches_price_for_sell_correctly.yml +56 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_MtGoxClient/_balance/fetches_the_balance_correctly.yml +77 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_MtGoxClient/_price/fetches_price_for_buy_correctly.yml +44 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_MtGoxClient/_price/fetches_price_for_sell_correctly.yml +44 -0
- data/spec/support/cassettes/RbtcArbitrage_Clients_MtGoxClient/_price/fetches_prices_correctly.yml +85 -0
- data/spec/support/cassettes/RbtcArbitrage_Trader/_execute_trade/should_raise_SecurityError_if_not_live.yml +88 -0
- data/spec/support/cassettes/RbtcArbitrage_Trader/_execute_trade/when_live/raises_SecurityError_if_not_enough_BTC.yml +81 -0
- data/spec/support/cassettes/RbtcArbitrage_Trader/_execute_trade/when_live/raises_SecurityError_if_not_enough_USD.yml +81 -0
- data/spec/support/cassettes/RbtcArbitrage_Trader/_execute_trade/when_live/should_fetch_balance.yml +88 -0
- data/spec/support/cassettes/RbtcArbitrage_Trader/_execute_trade/when_live/shouldn_t_raise_security_error.yml +166 -0
- data/spec/support/cassettes/RbtcArbitrage_Trader/_fetch_prices/gets_the_right_price_set.yml +173 -0
- data/spec/trader_spec.rb +228 -0
- metadata +249 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
class CLI < Thor
|
3
|
+
|
4
|
+
desc "arbitrage", "Get information about the current arbitrage levels."
|
5
|
+
option :live, type: :boolean, default: false, desc: "Execute live trades."
|
6
|
+
option :cutoff, type: :numeric, default: 2, desc: "The minimum profit level required to execute a trade."
|
7
|
+
option :volume, type: :numeric, default: 0.01, desc: "The amount of bitcoins to trade per transaction."
|
8
|
+
option :verbose, type: :boolean, default: true, desc: "Whether you wish to log information."
|
9
|
+
option :buyer, type: :string, default: "campbx"
|
10
|
+
option :seller, type: :string, default: "mtgox"
|
11
|
+
option :repeat, type: :numeric, default: nil
|
12
|
+
def trade
|
13
|
+
RbtcArbitrage::Trader.new(options).trade
|
14
|
+
end
|
15
|
+
|
16
|
+
default_task :trade
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
module Client
|
3
|
+
attr_accessor :options
|
4
|
+
attr_writer :balance
|
5
|
+
|
6
|
+
def initialize config={}
|
7
|
+
@options = config
|
8
|
+
@options = {}
|
9
|
+
set_key config, :volume, 0.01
|
10
|
+
set_key config, :cutoff, 2
|
11
|
+
set_key config, :logger, Logger.new(STDOUT)
|
12
|
+
set_key config, :verbose, true
|
13
|
+
set_key config, :live, false
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_keys *args
|
18
|
+
args.each do |key|
|
19
|
+
key = key.to_s.upcase
|
20
|
+
if ENV[key].blank?
|
21
|
+
raise ArgumentError, "Exiting because missing required ENV variable $#{key}."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def buy
|
27
|
+
trade :buy
|
28
|
+
end
|
29
|
+
|
30
|
+
def sell
|
31
|
+
trade :sell
|
32
|
+
end
|
33
|
+
|
34
|
+
def address
|
35
|
+
ENV["#{exchange.to_s.upcase}_ADDRESS"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def logger
|
39
|
+
@options[:logger]
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def set_key config, key, default
|
45
|
+
@options[key] = config.has_key?(key) ? config[key] : default
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
module Clients
|
3
|
+
class BtceClient
|
4
|
+
include RbtcArbitrage::Client
|
5
|
+
|
6
|
+
def exchange
|
7
|
+
:btce
|
8
|
+
end
|
9
|
+
|
10
|
+
def balance
|
11
|
+
return @balance if @balance
|
12
|
+
begin
|
13
|
+
balances = interface.get_info["return"]["funds"]
|
14
|
+
@balance = [balances["btc"], balances["usd"]]
|
15
|
+
rescue NoMethodError => e
|
16
|
+
raise SecurityError, "Invalid API key for BTC-e"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def interface
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate_env
|
24
|
+
validate_keys :btce_key, :btce_secret, :btce_address
|
25
|
+
end
|
26
|
+
|
27
|
+
def trade action
|
28
|
+
warning = "BTC-E does not support API bitcoin transfer. "
|
29
|
+
warning << "If you really want to trade, you will have "
|
30
|
+
warning << "to manually send bitcoin. Enter 'accept' to continue. \n> "
|
31
|
+
@options[:logger].warn warning if @options[:verbose]
|
32
|
+
return false unless gets.chomp == "accept"
|
33
|
+
opts = {
|
34
|
+
type: action,
|
35
|
+
rate: price(action),
|
36
|
+
amount: @options[:volume],
|
37
|
+
pair: "btc_usd"
|
38
|
+
}
|
39
|
+
interface.trade opts
|
40
|
+
end
|
41
|
+
|
42
|
+
def price action
|
43
|
+
return @ticker[action.to_s] if @ticker
|
44
|
+
@ticker = Btce::Ticker.new("btc_usd").json["ticker"]
|
45
|
+
@ticker[action.to_s]
|
46
|
+
end
|
47
|
+
|
48
|
+
def transfer client
|
49
|
+
if @options[:verbose]
|
50
|
+
error = "BTC-E does not have a 'transfer' API.\n"
|
51
|
+
error << "You must transfer bitcoin manually."
|
52
|
+
@options[:logger].error error
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def interface
|
57
|
+
opts = {key: ENV['BTCE_KEY'], secret: ENV['BTCE_SECRET']}
|
58
|
+
@interface ||= Btce::TradeAPI.new(opts)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
module Clients
|
3
|
+
class CampbxClient
|
4
|
+
include RbtcArbitrage::Client
|
5
|
+
|
6
|
+
def exchange
|
7
|
+
:campbx
|
8
|
+
end
|
9
|
+
|
10
|
+
def balance
|
11
|
+
return @balance if @balance
|
12
|
+
funds = interface.my_funds
|
13
|
+
[funds["Total BTC"].to_f, funds["Total USD"].to_f]
|
14
|
+
end
|
15
|
+
|
16
|
+
def interface
|
17
|
+
@interface ||= CampBX::API.new(ENV['CAMPBX_KEY'],ENV['CAMPBX_SECRET'])
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_env
|
21
|
+
validate_keys :campbx_key, :campbx_secret
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def trade action
|
26
|
+
trade_mode = "Quick#{action.to_s.capitalize}"
|
27
|
+
interface.trade_enter trade_mode, @options[:volume], price(action)
|
28
|
+
end
|
29
|
+
|
30
|
+
def price action
|
31
|
+
return @price if @price
|
32
|
+
action = {
|
33
|
+
buy: "Best Ask",
|
34
|
+
sell: "Best Bid",
|
35
|
+
}[action]
|
36
|
+
@price = interface.xticker[action].to_f
|
37
|
+
end
|
38
|
+
|
39
|
+
def transfer client
|
40
|
+
interface.send_btc client.address, @options[:volume]
|
41
|
+
end
|
42
|
+
|
43
|
+
def address
|
44
|
+
@address ||= interface.get_btc_address
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
module Clients
|
3
|
+
class ExchangeClient
|
4
|
+
include RbtcArbitrage::Client
|
5
|
+
|
6
|
+
# return a symbol as the name
|
7
|
+
# of this exchange
|
8
|
+
def exchange
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns an array of Floats.
|
12
|
+
# The first element is the balance in BTC;
|
13
|
+
# The second is in USD.
|
14
|
+
def balance
|
15
|
+
end
|
16
|
+
|
17
|
+
def interface
|
18
|
+
end
|
19
|
+
|
20
|
+
# Configures the client's API keys.
|
21
|
+
def validate_env
|
22
|
+
end
|
23
|
+
|
24
|
+
# `action` is :buy or :sell
|
25
|
+
def trade action
|
26
|
+
end
|
27
|
+
|
28
|
+
# `action` is :buy or :sell
|
29
|
+
# Returns a Numeric type.
|
30
|
+
def price action
|
31
|
+
end
|
32
|
+
|
33
|
+
# Transfers BTC to the address of a different
|
34
|
+
# exchange.
|
35
|
+
def transfer client
|
36
|
+
end
|
37
|
+
|
38
|
+
# If there is an API method to fetch your
|
39
|
+
# BTC address, implement this, otherwise
|
40
|
+
# remove this method and set the ENV
|
41
|
+
# variable [this-exchange-name-in-caps]_ADDRESS
|
42
|
+
def address
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
module Clients
|
3
|
+
class CoinbaseClient
|
4
|
+
include RbtcArbitrage::Client
|
5
|
+
|
6
|
+
# return a symbol as the name
|
7
|
+
# of this exchange
|
8
|
+
def exchange
|
9
|
+
:coinbase
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns an array of Floats.
|
13
|
+
# The first element is the balance in BTC;
|
14
|
+
# The second is in USD.
|
15
|
+
def balance
|
16
|
+
if @options[:verbose]
|
17
|
+
warning = "Coinbase doesn't provide a USD balance because"
|
18
|
+
warning << " it connects to your bank account. Be careful, "
|
19
|
+
warning << "because this will withdraw directly from your accounts."
|
20
|
+
logger.warn warning
|
21
|
+
end
|
22
|
+
@balance ||= [interface.balance.to_f, max_float]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Configures the client's API keys.
|
26
|
+
def validate_env
|
27
|
+
validate_keys :coinbase_key, :coinbase_address
|
28
|
+
end
|
29
|
+
|
30
|
+
# `action` is :buy or :sell
|
31
|
+
def trade action
|
32
|
+
interface.send("#{action}!".to_sym, @options[:volume])
|
33
|
+
end
|
34
|
+
|
35
|
+
# `action` is :buy or :sell
|
36
|
+
# Returns a Numeric type.
|
37
|
+
def price action
|
38
|
+
method = "#{action}_price".to_sym
|
39
|
+
@price ||= interface.send(method).to_f
|
40
|
+
end
|
41
|
+
|
42
|
+
# Transfers BTC to the address of a different
|
43
|
+
# exchange.
|
44
|
+
def transfer client
|
45
|
+
interface.send_money client.address, @options[:volume]
|
46
|
+
end
|
47
|
+
|
48
|
+
def interface
|
49
|
+
@interface ||= Coinbase::Client.new(ENV['COINBASE_KEY'])
|
50
|
+
end
|
51
|
+
|
52
|
+
def address
|
53
|
+
@address ||= interface.receive_address.address
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def max_float
|
59
|
+
Float::MAX
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
module Clients
|
3
|
+
class MtGoxClient
|
4
|
+
include RbtcArbitrage::Client
|
5
|
+
|
6
|
+
##
|
7
|
+
# Returns an array of Floats.
|
8
|
+
# The first element is the balance in BTC;
|
9
|
+
# The second is in USD.
|
10
|
+
def balance
|
11
|
+
return @balance if @balance
|
12
|
+
balances = MtGox.balance
|
13
|
+
@balance = [balances[0].amount.to_f, balances[1].amount.to_f]
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Configures the MtGox
|
18
|
+
# client's API keys.
|
19
|
+
def validate_env
|
20
|
+
validate_keys :mtgox_key, :mtgox_secret, :mtgox_address
|
21
|
+
MtGox.configure do |config|
|
22
|
+
config.key = ENV["MTGOX_KEY"]
|
23
|
+
config.secret = ENV["MTGOX_SECRET"]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def exchange
|
28
|
+
:mtgox
|
29
|
+
end
|
30
|
+
|
31
|
+
# `action` is :buy or :sell
|
32
|
+
# Returns a Numeric type.
|
33
|
+
def price action
|
34
|
+
return @price if @price
|
35
|
+
action = {
|
36
|
+
buy: :sell,
|
37
|
+
sell: :buy,
|
38
|
+
}[action]
|
39
|
+
@price = MtGox.ticker.send(action)
|
40
|
+
end
|
41
|
+
|
42
|
+
# `action` is :buy or :sell
|
43
|
+
def trade action
|
44
|
+
action = "#{action.to_s}!".to_sym
|
45
|
+
MtGox.send(action, @options[:volume], :market)
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Transfers BTC to the address of a different
|
50
|
+
# exchange.
|
51
|
+
def transfer other_client
|
52
|
+
MtGox.withdraw! @options[:volume], other_client.address
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module RbtcArbitrage
|
2
|
+
class Trader
|
3
|
+
attr_reader :buy_client, :sell_client, :received
|
4
|
+
attr_accessor :buyer, :seller, :options
|
5
|
+
|
6
|
+
def initialize config={}
|
7
|
+
opts = {}
|
8
|
+
config.each do |key, val|
|
9
|
+
opts[(key.to_sym rescue key) || key] = val
|
10
|
+
end
|
11
|
+
@buyer = {}
|
12
|
+
@seller = {}
|
13
|
+
@options = {}
|
14
|
+
set_key opts, :volume, 0.01
|
15
|
+
set_key opts, :cutoff, 2
|
16
|
+
set_key opts, :logger, Logger.new(STDOUT)
|
17
|
+
set_key opts, :verbose, true
|
18
|
+
set_key opts, :live, false
|
19
|
+
set_key opts, :repeat, nil
|
20
|
+
exchange = opts[:buyer] || :campbx
|
21
|
+
@buy_client = client_for_exchange(exchange)
|
22
|
+
exchange = opts[:seller] || :mtgox
|
23
|
+
@sell_client = client_for_exchange(exchange)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_key config, key, default
|
28
|
+
@options[key] = config.has_key?(key) ? config[key] : default
|
29
|
+
end
|
30
|
+
|
31
|
+
def trade
|
32
|
+
fetch_prices
|
33
|
+
log_info if options[:verbose]
|
34
|
+
|
35
|
+
if options[:live] && options[:cutoff] > @percent
|
36
|
+
raise SecurityError, "Exiting because real profit (#{@percent.round(2)}%) is less than cutoff (#{options[:cutoff].round(2)}%)"
|
37
|
+
end
|
38
|
+
|
39
|
+
execute_trade if options[:live]
|
40
|
+
|
41
|
+
if @options[:repeat]
|
42
|
+
trade_again
|
43
|
+
end
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def trade_again
|
48
|
+
sleep @options[:repeat]
|
49
|
+
@logger.info " - " if @options[:verbose]
|
50
|
+
@buy_client = @buy_client.class.new(@options)
|
51
|
+
@sell_client = @sell_client.class.new(@options)
|
52
|
+
trade
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute_trade
|
56
|
+
fetch_prices unless @paid
|
57
|
+
validate_env
|
58
|
+
raise SecurityError, "--live flag is false. Not executing trade." unless options[:live]
|
59
|
+
get_balance
|
60
|
+
if @percent > @options[:cutoff]
|
61
|
+
if @paid > buyer[:usd] || @options[:volume] > seller[:btc]
|
62
|
+
raise SecurityError, "Not enough funds. Exiting."
|
63
|
+
else
|
64
|
+
logger.info "Trading live!" if options[:verbose]
|
65
|
+
@buy_client.buy
|
66
|
+
@sell_client.sell
|
67
|
+
@buy_client.transfer @sell_client
|
68
|
+
end
|
69
|
+
else
|
70
|
+
logger.info "Not trading live because cutoff is higher than profit." if @options[:verbose]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def fetch_prices
|
75
|
+
logger.info "Fetching exchange rates" if @options[:verbose]
|
76
|
+
buyer[:price] = @buy_client.price(:buy)
|
77
|
+
seller[:price] = @sell_client.price(:sell)
|
78
|
+
prices = [buyer[:price], seller[:price]]
|
79
|
+
@paid = buyer[:price] * 1.006 * @options[:volume]
|
80
|
+
@received = seller[:price] * 0.994 * @options[:volume]
|
81
|
+
@percent = ((received/@paid - 1) * 100).round(2)
|
82
|
+
end
|
83
|
+
|
84
|
+
def log_info
|
85
|
+
lower_ex = @buy_client.exchange.to_s.capitalize
|
86
|
+
higher_ex = @sell_client.exchange.to_s.capitalize
|
87
|
+
logger.info "#{lower_ex}: $#{buyer[:price].round(2)}"
|
88
|
+
logger.info "#{higher_ex}: $#{seller[:price].round(2)}"
|
89
|
+
logger.info "buying #{@options[:volume]} btc from #{lower_ex} for $#{@paid.round(2)}"
|
90
|
+
logger.info "selling #{@options[:volume]} btc on #{higher_ex} for $#{@received.round(2)}"
|
91
|
+
logger.info "profit: $#{(@received - @paid).round(2)} (#{@percent.round(2)}%)"
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_balance
|
95
|
+
@seller[:btc], @seller[:usd] = @sell_client.balance
|
96
|
+
@buyer[:btc], @buyer[:usd] = @buy_client.balance
|
97
|
+
end
|
98
|
+
|
99
|
+
def logger
|
100
|
+
@options[:logger]
|
101
|
+
end
|
102
|
+
|
103
|
+
def validate_env
|
104
|
+
[@sell_client, @buy_client].each do |client|
|
105
|
+
client.validate_env
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def client_for_exchange market
|
110
|
+
market = market.to_sym unless market.is_a?(Symbol)
|
111
|
+
clazz = RbtcArbitrage::Clients.constants.find do |c|
|
112
|
+
clazz = RbtcArbitrage::Clients.const_get(c)
|
113
|
+
clazz.new.exchange == market
|
114
|
+
end
|
115
|
+
begin
|
116
|
+
clazz = RbtcArbitrage::Clients.const_get(clazz)
|
117
|
+
clazz.new @options
|
118
|
+
rescue TypeError => e
|
119
|
+
raise ArgumentError, "Invalid exchange - '#{market}'"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|