Browse Source

bringing all to git

master
lontra 3 years ago
commit
5172b6dcf4
22 changed files with 19921 additions and 0 deletions
  1. +4
    -0
      .gitignore
  2. +1
    -0
      README.md
  3. +83
    -0
      app/arbitrage.cr
  4. +81
    -0
      app/broker.cr
  5. +20
    -0
      app/config.cr
  6. +80
    -0
      app/helper.cr
  7. +114
    -0
      app/market.cr
  8. +110
    -0
      app/order.cr
  9. +140
    -0
      app/strategy.cr
  10. +108
    -0
      app/summary.cr
  11. +14
    -0
      app/tick.cr
  12. +123
    -0
      app/wallet.cr
  13. +8
    -0
      deploy.sh
  14. +11
    -0
      doc/arbitrage.md
  15. +53
    -0
      flow.cr
  16. +33
    -0
      renda.cr
  17. +22
    -0
      shard.lock
  18. +8
    -0
      shard.yml
  19. +4
    -0
      web/jquery-3.2.1.min.js
  20. +161
    -0
      web/monitor.html
  21. +18608
    -0
      web/monitor_files/Chart.bundle.js
  22. +135
    -0
      web/monitor_files/utils.js

+ 4
- 0
.gitignore View File

@ -0,0 +1,4 @@
/lib
/log
.shards
renda

+ 1
- 0
README.md View File

@ -0,0 +1 @@
= Renda Basica

+ 83
- 0
app/arbitrage.cr View File

@ -0,0 +1,83 @@
class Arbitrage
def self.make(from_broker, to_broker, currency, value)
puts "=> transfer $#{value} of #{currency.upcase} from #{from_broker} to #{to_broker}".colorize(:green)
# actual values
from_broker_value = to_broker_value = 0
if from_broker == "cryptopia"
from_broker_value = last_from_cryptopia(currency)
to_broker_value = last_from_bittrex(currency)
else
from_broker_value = last_from_bittrex(currency)
to_broker_value = last_from_cryptopia(currency)
end
perc = percentage(from_broker_value, to_broker_value)
last_btc_in_usd = Market.last_value_from("usdt-btc")
puts "=> #{from_broker} | #{from_broker_value} ฿ | $#{from_broker_value*last_btc_in_usd}".colorize(:yellow)
puts "=> #{to_broker} | #{to_broker_value} ฿ | $#{to_broker_value*last_btc_in_usd}".colorize(:yellow)
puts "=> +#{perc}% difference".colorize(:green)
exchange_fee = value * 0.02 # 0.2%
neto = value - exchange_fee
#puts "=> -$#{exchange_fee} exchange fee | $#{neto}".colorize(:red)
in_currency = ((1/from_broker_value)/last_btc_in_usd)*neto
in_btc = neto/last_btc_in_usd
puts "=> #{in_btc} ฿ => #{in_currency} #{currency.upcase}".colorize(:yellow)
count = total = 0
buy_orders = get_buy_orders(currency)
#puts "#{buy_orders.size} buy orders:"
buy_orders.each do |buy|
price = buy["Price"].to_s.to_f64
if price > from_broker_value
count += 1
qtd = buy["Total"].to_s.to_f64
puts "+#{percentage(from_broker_value, price)}% | $#{qtd*last_btc_in_usd}"
total += qtd
end
end
line
to_buy_usd = total*last_btc_in_usd
# quanto investir?
# (potencial / 2) + 0.25% exchange + 0.2 coin" transfer
puts "0.2'' = #{(1/from_broker_value)*0.2} "
puts "#{count} buyers with $#{to_buy_usd}" if total > 0
line
# transfer
#TODO: API send
#0.002
#puts "=> -$#{exchange_fee} fee | $#{neto}".colorize(:red)
end
def self.get_buy_orders(currency)
json = JSON.parse(get("https://www.cryptopia.co.nz", "/api/GetMarketOrders/#{currency.upcase}_BTC"))
Logger.log(json.to_pretty_json)
json["Data"]["Buy"]
end
def self.last_from_cryptopia(currency)
json = JSON.parse(get("https://www.cryptopia.co.nz", "/api/GetMarket/#{currency}_BTC"))
Logger.log(json.to_pretty_json)
json["Data"]["LastPrice"].to_s.to_f64
end
def self.last_from_bittrex(market)
json = JSON.parse(get("https://bittrex.com/", "/api/v1.1/public/getmarketsummary?market=btc-#{market}"))
Logger.log(json.to_pretty_json)
json["result"].first["Last"].to_s.to_f64
end
def self.get(host, url)
HTTP::Client.new(URI.parse(host)).get(url).body
end
def self.market_exists?(market, markets)
markets.each{|m| return true if m.marketname==market}
false
end
end
#
# Arbitrage.potential_markets.each do |c|
# Arbitrage.make("bittrex", "cryptopia", c, 100)
# line
# end

+ 81
- 0
app/broker.cr View File

