App-cryp-arbit

 view release on metacpan or  search on metacpan

lib/App/cryp/arbit/Strategy/merge_order_book.pm  view on Meta::CPAN

            $i++;
            my ($bcur) = $op->{buy}{pair}  =~ m!/(.+)!;
            my ($scur) = $op->{sell}{pair} =~ m!/(.+)!;

            if ($bcur eq $scur) {
                # there is no forex spread
                $op->{net_profit_margin} = $op->{trading_profit_margin};
                $op->{net_profit} = $op->{trading_profit};
                goto ADD;
            }

            my $spread;
            $spread = $forex_spreads->{"$bcur/$scur"} if $forex_spreads;

            unless (defined $spread) {
                log_warn "Order pair #%d (buy %s - sell %s): didn't find ".
                    "forex spread for %s/%s, not adjusting for forex spread",
                    $i, $op->{buy}{pair}, $op->{sell}{pair}, $bcur, $scur;
                next ORDER_PAIR;
            }
            log_trace "Order pair #%d (buy %s - sell %s, trading profit margin %.4f%%): adjusting ".
                "with %s/%s forex spread %.4f%%",
                $i, $op->{buy}{pair}, $op->{sell}{pair}, $op->{trading_profit_margin}, $bcur, $scur, $spread;
            $op->{forex_spread} = $spread;
            $op->{net_profit_margin} = $op->{trading_profit_margin} - $spread;
            $op->{net_profit} = $op->{trading_profit} * $op->{net_profit_margin} / $op->{trading_profit_margin};
            if ($op->{net_profit_margin} < $min_net_profit_margin) {
                log_trace "Order pair #%d: After forex spread adjustment, net profit margin is too small (%.4f%%, wants >= %.4f%%), skipping this order pair",
                    $i, $op->{net_profit_margin}, $min_net_profit_margin;
                next ORDER_PAIR;
            }

          ADD:
            push @order_pairs, $op;
        }
    } # ADJUST_FOREX_SPREAD

    # re-sort
    @order_pairs = sort { $b->{net_profit_margin} <=> $a->{net_profit_margin} } @order_pairs;

    (\@order_pairs, $opportunity);
}

sub calculate_order_pairs {
    my ($pkg, %args) = @_;

    my $r = $args{r};
    my $dbh = $r->{_stash}{dbh};

    my @order_pairs;

  GET_ACCOUNT_BALANCES:
    {
        last if $r->{args}{ignore_balance};
        App::cryp::arbit::_get_account_balances($r, 'no-cache');
    } # GET_ACCOUNT_BALANCES

  GET_FOREX_RATES:
    {
        # get foreign fiat currency vs USD exchange rate. we'll use the average
        # rate for this first. but we'll adjust the price difference percentage
        # with the buy-sell spread later.

        my %seen;

        $r->{_stash}{forex_rates} = {};

        for my $cur (@{ $r->{_stash}{quote_currencies} }) {
            next unless App::cryp::arbit::_is_fiat($cur);
            next if $cur eq 'USD';
            next if $seen{$cur}++;

            require Finance::Currency::FiatX;

            my $fxres_low  = Finance::Currency::FiatX::get_spot_rate(
                dbh => $dbh, from => $cur, to => 'USD', type => 'buy', source => ':lowest');
            if ($fxres_low->[0] != 200) {
                return [412, "Couldn't get conversion rate (lowest buy) from ".
                            "$cur to USD: $fxres_low->[0] - $fxres_low->[1]"];
            }

            my $fxres_high = Finance::Currency::FiatX::get_spot_rate(
                dbh => $dbh, from => $cur, to => 'USD', type => 'sell', source => ':highest');
            if ($fxres_high->[0] != 200) {
                return [412, "Couldn't get conversion rate (highest sell) ".
                            "from $cur to USD: $fxres_high->[0] - ".
                            "$fxres_high->[1]"];
            }

            my $fxrate_avg = ($fxres_low->[2]{rate} + $fxres_high->[2]{rate})/2;
            $r->{_stash}{forex_rates}{"$cur/USD"} = $fxrate_avg;
        }
    } # GET_FOREX_RATES

  GET_FOREX_SPREADS:
    {
        # when we arbitrage using two different fiat currencies, e.g. BTC/USD
        # and BTC/IDR, we want to take into account the USD/IDR buy-sell spread
        # (the "forex spread") and subtract this from the price difference
        # percentage to be safer.

        $r->{_stash}{forex_spreads} = {};

        my @curs;
        for my $cur (@{ $r->{_stash}{quote_currencies} }) {
            next unless App::cryp::arbit::_is_fiat($cur);
            push @curs, $cur unless grep { $cur eq $_ } @curs;
        }
        last unless @curs;

        require Finance::Currency::FiatX;

        for my $cur1 (@curs) {
            for my $cur2 (@curs) {
                next if $cur1 eq $cur2;

                my $fxres_low = Finance::Currency::FiatX::get_spot_rate(
                    dbh => $dbh, from => $cur1, to => $cur2, type => 'buy', source => ':lowest');
                if ($fxres_low->[0] != 200) {
                    return [412, "Couldn't get conversion rate (lowest buy) for ".
                                "$cur1/$cur2: $fxres_low->[0] - $fxres_low->[1]"];
                }

                my $fxres_high = Finance::Currency::FiatX::get_spot_rate(
                    dbh => $dbh, from => $cur1, to => $cur2, type => 'sell', source => ':highest');
                if ($fxres_high->[0] != 200) {
                    return [412, "Couldn't get conversion rate (highest sell) ".
                                "for $cur1/$cur2: $fxres_high->[0] - ".
                                "$fxres_high->[1]"];
                }

                my $r1 = $fxres_low->[2]{rate};
                my $r2 = $fxres_high->[2]{rate};
                my $spread = $r1 > $r2 ? ($r1-$r2)/$r2*100 : ($r2-$r1)/$r1*100;
                $r->{_stash}{forex_spreads}{"$cur1/$cur2"} = abs $spread;
            }
        }
    } # GET_FOREX_SPREADS

    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";
            }



( run in 1.532 second using v1.01-cache-2.11-cpan-99c4e6809bf )