Browse Source

update README

master
Rafael Polo 2 months ago
parent
commit
d0d5f09092
15 changed files with 244 additions and 24 deletions
  1. +21
    -0
      README.md
  2. +2
    -11
      app/broker.cr
  3. +40
    -0
      app/brokers/bitrex.cr
  4. +2
    -1
      app/brokers/braziliex.cr
  5. +8
    -0
      app/brokers/exchange.cr
  6. +4
    -2
      app/brokers/kraken.cr
  7. +2
    -1
      app/brokers/mercadobitcoin.cr
  8. +1
    -0
      app/brokers/novadax.cr
  9. +5
    -1
      app/helper.cr
  10. +4
    -0
      app/order.cr
  11. BIN
      doc/sample.png
  12. BIN
      doc/speculari.sample.pdf
  13. +6
    -3
      public/views/arbs.slang
  14. +113
    -0
      public/views/coin.slang
  15. +36
    -5
      speculari.cr

+ 21
- 0
README.md View File

@ -0,0 +1,21 @@
## Speculari
Esse sistema propõe uma base de análises gráficas para comparação entre preços de diferentes brokers, e eventual descoberta de oportunidades.
```
- converte BRL:USD
- baixa a cada segundos todos os tickers dos brokers
- baixa histórico de compras e vendas dados X segundos
- no browser, exige porcentagens de diferenças
```
---
#### Exemplo
Diferença de 2h entre Kraken e MercadoBitcoin para XRP,
![arbitrage](doc/sample.png)
Conferir [PDF](doc/speculari.sample.pdf) exportado direto do browser.

+ 2
- 11
app/broker.cr View File

@ -3,7 +3,7 @@ require "./brokers/*"
module Broker
def self.all
[Broker::MercadoBitcoin.new, Broker::Kraken.new]
[Broker::MercadoBitcoin.new, Broker::Kraken.new, Broker::Bitrex.new, Broker::Braziliex.new, Broker::NovaDAX.new]
end
def self.update!
@ -17,15 +17,6 @@ module Broker
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
self.all.each{|b| return b if b.to_s == name}
end
end

+ 40
- 0
app/brokers/bitrex.cr View File

@ -0,0 +1,40 @@
require "./exchange"
module Broker
class Bitrex < Exchange
@coins = ["aave", "ada", "bal", "band", "bch", "bsv", "btc", "cro", "dot", "eth", "knc", "renbtc", "ren", "trx", "uma", "usd", "usdt", "xlm", "xrp", "yfl"]
def book(coin)
return [] of Order unless @coins.index(coin)
book = get_json("https://api.bittrex.com", "/v3/markets/#{coin}-EUR/orderbook")
orders = Array(Order).new
asks = book["ask"]
(0..asks.size-1).each do |i|
order = asks[i]
# type, value, quantity, time
orders << Order.new("ask", (f(order["rate"])), (f(order["quantity"]) * f(order["rate"])), Time.utc.to_unix_ms.to_s)
end
bids = book["bid"]
(0..bids.size-1).each do |i|
order = bids[i]
# type, value, quantity, time
orders << Order.new("bid", (f(order["rate"])), (f(order["quantity"]) * f(order["rate"])), Time.utc.to_unix_ms.to_s)
end
orders.sort_by!{|o| o.utc}.reverse
end
def trades(coin)
return [] of Order unless @coins.index(coin)
book = get_json("https://api.bittrex.com", "/v3/markets/#{coin}-EUR/trades")
trades = Array(Order).new
(0..book.size-1).each do |i|
trade = book[i]
# type, value, quantity, time
type = trade["takerSide"] == "SELL" ? "sold" : "bought"
trades << Order.new(type, (f(trade["rate"])), (f(trade["rate"]) * f(trade["quantity"])), trade["executedAt"].to_s)
end
trades.sort_by{|t| t.utc}.reverse
end
end
end

+ 2
- 1
app/brokers/braziliex.cr View File

@ -3,7 +3,7 @@ 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"]
["brl","btc","bch","ltc","eth","dash","zec","dcr","xrp","usdt","tusd","zrx","dai","paxg","sngls","brzx","gmr","omg","gnt","epc","abc","mxt","smart","nbr","bsv","crw","lcc","onix","etc","iop","btg","eos","prsp","cfty","trx"]
def fees
result = Hash(String, Float64).new
@ -15,6 +15,7 @@ module Broker
end
def book(coin)
return [] of Order unless @coins.index(coin)
book = get_json("https://braziliex.com", "/api/v1/public/orderbook/#{coin}_brl")
orders = Array(Order).new
asks = book["asks"]


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

@ -3,6 +3,14 @@ abstract class Exchange
abstract def book(coin)
abstract def trades(coin)
def bids(coin)
book(coin).select{|o| o.type == "bid"}.sort_by!{|o| o.value}.reverse#[0..10]
end
def asks(coin)
book(coin).select{|o| o.type == "ask"}.sort_by!{|o| o.value}#[0..10]
end
def to_s
self.class.name.gsub("Broker::", "").downcase


+ 4
- 2
app/brokers/kraken.cr View File

@ -3,7 +3,7 @@ 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"]
["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","chz"]
COIN_CODE = {
"eth": "XETHZEUR",
@ -47,6 +47,8 @@ module Broker
}
def book(coin)
return [] of Order unless @coins.index(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]]
@ -83,4 +85,4 @@ module Broker
trades.sort_by{|o| o.utc}.reverse
end
end
end
end

+ 2
- 1
app/brokers/mercadobitcoin.cr View File