@ -0,0 +1,81 @@
require "base64"
require "json"
require "time"
require "openssl/hmac"
require "colorize"
require "http/client"
require "pretty_print"
require "./config"
require "./market"
require "./order"
require "./helper"
class Broker
@@requests_count = 0.as(Int32)
HOST = "https://bittrex.com/api/v1.1"
KEY = "ze41874d3706c4d98be6a2206bbbe41eaf" # example! change it.
PVT = "d4b67bf4bbbca4f56bfe99103ee352671g" # example! change it.
# for reference
WALLET = "s15dTUuy2zawbcgkgrc9Cnsa13t9YWaVSJus" # example! change it.
def self.get(method, params = nil, retry_count=0) #todo: retry as param++
start_time = Time.now
client = HTTP::Client.new(URI.parse(HOST))
url = "#{HOST}/#{method}"
Logger.log "-> #{method} #{params}"
# sign non-public requests
unless method.starts_with?("public")
url += "?apikey=#{KEY}&nonce=#{start_time.epoch_ms}&#{params}"
apisign = OpenSSL::HMAC.hexdigest(:sha512, PVT, url)
headers = HTTP::Headers{"apisign" => apisign}
else
url += "?#{params}"
end
result = nil
begin
@@requests_count += 1
response = client.get(url, headers: headers)
return again!(method, params, retry_count) unless response && response.body
json = JSON.parse(response.body.downcase)
#TODO: fix downcase Hash keys | fucks wallet addrs
if json["success"]
result = json["result"]
return again!(method, params, retry_count) if result.to_pretty_json == "null"
else
Logger.log "API error: #{json["message"]}", error: true
end
elapsed = Time.now - start_time
Logger.log "(#{elapsed.seconds}s#{elapsed.milliseconds}ms)".colorize(:yellow)
Logger.log "<- #{result.to_pretty_json}"
rescue err
puts
Logger.log "=> #{err} on #{method}\nfor #{url}", error: true
Logger.log json.to_pretty_json
puts
return again!(method, params, retry_count) if err.to_s.includes? "reset"
end
client.close
result.to_json
end
def self.again!(method, params, retry_count)
raise "Tryed to request 3x in vain" if retry_count==3
# Logger.log "=> retry #{method} #{params}", error: true
self.get(method, params, retry_count+=1)
end
def self.requests_count
@@requests_count.as(Int32)
end
# singleton class
def self.instance
@@instance ||= new
end
end

+ 20
- 0
app/config.cr View File

@ -0,0 +1,20 @@
class Config
property params = {} of Symbol => (String | Int32 | Float64 | Bool)
def initialize
@params[:log] = false
@params[:simulate] = false
@params[:retry] = false
@params[:start_time] = now
end
def self.params
self.instance.params
end
# singleton class
def self.instance
@@instance ||= new
end
end

+ 80
- 0
app/helper.cr View File

@ -0,0 +1,80 @@
TMZ = 2.hours # athens
line
puts "\t Renda Basica".colorize(:blue)
line
# finish! if interrupted
[Signal::QUIT, Signal::ABRT, Signal::INT, Signal::KILL, Signal::TERM].each do |s|
s.trap do
puts "=> Interrupted!".colorize(:red)
finish!
end
end
def line
puts "=================================".colorize(:blue)
end
def test_colors
colors = [:black, :red, :green, :yellow, :blue, :magenta, :cyan, :light_gray, :dark_gray, :light_red, :light_green, :light_yellow, :light_blue, :light_magenta, :light_cyan, :white]
colors.each do |c1|
colors.each do |c2|
puts "fore #{c1} | back #{c2}".colorize.fore(c1).back(c2)
end
end
end
def percentage(x, y)
x, y = x.to_f64, y.to_f64
pct = x < y ? ((y - x) / x) * 100.0 : - ((x - y) / y) * 100.0
pct.round(2)
end
def as_time(time, tmz=false)
parsed_time = Time.parse(time.to_s, "%Ft%T.%L")
tmz ? parsed_time + TMZ : parsed_time
end
def as_time_ago(span)
h = span.hours > 0 ? "#{span.hours}h" : ""
m = span.minutes > 0 ? "#{span.minutes}m" : ""
"#{h}#{m}#{span.seconds}s#{span.milliseconds}"
end
def as_dolar(value)
"$#{value.round(2)}"
end
def finish!
elapsed = Time.now - as_time(Config.params[:start_time])
line
puts "=> #{Broker.requests_count} requests in #{as_time_ago(elapsed)}".colorize(:yellow)
line
exit
end
def color(float : Float64)
float > 0 ? float.to_s.colorize(:green) : float.to_s.colorize(:red)
end
def now
Time.now.to_s("%Ft%T.%L")
end
def save_json(json, file)
File.open("log/#{file.to_s}.json", "w") do |f|
f.write("#{json.to_pretty_json}\n".to_slice)
end
end
class Logger
def self.log(msg, file=:renda, error=false)
File.open("log/#{file.to_s}.log", "a+") do |f|
stamped = "#{now} #{msg}"
puts stamped.colorize(error ? :red : :yellow) if Config.params[:log] || error
f.write("#{stamped}\n".to_slice)
end
end
end

+ 114
- 0
app/market.cr View File

