Class: RbtcArbitrage::Trader

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = {}) ⇒ Trader

Returns a new instance of Trader.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/rbtc_arbitrage/trader.rb', line 6

def initialize config={}
  opts = {}
  config.each do |key, val|
    opts[(key.to_sym rescue key) || key] = val
  end
  @buyer   = {}
  @seller  = {}
  @options = {}
  set_key opts, :volume, 0.01
  set_key opts, :cutoff, 2
  default_logger = Logger.new($stdout)
  default_logger.datetime_format = "%^b %e %Y %l:%M:%S %p %z"
  set_key opts, :logger, default_logger
  set_key opts, :verbose, true
  set_key opts, :live, false
  set_key opts, :repeat, nil
  set_key opts, :notify, false
  exchange = opts[:buyer] || :bitstamp
  @buy_client = client_for_exchange(exchange)
  exchange = opts[:seller] || :campbx
  @sell_client = client_for_exchange(exchange)
  self
end

Instance Attribute Details

#buy_clientObject (readonly)

Returns the value of attribute buy_client.



3
4
5
# File 'lib/rbtc_arbitrage/trader.rb', line 3

def buy_client
  @buy_client
end

#buyerObject

Returns the value of attribute buyer.



4
5
6
# File 'lib/rbtc_arbitrage/trader.rb', line 4

def buyer
  @buyer
end

#optionsObject

Returns the value of attribute options.



4
5
6
# File 'lib/rbtc_arbitrage/trader.rb', line 4

def options
  @options
end

#receivedObject (readonly)

Returns the value of attribute received.



3
4
5
# File 'lib/rbtc_arbitrage/trader.rb', line 3

def received
  @received
end

#sell_clientObject (readonly)

Returns the value of attribute sell_client.



3
4
5
# File 'lib/rbtc_arbitrage/trader.rb', line 3

def sell_client
  @sell_client
end

#sellerObject

Returns the value of attribute seller.



4
5
6
# File 'lib/rbtc_arbitrage/trader.rb', line 4

def seller
  @seller
end

Instance Method Details

#client_for_exchange(market) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rbtc_arbitrage/trader.rb', line 130

def client_for_exchange market
  market = market.to_sym unless market.is_a?(Symbol)
  clazz = RbtcArbitrage::Clients.constants.find do |c|
    clazz = RbtcArbitrage::Clients.const_get(c)
    clazz.new.exchange == market
  end
  begin
    clazz = RbtcArbitrage::Clients.const_get(clazz)
    clazz.new @options
  rescue TypeError => e
    raise ArgumentError, "Invalid exchange - '#{market}'"
  end
end

#execute_tradeObject

Raises:

  • (SecurityError)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rbtc_arbitrage/trader.rb', line 61

def execute_trade
  fetch_prices unless @paid
  validate_env
  raise SecurityError, "--live flag is false. Not executing trade." unless options[:live]
  get_balance
  if @percent > @options[:cutoff]
    if @paid > buyer[:usd] || @options[:volume] > seller[:btc]
      raise SecurityError, "Not enough funds. Exiting."
    else
      logger.info "Trading live!" if options[:verbose]
      @buy_client.buy
      @sell_client.sell
      @buy_client.transfer @sell_client
    end
  else
    logger.info "Not trading live because cutoff is higher than profit." if @options[:verbose]
  end
end

#fetch_pricesObject



80
81
82
83
84
85
86
87
88
# File 'lib/rbtc_arbitrage/trader.rb', line 80

def fetch_prices
  logger.info "Fetching exchange rates" if @options[:verbose]
  buyer[:price] = @buy_client.price(:buy)
  seller[:price] = @sell_client.price(:sell)
  prices = [buyer[:price], seller[:price]]
  @paid = buyer[:price] * 1.006 * @options[:volume]
  @received = seller[:price] * 0.994 * @options[:volume]
  @percent = ((received/@paid - 1) * 100).round(2)
end

#get_balanceObject



106
107
108
109
# File 'lib/rbtc_arbitrage/trader.rb', line 106

def get_balance
  @seller[:btc], @seller[:usd] = @sell_client.balance
  @buyer[:btc], @buyer[:usd] = @buy_client.balance
end

#log_infoObject



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/rbtc_arbitrage/trader.rb', line 90

def log_info
  lower_ex = @buy_client.exchange.to_s.capitalize
  higher_ex = @sell_client.exchange.to_s.capitalize
  logger.info "#{lower_ex}: $#{buyer[:price].round(2)}"
  logger.info "#{higher_ex}: $#{seller[:price].round(2)}"
  logger.info "buying #{@options[:volume]} btc from #{lower_ex} for $#{@paid.round(2)}"
  logger.info "selling #{@options[:volume]} btc on #{higher_ex} for $#{@received.round(2)}"

  profit_msg = "profit: $#{(@received - @paid).round(2)} (#{@percent.round(2)}%)"
  if cutoff = @options[:cutoff]
    profit_msg << " is #{@percent < cutoff ? 'below' : 'above'} cutoff"
    profit_msg << " of #{cutoff}%."
  end
  logger.info profit_msg
end

#loggerObject



111
112
113
# File 'lib/rbtc_arbitrage/trader.rb', line 111

def logger
  @options[:logger]
end

#notificationObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/rbtc_arbitrage/trader.rb', line 173

def notification
  lower_ex = @buy_client.exchange.to_s.capitalize
  higher_ex = @sell_client.exchange.to_s.capitalize
  "  Update from your friendly rbtc_arbitrage trader:\n\n  -------------------\n\n  \#{lower_ex}: $\#{buyer[:price].round(2)}\n  \#{higher_ex}: $\#{seller[:price].round(2)}\n  buying \#{@options[:volume]} btc from \#{lower_ex} for $\#{@paid.round(2)}\n  selling \#{@options[:volume]} btc on \#{higher_ex} for $\#{@received.round(2)}\n  profit: $\#{(@received - @paid).round(2)} (\#{@percent.round(2)}%)\n\n  -------------------\n\n  options:\n\n  \#{options.reject{|k,v| v.is_a?(Logger)}.collect{|k,v| \"\#{k}: \#{v.to_s}\"}.join(\" | \")}\n\n  -------------------\n\n  https://github.com/hstove/rbtc_arbitrage\n  eos\nend\n"

#notifyObject



144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rbtc_arbitrage/trader.rb', line 144

def notify
  return false unless options[:notify]
  return false unless @percent > options[:cutoff]
  setup_pony

  options[:logger].info "Sending email to #{ENV['SENDGRID_EMAIL']}"
  Pony.mail({
    body: notification,
    to: ENV['SENDGRID_EMAIL'],
  })
end

#set_key(config, key, default) ⇒ Object



30
31
32
# File 'lib/rbtc_arbitrage/trader.rb', line 30

def set_key config, key, default
  @options[key] = config.has_key?(key) ? config[key] : default
end

#setup_ponyObject



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/rbtc_arbitrage/trader.rb', line 156

def setup_pony
  Pony.options = {
    from: ENV['FROM_EMAIL'] || "[email protected]",
    subject: "rbtc_arbitrage notification",
    via: :smtp,
    via_options: {
      address: 'smtp.sendgrid.net',
      port: '587',
      domain: 'heroku.com',
      user_name: ENV['SENDGRID_USERNAME'],
      password: ENV['SENDGRID_PASSWORD'],
      authentication: :plain,
      enable_starttls_auto: true
    }
  }
end

#tradeObject



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/rbtc_arbitrage/trader.rb', line 34

def trade
  fetch_prices
  log_info if options[:verbose]

  if options[:live] && options[:cutoff] > @percent
    raise SecurityError, "Exiting because real profit (#{@percent.round(2)}%) is less than cutoff (#{options[:cutoff].round(2)}%)"
  end

  execute_trade if options[:live]

  notify

  if @options[:repeat]
    trade_again
  end

  self
end

#trade_againObject



53
54
55
56
57
58
59
# File 'lib/rbtc_arbitrage/trader.rb', line 53

def trade_again
  sleep @options[:repeat]
  logger.info " - " if @options[:verbose]
  @buy_client = @buy_client.class.new(@options)
  @sell_client = @sell_client.class.new(@options)
  trade
end

#validate_envObject



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/rbtc_arbitrage/trader.rb', line 115

def validate_env
  [@sell_client, @buy_client].each do |client|
    client.validate_env
  end
  if options[:notify]
    ["PASSWORD","USERNAME","EMAIL"].each do |key|
      key = "SENDGRID_#{key}"
      unless ENV[key]
        raise ArgumentError, "Exiting because missing required ENV variable $#{key}."
      end
    end
    setup_pony
  end
end