@ -3,9 +3,10 @@ require "./exchange"
module Broker
class MercadoBitcoin < Exchange
@coins = ["btc", "ltc", "usdc", "xrp", "bch", "eth"]
@coins = ["btc", "ltc", "usdc", "xrp", "bch", "eth", "paxg", "link", "chz"]
def book(coin)
return [] of Order unless @coins.index(coin)
book = get_json("https://www.mercadobitcoin.net", "/api/#{coin}/orderbook/")
orders = Array(Order).new
asks = book["asks"]


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

@ -4,6 +4,7 @@ module Broker
@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)
return [] of Order unless @coins.index(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"]


+ 5
- 1
app/helper.cr View File

@ -36,7 +36,11 @@ module Helper
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)
if ms[19]=="."
date = Time.parse(ms, "%Y-%m-%dT%H:%M:%S.%6N", Time::Location::UTC)
else
date = Time.parse(ms, "%Y-%m-%dT%H:%M:%S", Time::Location::UTC)
end
end
end


+ 4
- 0
app/order.cr View File

@ -7,4 +7,8 @@ class Order
def initialize(@type : String, @value : Float64, @price : Float64, @utc : String)
@utc = as_date(@utc).to_s.gsub(" UTC", "")
end
def to_s
"#{self.value.round(3)} - #{self.price.round(3)}"
end
end

BIN
doc/sample.png View File

Before After
Width: 632  |  Height: 275  |  Size: 42 KiB

BIN
doc/speculari.sample.pdf View File


+ 6
- 3
public/views/arbs.slang View File

@ -50,12 +50,14 @@ javascript:
};
function addTrace(id, broker, coin, type){
var horasAntes = new Date(new Date() - 1000*60*60*2);
var horasAntes = new Date(new Date() - 1000*60*60*3);
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 values = trades.map(function(e, i){ return e.price.toFixed(2) });
var avg = prices.reduce((a, b) => (parseFloat(a) + parseFloat(b))) / prices.length;
var symb = type == "bought" ? " < " : " > ";
@ -64,7 +66,8 @@ javascript:
y: prices,
name: broker + symb + trades.length,
line: {shape: 'spline'},
hovertemplate: '%{y:€.2f}',
hovertemplate: 'R$%{y:€.2f}<br>Total: %{text}',
text: values,
type: 'scatter',
mode: 'lines+markers',
marker: { size: 4 }
@ -78,7 +81,7 @@ javascript:
body
#quotes
- perms = [Broker::MercadoBitcoin.new, Broker::Kraken.new].combinations(2)
- perms = [Broker::Braziliex.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)


+ 113
- 0
public/views/coin.slang View File

@ -0,0 +1,113 @@
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: 65%; padding: 5px; display: inline-block }
.graph { border: 1px solid black; }
.stats{ text-align: right; padding: 5px; position: relative; top: -25px;}
.red{color: red}
.green{color: green}
.lado{display: inline-block; padding: 3px;}
.center{text-align: center}
.book{vertical-align: top; font-weight: 500; font-size: 13px; padding-top: 30px; background-color: white; display: inline-block; border: 1px solid black; padding: 20px; margin: 5px;}
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: {
x: 0.1,
y: 1.15,
orientation: "h",
font: {
family: 'Ubuntu',
size: 11,
color: '#000'
}
}
};
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*3);
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 values = trades.map(function(e, i){ return e.price.toFixed(2) });
var avg = prices.reduce((a, b) => (parseFloat(a) + parseFloat(b))) / prices.length;
var symb = type == "bought" ? " < " : " > ";
var trace = {
x: times,
y: prices,
name: broker + symb + trades.length,
text: values,
line: {shape: 'spline'},
hovertemplate: '%{y:€.2f}',
type: 'scatter',
hovertemplate: 'R$%{y:€.2f}<br>Total: %{text}',
mode: 'lines+markers',
marker: { size: 4 }
};
Plotly.addTraces(id, trace);
});
});
}
});
body
- perms = Broker.all.combinations(2)
- perms.each do |duo|
#quotes
.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}" %
.book
.lado
p= duo[0].to_s
br
.green.center
- duo[0].bids(coin).each do |o|
p= o
.red.center
- duo[0].asks(coin).each do |o|
p= o
.lado
p= duo[1].to_s
br
.red.center
- duo[1].asks(coin).each do |o|
p= o
.green.center
- duo[1].bids(coin).each do |o|
p= o

+ 36
- 5
speculari.cr View File

@ -7,14 +7,16 @@ 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/index.slang"
render "public/views/arbs.slang"
end
get "/coin/:coin" do |env|
coin = env.params.url["coin"]
render "public/views/coin.slang"
end
get "/:broker/:coin/orders.json" do |env|
env.response.content_type = "application/json"
coin = env.params.url["coin"]
@ -50,7 +52,36 @@ end
update_last_eur_brl
spawn { Broker.update! } unless config[:cache]
Kemal.run if config[:web]
# b = Broker::Braziliex.new
# k = Broker::Kraken.new
# (b.coins & k.coins).each do |coin|
# puts coin
# bids = b.bids(coin)
# asks = b.asks(coin)
# # puts b.to_s
# bid1 = bids.first
# ask1 = asks.first
# # puts "last bid: #{bid1.to_s}"
# # puts "last ask: #{ask1.to_s}"
# bids = k.bids(coin)
# asks = k.asks(coin)
# # puts k.to_s
# bid2 = bids.first
# ask2 = asks.first
# # puts "last bid: #{bid2.to_s}"
# # puts "last ask: #{ask2.to_s}"
# p = percentage(ask2.value, bid1.value)
# puts "<- #{p}% | #{ask2.value} : #{bid1.value}"
# p = percentage(ask1.value, bid2.value)
# puts "-> #{p}% | #{ask1.value} : #{bid2.value}"
# puts
# end
finish!

Loading…
Cancel
Save