@ -0,0 +1,114 @@
require "./helper"
require "./tick"
class Market
@@all = [] of Market
JSON.mapping({
marketname: String,
high: Float64,
low: Float64,
volume: Float64,
last: Float64,
basevolume: Float64,
timestamp: String,
bid: Float64,
ask: Float64,
openbuyorders: Float64,
opensellorders: Float64,
prevday: Float64,
created: String
# new attrs ?
})
def self.all(format=nil)
return @@all unless @@all.empty?
@@all = Array(Market).from_json(Broker.get("public/getmarketsummaries"))
if format
x = 0
@@all.each do |m|
puts "#{x+=1} # {m.marketname} \t #{percentage(m.openbuyorders, m.opensellorders)}% spread \t| change: #{percentage(m.prevday, m.last)}%".colorize(:yellow)
end
end
@@all
end
def self.fetch(market) : Market
Array(Market).from_json(Broker.get("public/getmarketsummary", "market=#{market}")).first
end
def self.exists?(market)
self.all.each{|m| return true if m.marketname==market}
false
end
def self.last_value_from(market)
last_tick = Tick.from_json(Broker.get("public/getticker", "market=#{market.downcase}"))
last_tick.last
end
def self.btc_to_dolar_of_date(datetime) : Float64
result = 0.0
begin
# date_in_btc = JSON.parse(File.read("log/orders.json"))[currency][datetime.to_s].to_s.to_f64
# result = date_in_btc * last_value_from("usdt-btc")
url = "/public?command=returnChartData&currencyPair=USDT_BTC&start=#{(datetime - 1.hour).epoch}&end=#{datetime.epoch}&period=86400"
client = HTTP::Client.new(URI.parse("https://poloniex.com"))
Logger.log "-> #{url}"
result = JSON.parse(client.get(url).body).first["weightedAverage"].to_s.to_f64.as(Float64)
client.close
rescue err
Logger.log(err, error: true)
end
result
end
def self.convert(market, value : Float64)
Market.fetch(market).last * value
end
def self.convert_to_dolar(currency, value)
return value if currency == "usdt"
value_in_btc = currency=="btc" ? value : self.convert("btc-#{currency}", value)
btc_in_dolar = Market.last_value_from("usdt-btc")
value_in_btc * btc_in_dolar
end
def self.observe_all
# btcs_markets = all.reject{|m| !m.marketname.starts_with?("btc")}
# puts "=> observing #{all.size} markets"
# btcs_markets.each do |m|
# spawn do
# Strategy.observe(m.marketname)
# end
# end
# Fiber.yield
# sleep
end
def self.follow(market, invested=1, proto=nil)
proto = Market.last_value_from(market) unless proto
in_usd = Market.last_value_from("usdt-btc")
puts "Follow #{market.upcase} at #{proto}...".colorize(:yellow)
loop do
value = Market.last_value_from(market)
perc = percentage(proto, value)
puts "#{perc}% | #{as_dolar(in_usd*value*invested)}"
sleep 1
end
end
def orderbook(market)
#TODO: /public/getorderbook
end
def history(market)
#TODO: /public/getmarkethistory
end
def self.currencies
get("public/getcurrencies")
#TODO: template as array
end
end

+ 110
- 0
app/order.cr View File

@ -0,0 +1,110 @@
require "./helper"
class Order
JSON.mapping({
accountid: String | Nil,
orderuuid: String,
exchange: String,
type: String | Nil,
ordertype: String | Nil,
quantity: Float64,
quantityremaining: Float64,
limit: Float64,
reserved: Float64 | Nil,
reserveremaining: Float64 | Nil,
commissionreserved: Float64 | Nil,
commissionreserveremaining: Float64 | Nil,
commissionpaid: Float64 | Nil,
price: Float64,
priceperunit: Float64 | Nil,
timestamp: String | Nil,
opened: String | Nil,
closed: String | Nil,
is_open: Bool | Nil,
sentinel: String | Nil,
cancelinitiated: Bool | Nil,
immediateorcancel: Bool,
isconditional: Bool,
condition: String,
conditiontarget: Bool | Nil,
#INFO: order history has other mapping
commission: {type: Float64, default: 0.to_f64},
order_dolar: {type: Float64, default: 0.to_f64},
atual_dolar: {type: Float64, default: 0.to_f64},
tax: {type: Float64, default: 0.to_f64},
change: {type: Float64, default: 0.to_f64}
})
def self.total_taxes
taxes = 0.to_f64.as(Float64)
self.all.each do |o|
taxes += o.tax
end
taxes
end
def self.all(format = nil)
orders = Array(Order).from_json(Broker.get("account/getorderhistory"))
return orders unless format
# TODO: add list of orderuid|finish_date
btc_usd = Market.last_value_from("usdt-btc")
orders.each do |o|
# set dolars paid when bought, actual price and taxes.
currency = o.exchange.split("-")[1]
day_dolar = Market.btc_to_dolar_of_date(as_time(o.closed))
o.order_dolar = day_dolar * o.quantity
o.atual_dolar = o.quantity * btc_usd
o.tax = day_dolar * o.commission.to_s.to_f64
o.change = percentage(o.order_dolar, o.atual_dolar)
puts day_dolar, o.order_dolar, o.atual_dolar, o.change
end
total = 0.0
puts "==> Orders ==>".colorize(:blue)
orders.each do |o|
profit = o.atual_dolar - o.order_dolar
if o.buy?
total += profit
else
total -= profit
end
out = "# #{as_time(o.closed)} | #{o.exchange.upcase} | #{o.change}% | #{as_dolar(o.order_dolar)} +#{as_dolar(profit)} | $#{o.tax.round(5)} tax"
puts o.buy? ? out.colorize(:green) : out.colorize(:red)
end
line
puts "==> #{as_dolar(total)} profit".colorize(:green)
orders
end
def buy?
self.ordertype == "limit_buy"
end
# show orders processing time
def self.avg_time
Order.all.each do |o|
elapsed = as_time_ago(as_time(o.closed) - as_time(o.timestamp))
o_type = o.ordertype.split("_")[1].upcase
puts "#{o_type} #{o.exchange.upcase} in #{elapsed}".colorize(:yellow)
end
end
# show total asks under Value for Market
def self.total_under(value, market)
market = market.upcase
json = JSON.parse(Broker.get("/public/getorderbook", "market=#{market}&type=sell"))
total = 0.0
json.each do |sell|
rate = sell["rate"].to_s.to_f64
if rate <= value
qtd = sell["quantity"].to_s.to_f64
total += (rate*qtd)
end
end
#"#{market} under #{value} => #{total} btc | #{in_usd}".colorize(:yellow)
total
end
end

