App-cryp-arbit
view release on metacpan or search on metacpan
lib/App/cryp/arbit.pm view on Meta::CPAN
unless (exists $account_exchanges{$xchg}) {
return [400, "Unknown exchange '$xchg'"]
unless $xcat->by_safename($xchg);
}
$account_exchanges{$xchg}{$acc} = 1;
}
return [400, "Please specify accounts on at least two ".
"cryptoexchanges, you only specify account(s) on " .
join(", ", keys %account_exchanges)]
unless keys(%account_exchanges) >= 2;
$r->{_stash}{account_exchanges} = \%account_exchanges;
}
}
my $dbh;
CONNECT:
{
last if $opts->{skip_connect};
require DBIx::Connect::MySQL;
log_trace "Connecting to database ...";
$r->{_stash}{dbh} = DBIx::Connect::MySQL->connect(
"dbi:mysql:database=$r->{args}{db_name}",
$r->{args}{db_username},
$r->{args}{db_password},
{RaiseError => 1},
);
$dbh = $r->{_stash}{dbh};
}
SETUP_SCHEMA:
{
last if $opts->{skip_connect};
require SQL::Schema::Versioned;
my $res = SQL::Schema::Versioned::create_or_update_db_schema(
dbh => $r->{_stash}{dbh}, spec => $db_schema_spec,
);
die "Cannot run the application: cannot create/upgrade database schema: $res->[1]"
unless $res->[0] == 200;
}
[200];
}
sub _init_arbit {
my $r = shift;
DETERMINE_QUOTE_CURRENCIES:
{
my @quotecurs;
my %fiatquotecurs; # key=fiat, value=1
my @quotecurs_arg = @{ $r->{args}{quote_currencies} // [] };
my %quotecur_exchanges; # key=(cryptocurrency code or ':fiat'), value={exchange1=>1, ...}
# list pairs on all exchanges
for my $e (sort keys %{ $r->{_stash}{account_exchanges} }) {
my $pair_recs = _get_exchange_pairs($r, $e);
for my $pair_rec (@$pair_recs) {
my $pair = $pair_rec->{name};
my ($basecur, $quotecur) = split m!/!, $pair;
# consider all fiat currencies as a single ":fiat" because we
# assume fiat currencies can be converted from one to the aother
# at a stable rate.
my $key;
if (_is_fiat($quotecur)) {
$key = ':fiat';
$fiatquotecurs{$quotecur} = 1;
} else {
$key = $quotecur;
}
$quotecur_exchanges{$key}{$e} = 1;
}
}
# only consider quote currencies that are traded in >1 exchanges, for
# arbitrage possibility.
my @possible_quotecurs = grep { keys(%{$quotecur_exchanges{$_}}) > 1 }
sort keys %quotecur_exchanges;
# convert back fiat currencies back to their original
if (grep {':fiat'} @possible_quotecurs) {
@possible_quotecurs = grep {$_ ne ':fiat'} @possible_quotecurs;
push @possible_quotecurs, sort keys %fiatquotecurs;
}
if (@quotecurs_arg) {
my @impossible_quotecurs;
for my $c (@quotecurs_arg) {
if (grep { $c eq $_ } @possible_quotecurs) {
push @quotecurs, $c;
} else {
push @impossible_quotecurs, $c;
}
}
if (@impossible_quotecurs) {
log_warn "The following quote currencies are not traded on at least two exchanges: %s, excluding these quote currencies",
\@impossible_quotecurs;
}
} else {
log_warn "Will be arbitraging using these quote currencies: %s",
\@possible_quotecurs;
@quotecurs = @possible_quotecurs;
}
$r->{_stash}{quote_currencies} = \@quotecurs;
} # DETERMINE_QUOTE_CURRENCIES
# determine possible base currencies to arbitrage against
DETERMINE_BASE_CURRENCIES:
{
my @basecurs;
my @basecurs_arg = @{ $r->{args}{base_currencies} // [] };
my %basecur_exchanges; # key=currency code, value={exchange1=>1, ...}
# list pairs on all exchanges
for my $e (sort keys %{ $r->{_stash}{account_exchanges} }) {
my $pair_recs = _get_exchange_pairs($r, $e);
for my $pair_rec (@$pair_recs) {
my $pair = $pair_rec->{name};
my ($basecur, $quotecur) = split m!/!, $pair;
next unless grep { $_ eq $quotecur } @{ $r->{_stash}{quote_currencies} };
$basecur_exchanges{$basecur}{$e} = 1;
}
}
# only consider base currencies that are traded in >1 exchanges, for
# arbitrage possibility
my @possible_basecurs = grep { keys(%{$basecur_exchanges{$_}}) > 1 }
keys %basecur_exchanges;
if (@basecurs_arg) {
my @impossible_basecurs;
for my $c (@basecurs_arg) {
if (grep { $c eq $_ } @possible_basecurs) {
push @basecurs, $c;
} else {
push @impossible_basecurs, $c;
}
}
if (@impossible_basecurs) {
log_warn "The following base currencies are not traded on at least two exchanges: %s, excluding these base currencies",
\@impossible_basecurs;
}
} else {
log_warn "Will be arbitraging these base currencies that are traded on at least two exchanges: %s",
\@possible_basecurs;
@basecurs = @possible_basecurs;
}
return [412, "No base currencies possible for arbitraging"] unless @basecurs;
$r->{_stash}{base_currencies} = \@basecurs;
} # DETERMINE_BASE_CURRENCIES
DETERMINE_TRADING_FEES:
{
# XXX hardcoded for now
$r->{_stash}{trading_fees} = {
':default' => {':default'=>0.3},
'indodax' => {':default'=>0.3},
'coinbase-pro' => {BTC=>0.25, ':default'=>0.3},
};
}
[200];
}
sub _format_order_pairs_response {
my $order_pairs = shift;
# format for table display
my @res;
for my $op (@$order_pairs) {
my $size = $op->{base_size};
my ($base_currency, $buy_currency) = $op->{buy}{pair} =~ m!(.+)/(.+)!;
my ($sell_currency) = $op->{sell}{pair} =~ m!/(.+)!;
my $profit_currency = _is_fiat($buy_currency) ? 'USD' : $buy_currency;
my $rec = {
size => $size,
currency => $base_currency,
lib/App/cryp/arbit.pm view on Meta::CPAN
my $r = $args{-cmdline_r};
my $res;
$res = _init($r); return $res unless $res->[0] == 200;
$res = _init_arbit($r); return $res unless $res->[0] == 200;
my $dbh = $r->{_stash}{dbh};
# this section is borrowed from App::cryp::arbit::Strategy::merge_order_book
my %exchanges_for; # key="base currency"/"quote cryptocurrency or ':fiat'", value => [exchange, ...]
my %fiat_for; # key=exchange safename, val=[fiat currency, ...]
my %pairs_for; # key=exchange safename, val=[pair, ...]
DETERMINE_SETS:
for my $exchange (sort keys %{ $r->{_stash}{exchange_clients} }) {
my $pair_recs = $r->{_stash}{exchange_pairs}{$exchange};
for my $pair_rec (@$pair_recs) {
my $pair = $pair_rec->{name};
my ($basecur, $quotecur) = $pair =~ m!(.+)/(.+)!;
next unless grep { $_ eq $basecur } @{ $r->{_stash}{base_currencies} };
next unless grep { $_ eq $quotecur } @{ $r->{_stash}{quote_currencies} };
my $key;
if (App::cryp::arbit::_is_fiat($quotecur)) {
$key = "$basecur/:fiat";
$fiat_for{$exchange} //= [];
push @{ $fiat_for{$exchange} }, $quotecur
unless grep { $_ eq $quotecur } @{ $fiat_for{$exchange} };
} else {
$key = "$basecur/$quotecur";
}
$exchanges_for{$key} //= [];
push @{ $exchanges_for{$key} }, $exchange;
$pairs_for{$exchange} //= [];
push @{ $pairs_for{$exchange} }, $pair
unless grep { $_ eq $pair } @{ $pairs_for{$exchange} };
}
} # DETERMINE_SETS
ROUND:
while (1) {
SET:
for my $set (keys %exchanges_for) {
my ($base_currency, $quote_currency0) = $set =~ m!(.+)/(.+)!;
EXCHANGE:
for my $exchange (sort keys %{ $r->{_stash}{exchange_clients} }) {
my $eid = App::cryp::arbit::_get_exchange_id($r, $exchange);
my $clients = $r->{_stash}{exchange_clients}{$exchange};
my $client = $clients->{ (sort keys %$clients)[0] };
my @pairs;
if ($quote_currency0 eq ':fiat') {
push @pairs, map { "$base_currency/$_" } @{ $fiat_for{$exchange} };
} else {
push @pairs, $set;
}
PAIR:
for my $pair (@pairs) {
my ($basecur, $quotecur) = split m!/!, $pair;
next unless grep { $_ eq $pair } @{ $pairs_for{$exchange} };
my $time = time();
log_debug "Getting orderbook %s on %s ...", $pair, $exchange;
my $res = $client->get_order_book(pair => $pair);
unless ($res->[0] == 200) {
log_error "Couldn't get orderbook %s on %s: %s, skipping this pair",
$pair, $exchange, $res;
next PAIR;
}
# save orderbook to database
TYPE:
for my $type ("buy", "sell") {
# sanity checks
unless ($res->[2]{$type} && @{ $res->[2]{$type} }) {
log_warn "No $type orders for %s on %s, skipping",
$pair, $exchange;
next;
}
$dbh->do("INSERT INTO orderbook (time,exchange_id,base_currency,quote_currency,type) VALUES (?,?,?,?,?)", {}, $time, $eid, $basecur, $quotecur, $type);
my $orderbook_id = $dbh->last_insert_id("","","","");
my $sth = $dbh->prepare("INSERT INTO orderbook_item (orderbook_id, price, amount) VALUES (?,?,?)");
for my $item (@{ $res->[2]{$type} }) {
#log_trace "item: %s", $item;
$sth->execute($orderbook_id, $item->[0], $item->[1]);
}
} # TYPE
} # PAIR
} # EXCHANGE
} # SET
SLEEP:
log_trace "Sleeping for %d second(s) before next round ...",
$args{frequency};
sleep $args{frequency};
} # ROUND
[200];
}
sub _check_orders {
my $r = shift;
my $dbh = $r->{_stash}{dbh};
my $code_update_buy_status = sub {
my ($id, $status, $summary) = @_;
local $dbh->{RaiseError};
$dbh->do(
"UPDATE order_pair SET buy_status=? WHERE id=?",
{},
$status,
$id,
) or do {
log_warn "Couldn't update buy status for order pair #%d: %s",
$id, $dbh->errstr;
return;
};
$dbh->do(
( run in 2.500 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )