Browse Source

base

master
Rafael Polo 10 months ago
commit
2a7bd5d9e4
19 changed files with 933 additions and 0 deletions
  1. +5
    -0
      .gitignore
  2. +30
    -0
      app/broker.cr
  3. +50
    -0
      app/brokers/braziliex.cr
  4. +10
    -0
      app/brokers/exchange.cr
  5. +86
    -0
      app/brokers/kraken.cr
  6. +38
    -0
      app/brokers/mercadobitcoin.cr
  7. +36
    -0
      app/brokers/novadax.cr
  8. +23
    -0
      app/config.cr
  9. +166
    -0
      app/helper.cr
  10. +10
    -0
      app/order.cr
  11. +103
    -0
      app/strategy.cr
  12. +39
    -0
      dev.cr
  13. +61
    -0
      public/js/plotly-latest.min.js
  14. +3
    -0
      public/js/umbrellajs.js
  15. +84
    -0
      public/views/arbs.slang
  16. +92
    -0
      public/views/index.slang
  17. +26
    -0
      shard.lock
  18. +11
    -0
      shard.yml
  19. +60
    -0
      speculari.cr

+ 5
- 0
.gitignore View File

@ -0,0 +1,5 @@
cache/
lib/
.vscode
bin
log/

+ 30
- 0
app/broker.cr View File

@ -0,0 +1,30 @@
require "./brokers/*"
module Broker
def self.all
[Broker::MercadoBitcoin.new, Broker::Kraken.new]
end
def self.update!
update_last_eur_brl
Broker.all.each do |b|
b.coins.each do |coin|
spawn { b.book(coin) }
end
end
end
def self.by_name(name)
return case name
when "novadax"
Broker::NovaDAX.new
when "mercadobitcoin"
Broker::MercadoBitcoin.new
when "braziliex"
Broker::Braziliex.new
when "kraken"
Broker::Kraken.new
end
end
end

+ 50
- 0
app/brokers/braziliex.cr View File

@ -0,0 +1,50 @@
require "./exchange"
module Broker
class Braziliex < Exchange
@coins =
["brl","btc","bch","ltc","eth","dash","zec","dcr","xrp","usdt","tusd","zrx","dai","paxg","sngls","brzx","gmr","omg","gnt","epc","abc","xmr","mxt","smart","nbr","bsv","crw","lcc","onix","etc","iop","btg","eos","prsp","cfty","trx"]
def fees
result = Hash(String, Float64).new
fees_json = get_json("https://braziliex.com", "/api/v1/public/currencies")
coins.each do |c|
result[c] = to_eur fees_json[c]["txWithdrawalFee"].to_s.to_f64
end
result
end
def book(coin)
book = get_json("https://braziliex.com", "/api/v1/public/orderbook/#{coin}_brl")
orders = Array(Order).new
asks = book["asks"]
(0..asks.size-1).each do |i|
order = asks[i]
# type, value, quantity, time
eur_price = to_eur(f(order["price"]))
orders << Order.new("ask", eur_price, f(order["amount"]) * eur_price, Time.utc.to_unix_ms.to_s)
end
bids = book["bids"]
(0..bids.size-1).each do |i|
order = bids[i]
# type, value, quantity, time
eur_price = to_eur(f(order["price"]))
orders << Order.new("bid", eur_price, f(order["amount"]) * eur_price, Time.utc.to_unix_ms.to_s)
end
orders.sort_by!{|o| o.utc}.reverse
end
def trades(coin)
trades_json = get_json("https://braziliex.com", "/api/v1/public/tradehistory/#{coin}_brl")
trades = Array(Order).new
(0..trades_json.size-1).each do |i|
trade = trades_json[i]
type = trade["type"].to_s == "sell" ? "sold" : "bought"
trades << Order.new(type, to_eur(f(trade["price"])), to_eur(f(trade["amount"]) * f(trade["price"])), trade["timestamp"].to_s)
end
trades.sort_by{|t| t.utc}.reverse
end
end
end

+ 10
- 0
app/brokers/exchange.cr View File

@ -0,0 +1,10 @@
abstract class Exchange
property coins = Array(String).new
abstract def book(coin)
abstract def trades(coin)
def to_s
self.class.name.gsub("Broker::", "").downcase
end
end

+ 86
- 0
app/brokers/kraken.cr View File

@ -0,0 +1,86 @@
require "./exchange"
module Broker
class Kraken < Exchange
@coins =
["eth","btc","xrp","ltc","usdt","bch","rep","zec","ada","qtum","xtz","atom","gno","eos","mln","bat","waves","icx","link","sc","omg","paxg","nano","lsk","algo","trx","oxt","kava","comp","storj","knc","repv2","xmr","xlm","dash","dai","usdc", "xdg"]
COIN_CODE = {
"eth": "XETHZEUR",
"btc": "XXBTZEUR",
"xrp": "XXRPZEUR",
"ltc": "XLTCZEUR",
"usdt": "USDTEUR",
"bch": "BCHEUR",
"rep": "XREPZEUR",
"zec": "XZECZEUR",
"ada": "ADAEUR",
"qtum": "QTUMEUR",
"xtz": "XTZEUR",
"atom": "ATOMEUR",
"gno": "GNOEUR",
"eos": "EOSEUR",
"mln": "XMLNZEUR",
"bat": "BATEUR",
"waves": "WAVESEUR",
"icx": "ICXEUR",
"link": "LINKEUR",
"sc": "SCEUR",
"omg": "OMGEUR",
"paxg": "PAXGEUR",
"nano": "NANOEUR",
"lsk": "LSKEUR",
"algo": "ALGOEUR",
"trx": "TRXEUR",
"oxt": "OXTEUR",
"kava": "KAVAEUR",
"comp": "COMPEUR",
"storj": "STORJEUR",
"knc": "KNCEUR",
"repv2": "REPV2EUR",
"xmr": "XXMRZEUR",
"xdg": "XDGEUR",
"xlm": "XXLMZEUR",
"dash": "DASHEUR",
"dai": "DAIEUR",
"usdc": "USDCEUR"
}
def book(coin)
url_coin = coin=="btc" ? "xbt" : coin
book = get_json("https://api.kraken.com", "/0/public/Depth?pair=#{url_coin}eur")["result"][COIN_CODE[coin]]
orders = Array(Order).new
asks = book["asks"]
(0..asks.size-1).each do |i|
order = asks[i]
# type, value, quantity, time
orders << Order.new("ask", f(order[0]), (f(order[1]) * f(order[0])), order[2].to_s)
end
bids = book["bids"]
(0..bids.size-1).each do |i|
order = bids[i]
# type, value, quantity, time
orders << Order.new("bid", f(order[0]), (f(order[1]) * f(order[0])), order[2].to_s)
end
orders.sort_by!{|o| o.utc}.reverse
end
def trades(coin)
url_coin = coin=="btc" ? "xbt" : coin
trades_json = get_json("https://api.kraken.com", "/0/public/Trades?pair=#{url_coin}eur")["result"][COIN_CODE[coin]]
trades = Array(Order).new
(0..trades_json.size-1).each do |i|
trade = trades_json[i]
type = trade[3].to_s == "s" ? "sold" : "bought"
trades << Order.new(type, f(trade[0]), (f(trade[1]) * f(trade[0])), trade[2].to_s)
end
trades.sort_by{|o| o.utc}.reverse
end
end
end

+ 38
- 0
app/brokers/mercadobitcoin.cr View File

@ -0,0 +1,38 @@
require "./exchange"
module Broker
class MercadoBitcoin < Exchange
@coins = ["btc", "ltc", "usdc", "xrp", "bch", "eth"]
def book(coin)
book = get_json("https://www.mercadobitcoin.net", "/api/#{coin}/orderbook/")
orders = Array(Order).new
asks = book["asks"]
(0..asks.size-1).each do |i|
order = asks[i]
# type, value, quantity, time
orders << Order.new("ask", to_eur(f(order[0])), to_eur(f(order[1]) * f(order[0])), Time.utc.to_unix_ms.to_s)
end
bids = book["bids"]
(0..bids.size-1).each do |i|
order = bids[i]
# type, value, quantity, time
orders << Order.new("bid", to_eur(f(order[0])), to_eur(f(order[1]) * f(order[0])), Time.utc.to_unix_ms.to_s)
end
orders.sort_by!{|o| o.utc}.reverse
end
def trades(coin)
trades_json = get_json("https://www.mercadobitcoin.net", "/api/#{coin}/trades/")
trades = Array(Order).new
(0..trades_json.size-1).each do |i|
trade = trades_json[i]
type = trade["type"].to_s == "sell" ? "sold" : "bought"
trades << Order.new(type, to_eur(f(trade["price"])), to_eur(f(trade["amount"]) * f(trade["price"])), trade["date"].to_s)
end
trades.sort_by{|t| t.utc}.reverse
end
end
end

+ 36
- 0
app/brokers/novadax.cr View File

@ -0,0 +1,36 @@
module Broker
class NovaDAX < Exchange
@coins = ["btc", "dgb", "dcr", "eth", "bsv", "xmr", "bch", "xlm", "xrp", "waves", "etc", "trx", "btt", "eos", "ada", "bnb", "link", "dash", "ltc", "xtz", "iota", "omg", "doge", "nuls", "brz"]
def book(coin)
book = get_json("https://api.novadax.com", "/v1/market/depth?symbol=#{coin.upcase}_BRL&limit=500")["data"]
orders = Array(Order).new
asks = book["asks"]
(0..asks.size-1).each do |i|
order = asks[i]
# type, value, quantity, time
orders << Order.new("ask", to_eur(f(order[0])), to_eur(f(order[1]) * f(order[0])), Time.utc.to_unix_ms.to_s)
end
bids = book["bids"]
(0..bids.size-1).each do |i|
order = bids[i]
# type, value, quantity, time
orders << Order.new("bid", to_eur(f(order[0])), to_eur(f(order[1]) * f(order[0])), Time.utc.to_unix_ms.to_s)
end
orders.sort_by!{|o| o.utc}.reverse
end
def trades(coin)
trades_json = get_json("https://api.novadax.com", "/v1/market/trades?symbol=#{coin.upcase}_BRL&limit=500")["data"]
trades = Array(Order).new
(0..trades_json.size-1).each do |i|
trade = trades_json[i]
type = trade["side"].to_s == "SELL" ? "sold" : "bought"
trades << Order.new(type, to_eur(f(trade["price"])), to_eur(f(trade["amount"]) * f(trade["price"])), trade["timestamp"].to_s)
end
trades.sort_by!{|t| t.utc}.reverse
end
end
end

+ 23
- 0
app/config.cr View File

@ -0,0 +1,23 @@
class Config
property params = {} of Symbol => (String | Int32 | Float64 | Bool)
def initialize
# @params[:requests] = 0
@params[:web] = false
@params[:cache] = false
@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

+ 166
- 0
app/helper.cr View File