+ 140
- 0
app/strategy.cr View File

@ -0,0 +1,140 @@
class Strategy
CHANGE = 2 # %
MINUTES = 10.minutes
def self.arbitrage
begin
fastestes = ["xrp","nano","eos","steem","xlm","bits"]
last_btc_in_usd = Market.last_value_from("usdt-btc")
loop do
sleep 1
cryptopia = get("https://www.cryptopia.co.nz", "/api/GetMarkets/BTC")
bittrex = get("https://bittrex.com", "/api/v1.1/public/getmarketsummaries")
cryptopia["Data"].each do |c|
bittrex["result"].each do |b|
coin = c["Label"].to_s.split("/")[0]
# only fastests
# if fastestes.includes?(coin.downcase)
market = b["MarketName"]
# hist[coin] = 0.0
if b["MarketName"] == "BTC-#{coin}"
last_c = c["LastPrice"].to_s.to_f64
last_b = b["Last"].to_s.to_f64
perc = from_value = to_value = 0
from = to = ""
if last_c < last_b
from = "c"; to = "b"
perc = percentage(last_c, last_b)
from_value = last_c
to_value = last_b
else
from = "b"; to = "c"
from_value = last_b
to_value = last_c
perc = percentage(last_b, last_c)
end
if perc > 8 && perc < 100
count = total = 0
puts "=> #{market} | #{from}->#{to} | #{from_value}->#{to_value} | +#{perc}%".colorize(:yellow)# if hist[coin]>0 || hist[coin]!=perc
# hist[coin] = perc
cryptopia_history(coin)[0..15].each do |h|
price = h["Price"].to_s.to_f64
qtd = h["Total"].to_s.to_f64
ago = as_time_ago(Time.parse(h["Timestamp"].to_s, "%s") - (Time.now + 2.hours))
puts "#{ago} | #{price} | #{percentage(from_value, price)}% | #{percentage(to_value, price)}% | $#{qtd*last_btc_in_usd}".colorize(h["Type"] == "Sell" ? :red : :green)
end
line
# if price > last_c
# count += 1
# qtd = buy["Total"].to_s.to_f64
# puts "+#{percentage(last_c, price)}% | $#{qtd*last_btc_in_usd}"
# total += qtd
# end
# end
# puts total*last_btc_in_usd if total > 0
# line
end
end
end
end
end
#puts "#{bigger} #{market} +#{perc} | #{cryptopia} / #{bittrex}".colorize(:green) if perc > 10 && perc < 200 && bigger=="cryptopia"
rescue e
puts e.colorize(:red)
end
end
def self.cryptopia_history(currency)
json = get("https://www.cryptopia.co.nz", "/api/GetMarketHistory/#{currency.upcase}_BTC/2")
Logger.log(json.to_pretty_json)
json["Data"].to_a.sort_by!{|e| e["Timestamp"].to_s.to_i}.reverse
end
def self.market_exists?(market, markets)
markets.each{|m| return true if m.marketname==market}
false
end
def self.observe(market)
from, currency = market.split("-")
start = Time.now
begin
proto = Tick.from_market(market)
# to avoid inconsistent ticket data, it needs to be confirmed 5x
confirmations = 0
loop do
sleep 2
actual = Tick.from_market(market)
last_change = percentage(proto.last, actual.last)
elapsed = Time.now - start
# pump detector | comprar se subiu 3% em 5 minutos e TODO ha 10% mais buy orders
if last_change >= CHANGE
if elapsed < MINUTES
diff = actual.last - proto.last
unless diff.to_s.includes?("e") # avoid micro changes
confirmations+=1
if confirmations==5
m_summary = Market.fetch(market)
spread = actual.ask - actual.bid
wall = percentage(m_summary.opensellorders, m_summary.openbuyorders)
#Wallet.buy_in_dolar_from_btc(currency, 100)
Logger.log("| #{market} | +#{last_change}% in #{as_time_ago(elapsed)} | +#{diff} | #{m_summary.openbuyorders - m_summary.opensellorders} buy/sell = #{wall}% | #{spread} spread", :sims)
confirmations = 0
break
end
end
else
# reset elapsed
proto = Tick.from_market(market)
end
else
# reset confirmations
confirmations = 0
end
# TODO: sell orders if they drop 2%
end
rescue err
#Logger.log "#{market} | #{err}", error: true
end
end
def self.get(host, url)
json = JSON.parse(HTTP::Client.new(URI.parse(host)).get(url).body)
Logger.log(json.to_pretty_json)
json
end
end

