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 )