@ -0,0 +1,166 @@
require "json"
require "http/client"
require "openssl/hmac"
# require "terminal_table"
require "digest/md5"
require "colorize"
require "./config"
module Helper
def finish!
puts line
puts "\t done in #{elapsed_time}".colorize(:blue)
puts line
end
[Signal::QUIT, Signal::ABRT, Signal::INT, Signal::KILL, Signal::TERM].each do |s|
s.trap do
puts "=> Bye!".colorize(:red)
exit
finish!
end
end
def f(el)
el.to_s.to_f64
end
def z(time)
return time < 10 ? "0#{time}" : time
end
def as_date(ms)
unix = ms.to_s.scan(/\d{10}/)
if !unix.empty? # unix time!
date = Time.unix(unix.[0][0].to_i64)
else
date = Time.parse(ms, "%Y-%m-%dT%H:%M:%S.%6N", Time::Location::UTC)
end
end
def ago(ms)
date = ms ? as_date(ms) : Time.utc unless ms
span = (Time.utc - date)
days = span.days > 0 ? "#{span.days}d" : ""
"#{days}#{z span.hours}h#{z span.minutes}m#{z span.seconds}s#{span.milliseconds}"
end
def intro
puts line
puts "\t Renda Basica".colorize(:blue)
puts line
end
def line
"=================================".colorize(:blue)
end
# def log(str)
# pp str if Config.params[:log]
# end
# def get_rate(currency, coin)
# HTTP::Client.get("http://#{currency}.rate.sx/#{coin}").body
# end
def get_json(url, path)
# @requests += 1
md5 = Digest::MD5.hexdigest("#{url}#{path}")
cache = "cache/#{md5}.json"
json = ""
begin
if Config.params[:cache] && File.exists?(cache)
json = File.read(cache)
Logger.log "=> #{url}#{path}".colorize(:yellow)
else
json = HTTP::Client.get("#{url}#{path}").body
File.write(cache, json)
Logger.log "=> #{url}#{path}".colorize(:green)
end
rescue e
Logger.log "error: #{e.message}".colorize(:red)
end
JSON.parse(json)
end
def update_last_eur_brl
json = get_json("https://economia.awesomeapi.com.br", "/eur")[0]
Config.params[:eur_brl] = json["ask"].to_s.to_f64
var_eur_brl = percentage(json["low"].to_s.to_f64, json["high"].to_s.to_f64)
spread = percentage(json["bid"].to_s.to_f64, json["ask"].to_s.to_f64)
puts "EUR:BRL: #{Config.params[:eur_brl]}".colorize(:light_red)
puts "#{var_eur_brl}% diff : #{spread}% spread".colorize(:light_red)
end
def to_eur(f64)
f64 / f(Config.params[:eur_brl])
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|
puts "color #{c1}".colorize(c1)
colors.each do |c2|
puts "fore #{c1} | back #{c2}".colorize.fore(c1).back(c2)
end
end
end
def profit_perct(x, por)
(x.to_f64 * por.to_f64/100).round(2).to_s
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 = 0.0 if pct.infinite? || pct.nan?
pct.round(4)
end
def as_time(time, tmz=false)
parsed_time = Time.parse(time.to_s, "%F %T.%L", Time::Location::UTC)
tmz ? parsed_time + 2.hours : parsed_time # TMZ = 2.hours # athens
end
def elapsed_time
as_time_ago(Time.utc - as_time(Config.params[:start_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 color(float : Float64)
float > 0 ? float.to_s.colorize(:green) : float.to_s.colorize(:red)
end
def now
Time.utc.to_s("%F %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
end
include Helper
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

+ 10
- 0
app/order.cr View File

@ -0,0 +1,10 @@
include Helper
class Order
include JSON::Serializable
getter type, value, price, utc
def initialize(@type : String, @value : Float64, @price : Float64, @utc : String)
@utc = as_date(@utc).to_s.gsub(" UTC", "")
end
end

+ 103
- 0
app/strategy.cr View File

@ -0,0 +1,103 @@
class Op
include JSON::Serializable
property coin, perc
def initialize(@coin : String, @perc : Float64)
end
end
class Strategy
def self.offers(b1, b2)
result = [] of Op
t_s = 0.0
coins = (b1.coins & b2.coins) - ["btc"]
coins.each do |coin|
b1_book = b1.book(coin)
b2_book = b2.book(coin)
begin
price_buy = b1_book.sort_by{|o| o.value}.select{|o| o.type=="ask"}.first.value
price_sell = b2_book.sort_by{|o| o.value}.select{|o| o.type=="bid"}.last.value
rescue
# when any is empty
price_buy = 0
price_sell = 0
end
perc = percentage(price_buy, price_sell).round(3)
# if perc >= -1
result << Op.new(coin.to_s.upcase, perc) if price_buy > 0
# table = TerminalTable.new
# table.headings = ["b1 buy < b2 sell", "total", "%", "profit"]
# sum = 0
# perc_t = 0
# count = 0
# # vendem em b1 por menos que compram em b2
# b1_book.sort_by!{|o| o.value}.select{|o|o.type=="ask" && o.value <= price_sell}.each do |o|
# perc = percentage(o.value, price_sell).to_f64 # taxas 0.16+0.16+0.5+0.5+send
# if perc >= PERC
# count+=1
# table << [o.value.round(4), "€ #{o.price.round(2)}", perc.round(2), "€ #{profit_perct(o.price, perc)}"]
# sum += o.price
# perc_t += perc
# end
# end
# avg_perc = perc_t/count
# profit = profit_perct(sum, avg_perc)
# t_s += profit.to_f64
# #t(ypetions?
# if count>0
# begin
# # fees = get_json("https://braziliex.com", "/api/v1/public/currencies")[coin]
# # result << "MinWithdrawal #{fees["MinWithdrawal"]}".colorize(:light_red)
# # result << "txWithdrawalFee #{fees["txWithdrawalFee"]} = €#{to_eur(fees["txWithdrawalFee"].to_s.to_f64)}".colorize(:light_red)
# # result << "minDeposit #{fees["minDeposit"]} = €#{to_eur(fees["txWithdrawalFee"].to_s.to_f64)}".colorize(:light_red)
# # result << "minAmountTradeUSDT #{fees["minAmountTradeUSDT"]}".colorize(:light_red)
# end
# result << table.render.colorize(:blue)
# # past buys
# trades = TerminalTable.new
# trades.headings = ["b1 bought", "total", "%", "ago"]
# t = b1.trades(coin).select{|o| o.type=="bought"}[0..5].each do |t|
# trades << [t.value.round(4), "€#{t.price.round(2)}", percentage(t.value, price_sell).to_f64, "#{ago(t.utc)}"]
# end
# result << trades.render.colorize(:yellow)
# # next buys
# table = TerminalTable.new
# table.headings = ["b2 buy > b1 ask", "total", "%", "profit"]
# # orders b2 buying > b1 selling
# b2_book.sort_by!{|o| o.value}.select{|o|o.type=="bid" && o.value >= price_buy}.reverse[0..5].each do |o|
# perc = percentage(price_buy, o.value).to_f64 # taxas 0.16+0.16+0.5+0.5+send
# table << [o.value.round(4), "€ #{o.price.round(2)}", perc.round(2), "€ #{profit_perct(o.price, perc)}"]
# end
# result << table.render.colorize(:green)
# # past sells
# trades = TerminalTable.new
# trades.headings = ["b2 sold", "total", "%", "ago"]
# t = b2.trades(coin).select{|o| o.type=="sold"}[0..5].each do |t|
# trades << [t.value.round(4), "€#{t.price.round(2)}", percentage(price_buy, t.value).to_f64, "#{ago(t.utc)}"]
# end
# result << trades.render.colorize(:red)
# result << "=> invest €#{sum.round(2)} to get +#{avg_perc.round(2)}% = #{profit}".colorize(:green)
# result << line
# end
# if t_s > 0
# result << "=> total €#{t_s.round(2)}".colorize(:yellow)
# result << line
# end
# end
end
result.sort_by!{|v| v.perc}.reverse
end
end

+ 39
- 0
dev.cr View File

@ -0,0 +1,39 @@
# abritrage!
require "./app/*"
# todo: could became a DSL!
# buy 100 BRL in XRP, if hasn't
mbtc = Broker::MercadoBitcoin.new
@invested = @xrp_last_sell_price * 100
unless mbtc.wallet.address["xrp"]["value"] >= @invested
mbtc.wallet.order(100, "BRL", "XRP", @invested)
end
# send xrp to Kraken
krk = Broker::Kraken.new
mbtc.withdraw("xrp", @invested, krk.wallet.address["xrp"])
# wait xrp
while krk.wallet.address["xrp"]["value"] < @received
wait
end
@received = @invested - @taxas
# check same transferCode ?
# xrp->eur
krk.wallet.order(@received, "xrp", "eur") # wait?
# eur->bch
krk.wallet.order(@xrp_to_euros, "eur", "bch")
# send bch to MercadoBitcoin
krk.withdraw("bch", @bch_last_sell*, mbtc.wallet.address["bch"])
# wait BCH
while krk.wallet.address["xrp"]["value"] < @xrp_last_sell_price * 100 - @taxas
wait
end
# sell per BRL
mbtc.wallet.order(100, "BCH", "BRL", @xrp_last_sell_price+1% + @taxas)

+ 61
- 0
public/js/plotly-latest.min.js
File diff suppressed because it is too large
View File


+ 3
- 0
public/js/umbrellajs.js
File diff suppressed because it is too large
View File


+ 84
- 0
public/views/arbs.slang View File

@ -0,0 +1,84 @@
head
title= "Speculari | €/R$#{Config.params[:eur_brl]}"
meta charset="UTF-8"
script src="js/plotly-latest.min.js"
script src="js/umbrellajs.js"
css:
* {margin: 0px; padding: 0px}
body { font-family: "Ubuntu"; font-size: 13px; padding: 10px; text-align: center; background-image: linear-gradient(to bottom right, gray, lightgray);}
#arb { width: 49%; padding: 5px; display: inline-block }
.graph { border: 1px solid black; }
.stats{ text-align: right; padding: 5px; position: relative; top: -25px;}
javascript:
u(document).on("DOMContentLoaded", function(){
u(".graph").each(function(i){
createGraph(u(i).attr("coin"), u(i).attr("b1"), u(i).attr("b2"))
})
function createGraph(coin, b1, b2){
var id = "graph-"+b1+"-"+b2+"-"+coin;
var layout = {
title: coin.toUpperCase(), // + " | " + b1 + " ⟷ " + b2,
legend: {
font: {
family: 'Ubuntu',
size: 13,
color: '#000'
},
borderwidth: 1
}
};
Plotly.newPlot(id, [], layout, {responsive: true});
addTrace(id, b1, coin, "bought");
addTrace(id, b1, coin, "sold");
addTrace(id, b2, coin, "bought");
addTrace(id, b2, coin, "sold");
myPlot = document.getElementById(id);
myPlot.on('plotly_hover', function (eventdata){
points = eventdata.points.map(function(i){return i.y}).sort();
var x = points[0]
var y = points[points.length-1]
var perc = x < y ? ((y - x) / x) * 100.0 : - ((x - y) / y) * 100.0;
u("#stats-"+b1+"-"+b2+"-"+coin).text(x + " -> "+ y + " = " + perc.toFixed(3) + "%");
});
};
function addTrace(id, broker, coin, type){
var horasAntes = new Date(new Date() - 1000*60*60*2);
fetch("/"+broker+"/"+coin+"/trades.json").then(function(response) {
return response.json().then(function(json) {
var trades = json.filter(function(e, i) { return e.type==type && new Date(e.utc) >= horasAntes });
var prices = trades.map(function(e, i){ return e.value.toFixed(3) });
var times = trades.map(function(e, i){ return e.utc });
var trace = {
x: times,
y: prices,
name: broker + " / " + type + " (" + trades.length + ")",
line: {shape: 'spline'},
hovertemplate: '%{y:€.2f}',
type: 'scatter',
mode: 'lines+markers',
marker: { size: 4 }
};
Plotly.addTraces(id, trace);
});
});
}
});
body
#quotes
- perms = [Broker::MercadoBitcoin.new, Broker::Kraken.new].combinations(2)
- perms.each do |duo|
/ h3= "#{duo[0].broker_name} ⟷ #{duo[1].broker_name}"
- coins = (duo[0].coins & duo[1].coins) - ["btc", ]
- coins.each do |coin|
#arb
.graph b1=duo[0].to_s b2=duo[1].to_s coin=coin id="graph-#{duo[0].to_s}-#{duo[1].to_s}-#{coin}"
p.stats b1=duo[0].to_s b2=duo[1].to_s coin=coin id="stats-#{duo[0].to_s}-#{duo[1].to_s}-#{coin}" %

+ 92
- 0
public/views/index.slang View File

@ -0,0 +1,92 @@
head
title= "Speculari | 1€:R$#{Config.params[:eur_brl]}"
meta charset="UTF-8"
script src="js/plotly-latest.min.js"
script src="js/umbrellajs.js"
css:
* {margin: 0px; padding: 0px}
body {font-family: "Ubuntu"; font-size: 12px; padding: 10px; background-image: linear-gradient(to bottom right, gray, lightgray);}
h3 { text-align: center; font-size: 13px; padding: 3px}
.red {color: red}
.green { color: green}
.faixa {display: flex}
.quote{ text-align: center; padding: 5px; }
.quote:hover { background-color: white; cursor: pointer}
.seta {font-size: 19px; line-height: 35px;}
.caixa { border: 1px solid black; display: inline-block; padding: 5px; margin: 5px; background-color: #f1f0f0;}
#quotes{ text-align: center; padding-bottom: 20px }
#graph { width:100%; margin: 0px auto }
.main-svg{ background-color: rgb(255 255 255 / 0%) important! }
#stats{text-align: right; padding-bottom: 5px}
javascript:
u(document).on("DOMContentLoaded", function(){
u(".quote").on("click", function(){
// make Graph
var coin = u(this).children('p:first-child').text().toLowerCase();
var b1 = u(this).parent().attr("broker1").toLowerCase();
var b2 = u(this).parent().attr("broker2").toLowerCase();
Plotly.newPlot('graph', [], {}, {responsive: true});
addTrace(b1, coin, "bought");
addTrace(b1, coin, "sold");
addTrace(b2, coin, "bought");
addTrace(b2, coin, "sold");
myPlot = document.getElementById('graph');
myPlot.on('plotly_hover', function (eventdata){
points = eventdata.points.map(function(i){return i.y}).sort();
var x = points[0]
var y = points[points.length-1]
var perc = x < y ? ((y - x) / x) * 100.0 : - ((x - y) / y) * 100.0;
u("#stats").text(x.toFixed(3) + " -> "+ y.toFixed(3) + " = " + perc.toFixed(3) + "%")
});
});
function addTrace(broker, coin, type){
var antes24h = new Date(new Date() - 1000*60*60*24);
fetch("/"+broker+"/"+coin+"/trades.json").then(function(response) {
return response.json().then(function(json) {
var trades = json.filter(function(e, i) { return e.type==type && new Date(e.utc) > antes24h });
var prices = trades.map(function(e, i){ return e.value });
var times = trades.map(function(e, i){ return e.utc });
var trace = {
x: times,
y: prices,
name: broker + " / " + coin.toUpperCase() + " / " + type + " / " + trades.length ,
line: {shape: 'spline'},
hovertemplate: '%{y:$.2f}',
type: 'scatter'
};
Plotly.addTraces("graph", trace);
});
});
}
});
body
#quotes
- perms = [Broker::Braziliex.new, Broker::MercadoBitcoin.new, Broker::Kraken.new, Broker::NovaDAX.new].combinations(2)
- perms.each do |duo|
div.caixa
h3= "#{duo[0]} ⟷ #{duo[1]}"
.faixa broker1="#{duo[0]}" broker2="#{duo[1]}"
p.seta= "↠"
- Strategy.offers(duo[0], duo[1]).each do |op|
- color = op.perc < 0 ? "red" : "green"
span.quote class=color
p= op.coin
p= "#{op.perc}%"
.faixa broker1="#{duo[0]}" broker2="#{duo[1]}"
p.seta= "↞"
- Strategy.offers(duo[1], duo[0]).each do |op|
- color = op.perc < 0 ? "red" : "green"
span.quote class=color
p= op.coin
p= "#{op.perc}%"
#stats
#graph

+ 26
- 0
shard.lock View File

@ -0,0 +1,26 @@
version: 2.0
shards:
exception_page:
git: https://github.com/crystal-loot/exception_page.git
version: 0.1.4
kemal:
git: https://github.com/kemalcr/kemal.git
version: 0.26.1+git.commit.a819d4792bbc63d993d6dd2b15f9af5efb0c2d8c
kilt:
git: https://github.com/jeromegn/kilt.git
version: 0.4.0
radix:
git: https://github.com/luislavena/radix.git
version: 0.3.9
slang:
git: https://github.com/jeromegn/slang.git
version: 1.7.1
terminal_table:
git: https://github.com/benoist/terminal_table.cr.git
version: 0.1.0+git.commit.a14e244219a6a7b804b3491bafcff73f4b74855e

+ 11
- 0
shard.yml View File

@ -0,0 +1,11 @@
name: shards
version: 0.1.0
dependencies:
terminal_table:
github: benoist/terminal_table.cr
kemal:
github: kemalcr/kemal
branch: master
slang:
github: jeromegn/slang

+ 60
- 0
speculari.cr View File

@ -0,0 +1,60 @@
# author: rafael polo
require "kemal"
require "kilt"
require "kilt/slang"
require "option_parser"
require "./app/*"
get "/" do |env|
render "public/views/index.slang"
end
get "/arbs" do |env|
# arbs = Strategy.get_arbitrages
render "public/views/arbs.slang"
end
get "/:broker/:coin/orders.json" do |env|
env.response.content_type = "application/json"
coin = env.params.url["coin"]
broker = Broker.by_name(env.params.url["broker"])
broker.book(coin).to_json if broker
end
get "/:broker/:coin/trades.json" do |env|
env.response.content_type = "application/json"
coin = env.params.url["coin"]
broker = Broker.by_name(env.params.url["broker"])
broker.trades(coin).to_json if broker
end
config = Config.params
OptionParser.parse do |args|
args.banner = "Usage: rico [arguments]"
args.on("-l", "--log", "Show broker HTTP/JSON request/answer logs"){ config[:log] = true }
args.on("-c", "--cache", "Use last cached requests"){ config[:cache] = 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("-w", "--web", "Enable web mode localhost:5000"){ config[:web] = true }
# 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; exit }
end
puts intro
if config[:cache]
update_last_eur_brl
else
spawn { puts "Updating..."; Broker.update! }
end
Kemal.run if config[:web]
finish!

Loading…
Cancel
Save