+ 108
- 0
app/summary.cr View File

@ -0,0 +1,108 @@
class Summary
JSON.mapping({
marketname: String,
high: Float64,
low: Float64,
volume: Float64,
last: Float64,
basevolume: Float64,
timestamp: String,
bid: Float64,
ask: Float64,
openbuyorders: Int64,
opensellorders: Int64,
prevday: Float64,
created: String
})
def self.updated
Array(Summary).from_json(Broker.get("public/getmarketsummaries"))
end
def self.monitor!
count = 0
protos = Summary.updated
protos.each{|p| count +=1 if p.marketname.starts_with?("btc") }
btc_usd = select_by_market(protos, "usdt-btc").last
puts "Observing #{count} markets...".colorize(:yellow)
cache = {} of String => Float64
loop do
begin
Summary.updated.each do |atual|
market = atual.marketname
#if market.starts_with?("btc")
proto = select_by_market(protos, market)
cache[market] = 0.0 unless cache.has_key?(market)
perc = percentage(proto.last, atual.last)
if atual.openbuyorders > atual.opensellorders * 2
# if perc > 5 && perc < 50 && cache[market] != perc
cache[market] = perc
ago = as_time_ago(as_time(atual.timestamp) - as_time(proto.timestamp))
puts "#{ago} | #{market} | +#{perc}% | #{proto.last} -> #{atual.last}".colorize(:green)
puts "#{atual.opensellorders} sell | #{atual.openbuyorders} buy".colorize(:blue)
# orderbook = JSON.parse(Broker.get("/public/getorderbook", "market=#{market}&type=both"))
# # analise sell offers
# total = 0.0
# count = 0
# sells = orderbook["sell"].to_a.sort_by{|s| s["rate"].to_s.to_f64}.reverse
# sells.each do |sell|
# rate = sell["rate"].to_s.to_f64
# if rate < atual.last
# qtd = sell["quantity"].to_s.to_f64
# selling = as_dolar(rate*qtd*btc_usd)
# puts "-> #{rate} | #{qtd}x | #{selling}...".colorize(:red)
# total += (rate*qtd)
# count+=1
# end
# end
# under_in_usd = as_dolar(total * btc_usd)
# puts "##{count} offers at #{under_in_usd} < #{atual.last}".colorize(:yellow) if count > 0
# # analise buy offers
# total = 0.0
# count = 0
# buys = orderbook["buy"].to_a.sort_by{|s| s["rate"].to_s.to_f64}.reverse
# buys.each do |buy|
# rate = buy["rate"].to_s.to_f64
# if rate > proto.last
# qtd = buy["quantity"].to_s.to_f64
# buying = as_dolar(rate*qtd*btc_usd)
# puts "+> #{rate} | #{qtd}x | #{buying}...".colorize(:green)
# total += (rate*qtd)
# count+=1
# end
# end
# atual_in_usd = as_dolar(total * btc_usd)
# puts "##{count} offers at #{atual_in_usd} > #{proto.last}".colorize(:yellow) if count > 0
# puts
# pegar history
# TODO : https://bittrex.com/api/v1.1/public/getmarkethistory?market=BTC-DOGE
end
# end
end
rescue err
puts err.colorize(:red)
end
sleep 1
end
end
def self.select_by_market(array, marketname)
selected = uninitialized Summary
array.each do |m|
if m.marketname == marketname
selected = m
break
end
end
selected
end
# singleton class
def self.instance
@@instance ||= new
end
end

+ 14
- 0
app/tick.cr View File

@ -0,0 +1,14 @@
class Tick
JSON.mapping({
bid: Float64,
ask: Float64,
last: Float64
})
def self.from_market(market)
#TODO: WebSockets!
self.from_json(Broker.get("public/getticker", "market=#{market}"))
end
end

+ 123
- 0
app/wallet.cr View File

@ -0,0 +1,123 @@
require "./order"
require "./strategy"
require "./helper"
# TODO: a wallet is array of assets
class Wallet
@@total = 0.0
@@taxes = 0.0
JSON.mapping({
currency: String,
balance: Float64,
available: Float64,
pending: Float64,
cryptoaddress: String | Nil,
# new attrs
as_dolar: {type: Float64, default: 0.to_f64}
})
def self.assets(format = nil)
assets = Array(Wallet).from_json(Broker.get("account/getbalances"))
# set Wallet.total & asset as_dolar & orders taxes
@@total = 0
assets.each do |a|
a.as_dolar = Market.convert_to_dolar(a.currency, a.balance)
@@total += a.as_dolar
end
# TODO: consider deposits
@@taxes = Order.total_taxes
return assets unless format
assets.each do |a|
puts "# #{a.currency.upcase} => #{a.balance} | #{as_dolar(a.as_dolar)}".colorize(:green) if a.balance > 0
end
line
assets
end
def deposits
#TODO: /account/getdeposithistory | account/getwithdrawalhistory?currency=BTC
end
def open_orders
#TODO: ? | get /market/getopenorders
end
def self.simulate_buy(market, quantity)
# store data sims and show as orders for post-analises
Logger.log "#{market},#{quantity}", :sims
puts "=> simulated buy #{market} #{quantity}".colorize(:yellow)
end
def self.sell(market, quantity, rate)
if Market.exists?(market)
puts "Selling #{market}..."
order = Broker.get("market/selllimit", "market=#{market}&quantity=#{quantity}&rate=#{rate}")
if order_id = JSON.parse(order)["uuid"]
order = Order.from_json(Broker.get("account/getorder", "uuid=#{order_id}"))
puts "Sould #{order.quantity} of #{market.upcase} for #{rate} BTC".colorize(:green)
return order
end
else
puts "error: market #{market} does not exist.".colorize(:red)
end
end
def self.buy(market, quantity, rate=nil)
if Market.exists?(market)
return simulate_buy(market, quantity) if Config.params[:simulate]
rate = Market.last_value_from(market) unless rate # use last asked price
Logger.log "=> buy #{market} #{quantity} for #{rate}", :orders
# TODO: better understand rate param on API | rate to place the order
order = Broker.get("market/buylimit", "market=#{market}&quantity=#{quantity}&rate=#{rate}")
# and if not return uuid? :O
if order_id = JSON.parse(order)["uuid"]
order = Order.from_json(Broker.get("account/getorder", "uuid=#{order_id}"))
# TODO: add to orders.json
# orders = JSON.parse(File.read("log/orders.json")).as(Hash)
# timestamp = Time.parse(order.opened.to_s, "%Ft%T.%L").to_s
# orders[timestamp][to]=quantity
# save_json(orders, :orders)
puts "Bought #{order.quantity} of #{market.upcase} for #{rate} BTC".colorize(:green)
return order
else
puts "error: no order made.".colorize(:red)
end
else
puts "error: market #{market} does not exist.".colorize(:red)
end
end
def self.buy_in_dolar(market, value)
if Market.exists?(market)
to_btc = value / Market.last_value_from("usdt-btc")
one_currency_in_btc = Market.last_value_from(market)
currency_in_btc = to_btc / one_currency_in_btc
self.buy(market, currency_in_btc, one_currency_in_btc)
else
puts "error: market #{market.upcase} does not exist.".colorize(:red)
end
end
def self.withdraw
# TODO: flush! send back to main external wallet
# TODO: /account/getwithdrawalhistory
end
def self.flush_all(to = "btc")
Wallet.assets.each do |w|
market = "#{to}-#{w.currency}"
Wallet.sell(market, w.available, Market.last_value_from(market)) if w.currency != to
end
end
def self.total(format = nil)
self.assets if @@total < 1
return @@total unless format
last_btc = Market.last_value_from("usdt-btc")
print "==> #{as_dolar @@total} total | #{@@total/last_btc} btc".colorize(:green)
puts " | #{as_dolar(@@taxes)} taxes".colorize(:yellow)
end
end

+ 8
- 0
deploy.sh View File

@ -0,0 +1,8 @@
echo "=> Compiling"
crystal build renda.cr --release --no-debug
echo "=> Stopping"
ssh crypta "killall -9 renda && rm renda"
echo "=> Deploy"
scp renda crypta:~
echo "=> Starting"
ssh crypta "./renda -arb"

+ 11
- 0
doc/arbitrage.md View File

@ -0,0 +1,11 @@
Note over From,To: Make Arbitrage of Coin"
From->From: BTC to Coin"
Note over From: -0.2% exchange fee
From->To: Transfer Coin"
Note over From: -0.002฿ transfer fee
Note over To: Arrived?
Note over To: Exchange with profit
To->To: Coin" to BTC
Note over To: -0.2% exchange fee
To-->From: BTC
Note over To: -0.002฿ fee

+ 53
- 0
flow.cr View File

@ -0,0 +1,53 @@
require "colorize"
# is sell it in BRL profitable bringing back to EUR? no!
def profit!(invest)
# euro_to_exchange | n26 -> luno
puts "=> #{invest} EUR".colorize(:green)
puts "euro -> btc".colorize(:blue)
last_eur_btc = 8821.225 # @last_eur_btc = https://www.luno.com/ajax/1/price_chart?currency=XBTEUR
btcs = (1/last_eur_btc) * invest
puts "=> #{btcs} BTC".colorize(:green)
puts "# btc_to_market".colorize(:blue) # | luno -> mercadobitcoin
send_fee = (1/last_eur_btc) * 30 # 2018/01/20"),22.403 : https://bitinfocharts.com/comparison/bitcoin-transactionfees.html#3m
puts "=> #{send_fee} BTC send fee".colorize(:red)
sent = btcs - send_fee
puts "=> #{sent} BTC sent".colorize(:green)
puts "# sell_btc_for_BRL".colorize(:blue)
received = sent
last_btc_brl = 36996.0 # https://www.mercadobitcoin.net/api/BTC/ticker/
in_brl = last_btc_brl * received
puts "=> R$#{in_brl} conversion".colorize(:yellow)
execution_fee = in_brl - (in_brl - (in_brl*0.01))
puts "=> R$#{execution_fee} execution fee".colorize(:red)
convertido = in_brl - execution_fee
puts "=> R$#{convertido} convertido".colorize(:yellow)
puts "# withdraw_real".colorize(:blue) # send to ITAU
saque_fee = in_brl - (in_brl - (in_brl*0.02)) - 3 # R$ 2,90 + 1,99%
puts "=> R$#{saque_fee} saque fee".colorize(:red)
sacado = convertido - saque_fee
puts "=> R$#{sacado} sacado".colorize(:yellow)
puts "# EUR_in_BRL".colorize(:blue)
cotacao = 0.25439 # brl-eur
converted = (1/cotacao) * (invest - 30)
puts "=> R$#{converted} EUR/BRL".colorize(:green)
puts "# real_to_eur".colorize(:blue) # transferWise euros back
puts "=> #{converted} EUR convertido".colorize(:yellow)
reenvio_fee = converted - (converted - (converted*0.028))
puts "=> #{reenvio_fee} BRL reenvio fee".colorize(:red)
total = converted - reenvio_fee
puts "=> #{total} EUR reconvertido".colorize(:yellow)
puts "=> #{total-invest} EUR lucro".colorize(:green)
end
profit! 8821

+ 33
- 0
renda.cr View File

@ -0,0 +1,33 @@
# author: rafael polo
# require "kemal"
# require "http/web_socket"
require "option_parser"
require "./app/broker"
require "./app/helper"
require "./app/wallet"
require "./app/summary"
require "./app/arbitrage"
config = Config.params
OptionParser.parse! do |args|
args.banner = "Usage: renda [arguments]"
args.on("-l", "--log", "Show broker HTTP/JSON request/answer logs"){ config[:log] = true }
args.on("-s", "--simulate", "Fake buy"){ config[:simulate] = true }
args.on("-a", "--assets", "Show wallets"){ Wallet.assets(:format) }
args.on("-t", "--total", "Sum wallets values and taxes in dolar"){ Wallet.total(:format) }
args.on("-o", "--orders", "Show previous orders"){ Order.all(:format) }
args.on("-obs", "--observe", "Observe markets"){ Summary.monitor! }
args.on("-arb", "--arbitrage", "Arbitrage"){ Strategy.arbitrage }
#TODO: args.on("-w", "--web", "Enable web mode localhost:5000"){ Web.start }
args.on("-m MARKET", "--market=NAME", "Set market to buy"){ |m| config[:market] = m }
args.on("-v VALUE", "--value=VALUE", "Set value to buy in dolars"){ |v| config[:value] = v }
args.on("-b", "--buy", "Buy {market} and {value} from BTC") {
Wallet.buy_in_dolar(config[:market].to_s.downcase, config[:value].to_s.to_f64)
}
args.on("-h", "--help", "Show this help") { puts args }
end
#test_colors
finish!

+ 22
- 0
shard.lock View File

@ -0,0 +1,22 @@
version: 1.0
shards:
crystagiri:
github: madeindjs/crystagiri
version: 0.3.2
kemal:
github: kemalcr/kemal
version: 0.21.0
kilt:
github: jeromegn/kilt
version: 0.4.0
radix:
github: luislavena/radix
version: 0.3.8
slang:
github: jeromegn/slang
version: 1.7.0

+ 8
- 0
shard.yml View File

@ -0,0 +1,8 @@
name: shards
version: 0.1.0
dependencies:
kemal:
github: kemalcr/kemal
slang:
github: jeromegn/slang

+ 4
- 0
web/jquery-3.2.1.min.js
File diff suppressed because it is too large
View File


+ 161
- 0
web/monitor.html View File

@ -0,0 +1,161 @@
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Bittrex Monit</title>
<script src="./monitor_files/Chart.bundle.js"></script>
<script src="./monitor_files/utils.js"></script>
<script src="./jquery-3.2.1.min.js"></script>
<style type="text/css">
body {background-color: rgb(54,53,49)}
@-webkit-keyframes chartjs-render-animation{from{opacity:0.99}to{opacity:1}}
@keyframes chartjs-render-animation{from{opacity:0.99}to{opacity:1}}
.chartjs-render-monitor{-webkit-animation:chartjs-render-animation 0.001s;animation:chartjs-render-animation 0.001s;}
canvas{ -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none; }
</style>
</head>
<body>
<canvas id="canvas" width="1158" height="579"></canvas>
<br>
<button id="randomizeData">Randomize Data</button>
<button id="addDataset">Add Dataset</button>
<button id="removeDataset">Remove Dataset</button>
<button id="addData">Add Data</button>
<button id="removeData">Remove Data</button>
<script>
var MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var config = {
type: 'line',
data: {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{
label: "My First dataset",
backgroundColor: window.chartColors.red,
borderColor: window.chartColors.red,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
fill: false,
}, {
label: "My Second dataset",
fill: false,
backgroundColor: window.chartColors.blue,
borderColor: window.chartColors.blue,
data: [
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor(),
randomScalingFactor()
],
}]
},
options: {
responsive: true,
title:{
display:true,
text:'Chart.js Line Chart'
},
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'nearest',
intersect: true
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Month'
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Value'
}
}]
}
}
};
window.onload = function() {
var ctx = document.getElementById("canvas").getContext("2d");
window.myLine = new Chart(ctx, config);
};
document.getElementById('randomizeData').addEventListener('click', function() {
config.data.datasets.forEach(function(dataset) {
dataset.data = dataset.data.map(function() {
return randomScalingFactor();
});
});
window.myLine.update();
});
var colorNames = Object.keys(window.chartColors);
document.getElementById('addDataset').addEventListener('click', function() {
var colorName = colorNames[config.data.datasets.length % colorNames.length];
var newColor = window.chartColors[colorName];
var newDataset = {
label: 'Dataset ' + config.data.datasets.length,
backgroundColor: newColor,
borderColor: newColor,
data: [],
fill: false
};
for (var index = 0; index < config.data.labels.length; ++index) {
newDataset.data.push(randomScalingFactor());
}
config.data.datasets.push(newDataset);
window.myLine.update();
});
document.getElementById('addData').addEventListener('click', function() {
if (config.data.datasets.length > 0) {
var month = MONTHS[config.data.labels.length % MONTHS.length];
config.data.labels.push(month);
config.data.datasets.forEach(function(dataset) {
dataset.data.push(randomScalingFactor());
});
window.myLine.update();
}
});
document.getElementById('removeDataset').addEventListener('click', function() {
config.data.datasets.splice(0, 1);
window.myLine.update();
});
document.getElementById('removeData').addEventListener('click', function() {
config.data.labels.splice(-1, 1); // remove the label first
config.data.datasets.forEach(function(dataset, datasetIndex) {
dataset.data.pop();
});
window.myLine.update();
});
</script>
</body></html>

+ 18608
- 0
web/monitor_files/Chart.bundle.js
File diff suppressed because it is too large
View File


+ 135
- 0
web/monitor_files/utils.js View File

@ -0,0 +1,135 @@
'use strict';
window.chartColors = {
red: 'rgb(255, 99, 132)',
orange: 'rgb(255, 159, 64)',
yellow: 'rgb(255, 205, 86)',
green: 'rgb(75, 192, 192)',
blue: 'rgb(54, 162, 235)',
purple: 'rgb(153, 102, 255)',
grey: 'rgb(201, 203, 207)'
};
(function(global) {
var Months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
var COLORS = [
'#4dc9f6',
'#f67019',
'#f53794',
'#537bc4',
'#acc236',
'#166a8f',
'#00a950',
'#58595b',
'#8549ba'
];
var Samples = global.Samples || (global.Samples = {});
var Color = global.Color;
Samples.utils = {
// Adapted from http://indiegamr.com/generate-repeatable-random-numbers-in-js/
srand: function(seed) {
this._seed = seed;
},
rand: function(min, max) {
var seed = this._seed;
min = min === undefined ? 0 : min;
max = max === undefined ? 1 : max;
this._seed = (seed * 9301 + 49297) % 233280;
return min + (this._seed / 233280) * (max - min);
},
numbers: function(config) {
var cfg = config || {};
var min = cfg.min || 0;
var max = cfg.max || 1;
var from = cfg.from || [];
var count = cfg.count || 8;
var decimals = cfg.decimals || 8;
var continuity = cfg.continuity || 1;
var dfactor = Math.pow(10, decimals) || 0;
var data = [];
var i, value;
for (i = 0; i < count; ++i) {
value = (from[i] || 0) + this.rand(min, max);
if (this.rand() <= continuity) {
data.push(Math.round(dfactor * value) / dfactor);
} else {
data.push(null);
}
}
return data;
},
labels: function(config) {
var cfg = config || {};
var min = cfg.min || 0;
var max = cfg.max || 100;
var count = cfg.count || 8;
var step = (max - min) / count;
var decimals = cfg.decimals || 8;
var dfactor = Math.pow(10, decimals) || 0;
var prefix = cfg.prefix || '';
var values = [];
var i;
for (i = min; i < max; i += step) {
values.push(prefix + Math.round(dfactor * i) / dfactor);
}
return values;
},
months: function(config) {
var cfg = config || {};
var count = cfg.count || 12;
var section = cfg.section;
var values = [];
var i, value;
for (i = 0; i < count; ++i) {
value = Months[Math.ceil(i) % 12];
values.push(value.substring(0, section));
}
return values;
},
color: function(index) {
return COLORS[index % COLORS.length];
},
transparentize: function(color, opacity) {
var alpha = opacity === undefined ? 0.5 : 1 - opacity;
return Color(color).alpha(alpha).rgbString();
}
};
// DEPRECATED
window.randomScalingFactor = function() {
return Math.round(Samples.utils.rand(-100, 100));
};
// INITIALIZATION
Samples.utils.srand(Date.now());
}(this));

Loading…
Cancel
Save