App-cryp-arbit

 view release on metacpan or  search on metacpan

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

    accounts => {
        summary => 'Cryptoexchange accounts',
        schema => ['array*', of=>'cryptoexchange::account', min_len=>2],
        description => <<'_',

There should at least be two accounts, on at least two different
cryptoexchanges. If not specified, all accounts listed on the configuration file
will be included. Note that it's possible to include two or more accounts on the
same cryptoexchange.

_
    },
    base_currencies => {
        'x.name.is_plural' => 1,
        'x.name.singular' => 'base_currency',
        summary => 'Target (crypto)currencies to arbitrate',
        schema => ['array*', of=>'cryptocurrency*', min_len=>1],
        description => <<'_',

If not specified, will list all supported pairs on all the exchanges and include
the base cryptocurrencies that are listed on at least 2 different exchanges (for
arbitrage possibility).

_
    },
    quote_currencies => {
        'x.name.is_plural' => 1,
        'x.name.singular' => 'quote_currency',
        summary => 'The currencies to exchange (buy/sell) the target currencies',
        schema => ['array*', of=>'fiat_or_cryptocurrency*', min_len=>1],
        description => <<'_',

You can have fiat currencies as the quote currencies, to buy/sell the target
(base) currencies during arbitrage. For example, to arbitrage LTC against USD
and IDR, `base_currencies` is ['BTC'] and `quote_currencies` is ['USD', 'IDR'].

You can also arbitrage cryptocurrencies against other cryptocurrency (usually
BTC, "the USD of cryptocurrencies"). For example, to arbitrage XMR and LTC
against BTC, `base_currencies` is ['XMR', 'LTC'] and `quote_currencies` is
['BTC'].

_
    },
);

# shared between these subcommands: opportunities, arbit
our %args_arbit_common = (
    strategy => {
        summary => 'Which strategy to use for arbitration',
        schema => ['str*', match=>qr/\A\w+\z/],
        default => 'merge_order_book',
        description => <<'_',

Strategy is implemented in a `App::cryp::arbit::Strategy::*` perl module.

_
    },
    %args_accounts_and_currencies,
    min_net_profit_margin => {
        summary => 'Minimum net profit margin that will trigger an arbitrage '.
            'trading, in percentage',
        schema => 'float*',
        default => 0,
        description => <<'_',

Below this percentage number, no order pairs will be sent to the exchanges to do
the arbitrage. Note that the net profit margin already takes into account
trading fees and forex spread (see Glossary section for more details and
illustration).

Suggestion: If you set this option too high, there might not be any order pairs
possible. If you set this option too low, you will be getting too thin profits.
Run `cryp-arbit opportunities` or `cryp-arbit arbit --dry-run` for a while to
see what the average percentage is and then decide at which point you want to
perform arbitrage.

_
    },
    max_order_quote_size => {
        summary => 'What is the maximum amount of a single order',
        schema => 'float*',
        default => 100,
        description => <<'_',

A single order will be limited to not be above this value (in quote currency,
which if fiat will be converted to USD). This is the amount for the buying
(because an arbitrage transaction is comprised of a pair of orders, where one
order is a selling order at a higher quote currency size than the buying order).

For example if you are arbitraging BTC against USD and IDR, and set this option
to 75, then orders will not be above 75 USD. If you are arbitraging LTC against
BTC and set this to 0.03 then orders will not be above 0.03 BTC.

Suggestion: If you set this option too high, a few orders can use up your
inventory (and you might not be getting optimal profit percentage). Also, large
orders can take a while (or too long) to fill. If you set this option too low,
you will hit the exchanges' minimum order size and no orders can be created.
Since we want smaller risk of orders not getting filled quickly, we want small
order sizes. The optimum number range a little above the exchanges' minimum
order size.

_
    },
    max_order_pairs_per_round => {
        summary => 'Maximum number of order pairs to create per round',
        schema => 'posint*',
    },
    min_account_balances => {
        summary => 'What are the minimum account balances',
        schema => ['hash*', {
            each_key => 'cryptoexchange::account*',
            each_value => ['hash*', {
                each_key => 'fiat_or_cryptocurrency*',
                each_value => 'float',
            }],
        }],
    },
);

our %arg_max_order_age = (
    max_order_age => {
        summary => 'How long should we wait for orders to be completed '.
            'before cancelling them (in seconds)',
        schema => 'posint*',
        default => 86400,
        description => <<'_',

Sometimes because of rapid trading and price movement, our order might not be
filled immediately. This setting sets a limit on how long should an order be
left open. After this limit is reached, we cancel the order. The imbalance of
the arbitrage transaction will be recorded.

_
    },
);

our %arg_usd_rates = (
    usd_rates => {
        summary => 'Set USD rates',
        'x.name.is_plural' => 1,
        'x.name.singular' => 'usd_rate',
        schema => ['hash*', each_key=>["str*", match=>qr/\A[A-Z]{3}\z/], each_value=>'float*'],
        description => <<'_',

Example:

    --usd-rate IDR=14500 --usd-rate THB=33.25

_
    },
);

our $db_schema_spec = {
    component_name => 'cryp_arbit',
    latest_v => 3,

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

        'CREATE TABLE balance_history (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL,
             account_id INT NOT NULL,
             currency VARCHAR(10) NOT NULL,
             UNIQUE(time, account_id, currency),
             available DECIMAL(21,8) NOT NULL
         )',

        # XXX later move to cryp-folio
        'CREATE TABLE price (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL, INDEX(time),
             base_currency VARCHAR(10) NOT NULL,
             quote_currency VARCHAR(10) NOT NULL,
             type VARCHAR(4) NOT NULL, -- "buy" or "sell"
             price DECIMAL(21,8) NOT NULL, -- price to buy (or sell) base_currency in quote_currency, e.g. if base_currency = BTC, quote_currency = USD, price = 11150 means 1 BTC is $11150
             exchange_id INT NOT NULL,
             note VARCHAR(255)
         )',

        'CREATE TABLE arbit_opportunity (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL, INDEX(time),
             base_currency VARCHAR(10) NOT NULL,
             quote_currency VARCHAR(10) NOT NULL,
             -- base_size DECIMAL(21,8),
             buy_exchange_id INT NOT NULL,
             buy_price DECIMAL(21,8) NOT NULL,
             sell_exchange_id INT NOT NULL,
             sell_price DECIMAL(21,8) NOT NULL,
             gross_profit_margin DOUBLE NOT NULL,
             trading_profit_margin DOUBLE NOT NULL,
             net_profit_margin DOUBLE
         )',

        # to collect historical orderbook data
        'CREATE TABLE orderbook (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL, INDEX(time),
             exchange_id INT NOT NULL,
             base_currency VARCHAR(10) NOT NULL,
             quote_currency VARCHAR(10) NOT NULL,
             type TEXT NOT NULL -- "buy" or "sell"
         )',

        'CREATE TABLE orderbook_item (
             id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             orderbook_id INT NOT NULL, INDEX(orderbook_id),
             amount DECIMAL(21,8) NOT NULL,
             price DECIMAL(21,8) NOT NULL
         ) ENGINE=MyISAM',

        'CREATE TABLE order_pair (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             ctime DOUBLE NOT NULL, INDEX(ctime), -- create time in our database

             base_currency VARCHAR(10) NOT NULL, -- the currency we are arbitraging, e.g. LTC
             base_size DECIMAL(21,8) NOT NULL, -- amount of "currency" that we are arbitraging (sell on "sell exchange" and buy on "buy exchange")

             expected_profit_margin DOUBLE NOT NULL, -- expected profit percentage (after trading fees & forex spread)
             expected_net_profit DOUBLE NOT NULL, -- expected net profit (after trading fees & forex spread) in quote currency (converted to USD if fiat) if fully executed

             -- we buy "base_size" of "base_currency" on "buy exchange" at
             -- "buy_gross_price_orig" (in "buy_quote_currency") a.k.a
             -- "buy_gross_price" (in "buy_quote_currency" converted to USD if
             -- fiat)

             -- possible statuses/lifecyle: creating (submitting to exchange),
             -- open (created and open), cancelling, cancelled, done

             buy_exchange_id INT NOT NULL,
             buy_account_id INT NOT NULL,
             buy_quote_currency VARCHAR(10) NOT NULL,
             buy_gross_price_orig DECIMAL(21,8) NOT NULL,
             buy_gross_price DECIMAL(21,8) NOT NULL,
             buy_status VARCHAR(16) NOT NULL,

             buy_ctime DOUBLE, -- order create time in "buy_exchange"
             buy_order_id VARCHAR(80),
             buy_actual_price DECIMAL(21,8), -- actual price after we create on exchange
             buy_actual_base_size DECIMAL(21,8), -- actual size after we create on exchange
             buy_filled_base_size DECIMAL(21,8),

             -- then sell the same "base_size" of "base_currency"" on "sell
             -- exchange" (the "base_currency"/"sell_exchange_quote_currency"
             -- market pair) at "sell_gross_price_orig" (in
             -- "sell_exchange_quote_currency") a.k.a "sell_gross_price" (in
             -- "sell_exchange_quote_currency" converted to USD if fiat)

             sell_exchange_id INT NOT NULL,
             sell_account_id INT NOT NULL,
             sell_quote_currency VARCHAR(10) NOT NULL,
             sell_gross_price_orig DECIMAL(21,8) NOT NULL,
             sell_gross_price DECIMAL(21,8) NOT NULL,
             sell_status VARCHAR(16) NOT NULL,

             sell_ctime DOUBLE, -- create time in "sell exchange"
             sell_order_id VARCHAR(80),
             sell_actual_price DECIMAL(21,8), -- actual price after we create on exchange
             sell_actual_base_size DECIMAL(21,8), -- actual size after we create on exchange
             sell_filled_base_size DECIMAL(21,8)
         )',

        'CREATE TABLE arbit_order_log (
            id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
            order_pair_id INT NOT NULL,
            type VARCHAR(4) NOT NULL, -- "buy" or "sell"
            summary TEXT NOT NULL
        )',
    ],
    upgrade_to_v3 => [
        'ALTER TABLE orderbook_item ENGINE=MyISAM, CHANGE COLUMN id id BIGINT NOT NULL AUTO_INCREMENT',
    ],
    upgrade_to_v2 => [
        # to collect historical orderbook data
        'CREATE TABLE orderbook (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL, INDEX(time),
             exchange_id INT NOT NULL,
             base_currency VARCHAR(10) NOT NULL,

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

             exchange_id INT NOT NULL,
             nickname VARCHAR(64) NOT NULL,
             UNIQUE(exchange_id,nickname),
             note VARCHAR(255)
         )',

        # XXX later move to cryp-folio?
        'CREATE TABLE latest_balance (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL,
             account_id INT NOT NULL,
             currency VARCHAR(10) NOT NULL,
             UNIQUE(account_id, currency),
             available DECIMAL(21,8) NOT NULL
         )',

        # XXX later move to cryp-folio?
        'CREATE TABLE balance_history (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL,
             account_id INT NOT NULL,
             currency VARCHAR(10) NOT NULL,
             UNIQUE(time, account_id, currency),
             available DECIMAL(21,8) NOT NULL
         )',

        # XXX later move to cryp-folio
        'CREATE TABLE price (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL, INDEX(time),
             base_currency VARCHAR(10) NOT NULL,
             quote_currency VARCHAR(10) NOT NULL,
             type VARCHAR(4) NOT NULL, -- "buy" or "sell"
             price DECIMAL(21,8) NOT NULL, -- price to buy (or sell) base_currency in quote_currency, e.g. if base_currency = BTC, quote_currency = USD, price = 11150 means 1 BTC is $11150
             exchange_id INT NOT NULL,
             note VARCHAR(255)
         )',

        'CREATE TABLE arbit_opportunity (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             time DOUBLE NOT NULL, INDEX(time),
             base_currency VARCHAR(10) NOT NULL,
             quote_currency VARCHAR(10) NOT NULL,
             -- base_size DECIMAL(21,8),
             buy_exchange_id INT NOT NULL,
             buy_price DECIMAL(21,8) NOT NULL,
             sell_exchange_id INT NOT NULL,
             sell_price DECIMAL(21,8) NOT NULL,
             gross_profit_margin DOUBLE NOT NULL,
             trading_profit_margin DOUBLE NOT NULL,
             net_profit_margin DOUBLE
         )',

        'CREATE TABLE order_pair (
             id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
             ctime DOUBLE NOT NULL, INDEX(ctime), -- create time in our database

             base_currency VARCHAR(10) NOT NULL, -- the currency we are arbitraging, e.g. LTC
             base_size DECIMAL(21,8) NOT NULL, -- amount of "currency" that we are arbitraging (sell on "sell exchange" and buy on "buy exchange")

             expected_profit_margin DOUBLE NOT NULL, -- expected profit percentage (after trading fees & forex spread)
             expected_net_profit DOUBLE NOT NULL, -- expected net profit (after trading fees & forex spread) in quote currency (converted to USD if fiat) if fully executed

             -- we buy "base_size" of "base_currency" on "buy exchange" at
             -- "buy_gross_price_orig" (in "buy_quote_currency") a.k.a
             -- "buy_gross_price" (in "buy_quote_currency" converted to USD if
             -- fiat)

             -- possible statuses/lifecyle: creating (submitting to exchange),
             -- open (created and open), cancelling, cancelled, done

             buy_exchange_id INT NOT NULL,
             buy_account_id INT NOT NULL,
             buy_quote_currency VARCHAR(10) NOT NULL,
             buy_gross_price_orig DECIMAL(21,8) NOT NULL,
             buy_gross_price DECIMAL(21,8) NOT NULL,
             buy_status VARCHAR(16) NOT NULL,

             buy_ctime DOUBLE, -- order create time in "buy_exchange"
             buy_order_id VARCHAR(80),
             buy_actual_price DECIMAL(21,8), -- actual price after we create on exchange
             buy_actual_base_size DECIMAL(21,8), -- actual size after we create on exchange
             buy_filled_base_size DECIMAL(21,8),

             -- then sell the same "base_size" of "base_currency"" on "sell
             -- exchange" (the "base_currency"/"sell_exchange_quote_currency"
             -- market pair) at "sell_gross_price_orig" (in
             -- "sell_exchange_quote_currency") a.k.a "sell_gross_price" (in
             -- "sell_exchange_quote_currency" converted to USD if fiat)

             sell_exchange_id INT NOT NULL,
             sell_account_id INT NOT NULL,
             sell_quote_currency VARCHAR(10) NOT NULL,
             sell_gross_price_orig DECIMAL(21,8) NOT NULL,
             sell_gross_price DECIMAL(21,8) NOT NULL,
             sell_status VARCHAR(16) NOT NULL,

             sell_ctime DOUBLE, -- create time in "sell exchange"
             sell_order_id VARCHAR(80),
             sell_actual_price DECIMAL(21,8), -- actual price after we create on exchange
             sell_actual_base_size DECIMAL(21,8), -- actual size after we create on exchange
             sell_filled_base_size DECIMAL(21,8)
         )',

        'CREATE TABLE arbit_order_log (
            id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
            order_pair_id INT NOT NULL,
            type VARCHAR(4) NOT NULL, -- "buy" or "sell"
            summary TEXT NOT NULL
        )',
    ],
};

my $fnum2 = [number => {precision=>2}];
my $fnum4 = [number => {precision=>4}];
my $fnum8 = [number => {precision=>8}];

sub _exchange_catalog {
    state $xcat = do {
        require CryptoExchange::Catalog;
        CryptoExchange::Catalog->new;

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

        }
        push @recs, {
            summary => 'Profit',
            currency => 'USD',
            amount => $profit,
            amount_usd => $profit,
        };
        for my $cur (sort keys %per_currency_sums) {
            next if _is_fiat($cur);
            next if $per_currency_sums{$cur} == 0;
            push @recs, {
                currency => $cur,
                amount => $per_currency_sums{$cur},
            };
        }
    }

    my $resmeta = {
        'table.fields'        => ['time'            , 'summary', 'currency', 'amount', 'amount_usd'],
        'table.field_labels'  => [undef             , undef    , 'c'        , undef  , 'amountUSD'],
        'table.field_formats' => ['iso8601_datetime', undef    , undef     , $fnum8  , $fnum8],
        'table.field_aligns'  => ['left'            , 'left'   , 'left'    , 'right' , 'right'],
    };

    [200, "OK", \@recs, $resmeta];
}

1;
# ABSTRACT: Cryptocurrency arbitrage utility

__END__

=pod

=encoding UTF-8

=head1 NAME

App::cryp::arbit - Cryptocurrency arbitrage utility

=head1 VERSION

This document describes version 0.010 of App::cryp::arbit (from Perl distribution App-cryp-arbit), released on 2021-05-26.

=head1 SYNOPSIS

Please see included script L<cryp-arbit>.

=head1 DESCRIPTION

=head2 Glossary

=over

=item * inventory

=item * order pair

=item * gross profit margin

Price difference percentage of a cryptocurrency between two exchanges, without
taking into account trading fees and foreign exchange spread.

For example, suppose BTC is being offered (ask price, sell price) at 7010 USD on
exchange1 and is being bidden (bid price, buy price) at 7150 USD on exchange2.
This means there is a (7150-7010)/7010 = 1.997% gross profit margin. We can buy
BTC on exchange1 for 7010 USD then sell the same amout of BTC on exchange2 for
7150 USD and gain (7150-7010) = 140 USD per BTC, before fees.

=item * trading profit margin

Price difference percentage of a cryptocurrency between two exchanges, after
taking into account trading fees.

For example, suppose BTC is being offered (ask price, sell price) at 7010 USD on
exchange1 and is being bidden (bid price, buy price) at 7150 USD on exchange2.
Trading (market maker) fee on exchange1 is 0.3% and on exchange2 is 0.25%. After
trading fees, the ask price becomes 7010 * (1+0.3%) = 7031.03 USD and the bid
price becomes 7150 * (1-0.25%) = 7132.125. The trading profit margin is
(7132.125-7031.03)/7031.03 = 1.438%. We can buy BTC on exchange1 for 7010 USD
then sell the same amout of BTC on exchange2 for 7150 USD and still gain
(7132.125-7031.03) = 101.095 USD per BTC, after trading fees.

=item * net profit margin

Price difference percentage of a cryptocurrency between two exchanges, after
taking into account trading fees and foreign exchange spread. If the price on
both exchanges are quoted in the same currency (e.g. USD) then there is no forex
spread and net profit margin is the same as trading profit margin.

If the quoting currencies are different, e.g. USD on exchange1 and IDR on
exchange2, then first we calculate gross and trading profit margin using prices
converted to USD using average forex rate (highest forex dealer's sell price +
lowest buy price, divided by two). Then we subtract trading profit margin with
forex spread for safety.

For example, suppose BTC is being offered (ask price, sell price) at 7010 USD on
exchange1 and is being bidden (bid price, buy price) at 99,500,000 IDR on
exchange2. The forex rate for USD/IDR is: buy 13,895, sell 13,925, average
(13,925+13,895)/2 = 13,910, spread (13,925-13,895)/13,895 = 0.216%. The price on
exchange2 in USD is 99,500,000 / 13,910 = 7153.127 USD. Trading (market maker)
fee on exchange1 is 0.3% and on exchange2 is 0.25%. After trading fees, the ask
price becomes 7010 * (1+0.3%) = 7031.03 USD and the bid price becomes 7153.127 *
(1-0.25%) = 7135.244. The trading profit margin is (7135.244-7031.03)/7031.03 =
1.482%. We can buy BTC on exchange1 for 7010 USD then sell the same amout of BTC
on exchange2 for 7150 USD and still gain (7132.125-7031.03) = 101.095 USD per
BTC, after trading fees. The net profit margin is 1.482% - 0.216% = 1.266%.

=back

=head1 INTERNAL NOTES

The cryp app family uses L<Perinci::CmdLine::cryp> which puts cryp-specific
information from the configuration into the $r->{_cryp} hash:

 $r->{_cryp}
   {arbit_strategies}  # from [arbit-strategy/XXX] config sections
   {exchanges}         # from [exchange/XXX(/YYY)?] config sections
   {masternodes}       # from [masternode/XXX(/YYY)?] config sections
   {wallet}            # from [wallet/COIN]

Routines inside this module communicate with one another either using the
database (obviously), or by putting stuffs in C<$r> (the request hash/stash) and
passing C<$r> around. The keys that are used by routines in this module:

 $r->{_stash}
   {dbh}
   {account_balances}          # key=exchange safename, value={currency1 => [{account=>account1, account_id=>aid, available=>..., ...}, {...}]}. value->{currency} sorted by largest available balance first
   {account_exchanges}         # key=exchange safename, value={account1 => 1, ...}
   {account_ids}               # key=exchange safename, value={account1 => numeric ID from db, ...}
   {base_currencies}           # target (crypto)currencies to arbitrage
   {exchange_clients}          # key=exchange safename, value={account1 => $client1, ...}
   {exchange_ids}              # key=exchange safename, value=exchange (numeric) ID from db
   {exchange_recs}             # key=exchange safename, value=hash (from CryptoExchange::Catalog)
   {exchange_coins}            # key=exchange safename, value=[COIN1, COIN2, ...]
   {exchange_pairs}            # key=exchange safename, value=[{name=>PAIR1, min_base_size=>..., min_quote_size=>...}, ...]
   {forex_rates}               # key=currency pair (e.g. IDR/USD), val=exchange rate (avg rate)
   {forex_spreads}             # key=fiat currency pair, e.g. USD/IDR, value=percentage
   {fx}                        # key=currency value=result from get_spot_rate()
   {order_pairs}               # result from calculate_order_pairs()
   {quote_currencies}          # what currencies we use to buy/sell the base currencies
   {quote_currencies_for}      # key=base currency, value={quotecurrency1 => 1, quotecurrency2=>1, ...}
   {trading_fees}              # key=exchange safename, value={coin1=>num (in percent) market taker fees, ...}, ':default' for all other coins, ':default' for all other exchanges

=head1 FUNCTIONS


=head2 arbit

Usage:

 arbit(%args) -> [$status_code, $reason, $payload, \%result_meta]

Perform arbitrage.

This utility monitors prices of several cryptocurrencies ("base currencies",
e.g. LTC) in several cryptoexchanges. The "quote currency" can be fiat (e.g.
USD, all other fiat currencies will be converted to USD) or another
cryptocurrency (usually BTC).

When it detects a net price difference for a base currency that is large enough
(see C<min_net_profit_margin> option), it will perform a buy order on the
exchange that has the lower price and sell the exact same amount of base
currency on the exchange that has the higher price. For example, if on XCHG1 the
buy price of LTC 100.01 USD and on XCHG2 the sell price of LTC is 98.80 USD,
then this utility will buy LTC on XCHG2 for 98.80 USD and sell the same amount
of LTD on XCHG1 for 100.01 USD. The profit is (100.01 - 98.80 - trading fees)
per LTC arbitraged. You have to maintain enough LTC balance on XCHG1 and enough
USD balance on XCHG2.

The balances are called inventories or your working capital. You fill and
transfer inventories manually to refill balances and/or to collect profits.

This function is not exported.

This function supports dry-run operation.


Arguments ('*' denotes required arguments):

=over 4

=item * B<accounts> => I<array[cryptoexchange::account]>

Cryptoexchange accounts.

There should at least be two accounts, on at least two different
cryptoexchanges. If not specified, all accounts listed on the configuration file
will be included. Note that it's possible to include two or more accounts on the
same cryptoexchange.

=item * B<base_currencies> => I<array[cryptocurrency]>

Target (crypto)currencies to arbitrate.

If not specified, will list all supported pairs on all the exchanges and include
the base cryptocurrencies that are listed on at least 2 different exchanges (for
arbitrage possibility).

=item * B<db_name>* => I<str>

=item * B<db_password> => I<str>

=item * B<db_username> => I<str>

=item * B<frequency> => I<posint> (default: 30)

How many seconds to wait between rounds (in seconds).

A round consists of checking prices and then creating arbitraging order pairs.

=item * B<max_order_age> => I<posint> (default: 86400)

How long should we wait for orders to be completed before cancelling them (in seconds).

Sometimes because of rapid trading and price movement, our order might not be
filled immediately. This setting sets a limit on how long should an order be
left open. After this limit is reached, we cancel the order. The imbalance of
the arbitrage transaction will be recorded.

=item * B<max_order_pairs_per_round> => I<posint>

Maximum number of order pairs to create per round.

=item * B<max_order_quote_size> => I<float> (default: 100)

What is the maximum amount of a single order.

A single order will be limited to not be above this value (in quote currency,
which if fiat will be converted to USD). This is the amount for the buying
(because an arbitrage transaction is comprised of a pair of orders, where one
order is a selling order at a higher quote currency size than the buying order).

For example if you are arbitraging BTC against USD and IDR, and set this option
to 75, then orders will not be above 75 USD. If you are arbitraging LTC against
BTC and set this to 0.03 then orders will not be above 0.03 BTC.

Suggestion: If you set this option too high, a few orders can use up your
inventory (and you might not be getting optimal profit percentage). Also, large
orders can take a while (or too long) to fill. If you set this option too low,
you will hit the exchanges' minimum order size and no orders can be created.
Since we want smaller risk of orders not getting filled quickly, we want small
order sizes. The optimum number range a little above the exchanges' minimum
order size.

=item * B<min_account_balances> => I<hash>

What are the minimum account balances.

=item * B<min_net_profit_margin> => I<float> (default: 0)

Minimum net profit margin that will trigger an arbitrage trading, in percentage.

Below this percentage number, no order pairs will be sent to the exchanges to do
the arbitrage. Note that the net profit margin already takes into account
trading fees and forex spread (see Glossary section for more details and
illustration).

Suggestion: If you set this option too high, there might not be any order pairs
possible. If you set this option too low, you will be getting too thin profits.
Run C<cryp-arbit opportunities> or C<cryp-arbit arbit --dry-run> for a while to
see what the average percentage is and then decide at which point you want to
perform arbitrage.

=item * B<quote_currencies> => I<array[fiat_or_cryptocurrency]>

The currencies to exchange (buyE<sol>sell) the target currencies.

You can have fiat currencies as the quote currencies, to buy/sell the target
(base) currencies during arbitrage. For example, to arbitrage LTC against USD
and IDR, C<base_currencies> is ['BTC'] and C<quote_currencies> is ['USD', 'IDR'].

You can also arbitrage cryptocurrencies against other cryptocurrency (usually
BTC, "the USD of cryptocurrencies"). For example, to arbitrage XMR and LTC
against BTC, C<base_currencies> is ['XMR', 'LTC'] and C<quote_currencies> is
['BTC'].

=item * B<rounds> => I<int> (default: 1)

How many rounds.

-1 means unlimited.

=item * B<strategy> => I<str> (default: "merge_order_book")

Which strategy to use for arbitration.

Strategy is implemented in a C<App::cryp::arbit::Strategy::*> perl module.


=back

Special arguments:

=over 4

=item * B<-dry_run> => I<bool>

Pass -dry_run=E<gt>1 to enable simulation mode.

=back

Returns an enveloped result (an array).

First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.

Return value:  (any)



=head2 check_orders

Usage:

 check_orders(%args) -> [$status_code, $reason, $payload, \%result_meta]

Check the orders that have been created.

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


This subcommand, like the C<arbit> subcommand, checks prices of cryptocurrencies
on several exchanges for arbitrage possibility; but does not actually perform
the arbitraging.

This function is not exported.

Arguments ('*' denotes required arguments):

=over 4

=item * B<accounts> => I<array[cryptoexchange::account]>

Cryptoexchange accounts.

There should at least be two accounts, on at least two different
cryptoexchanges. If not specified, all accounts listed on the configuration file
will be included. Note that it's possible to include two or more accounts on the
same cryptoexchange.

=item * B<base_currencies> => I<array[cryptocurrency]>

Target (crypto)currencies to arbitrate.

If not specified, will list all supported pairs on all the exchanges and include
the base cryptocurrencies that are listed on at least 2 different exchanges (for
arbitrage possibility).

=item * B<db_name>* => I<str>

=item * B<db_password> => I<str>

=item * B<db_username> => I<str>

=item * B<ignore_balance> => I<bool> (default: 0)

Ignore account balances.

=item * B<ignore_min_order_size> => I<bool> (default: 0)

Ignore minimum order size limitation from exchanges.

=item * B<max_order_pairs_per_round> => I<posint>

Maximum number of order pairs to create per round.

=item * B<max_order_quote_size> => I<float> (default: 100)

What is the maximum amount of a single order.

A single order will be limited to not be above this value (in quote currency,
which if fiat will be converted to USD). This is the amount for the buying
(because an arbitrage transaction is comprised of a pair of orders, where one
order is a selling order at a higher quote currency size than the buying order).

For example if you are arbitraging BTC against USD and IDR, and set this option
to 75, then orders will not be above 75 USD. If you are arbitraging LTC against
BTC and set this to 0.03 then orders will not be above 0.03 BTC.

Suggestion: If you set this option too high, a few orders can use up your
inventory (and you might not be getting optimal profit percentage). Also, large
orders can take a while (or too long) to fill. If you set this option too low,
you will hit the exchanges' minimum order size and no orders can be created.
Since we want smaller risk of orders not getting filled quickly, we want small
order sizes. The optimum number range a little above the exchanges' minimum
order size.

=item * B<min_account_balances> => I<hash>

What are the minimum account balances.

=item * B<min_net_profit_margin> => I<float> (default: 0)

Minimum net profit margin that will trigger an arbitrage trading, in percentage.

Below this percentage number, no order pairs will be sent to the exchanges to do
the arbitrage. Note that the net profit margin already takes into account
trading fees and forex spread (see Glossary section for more details and
illustration).

Suggestion: If you set this option too high, there might not be any order pairs
possible. If you set this option too low, you will be getting too thin profits.
Run C<cryp-arbit opportunities> or C<cryp-arbit arbit --dry-run> for a while to
see what the average percentage is and then decide at which point you want to
perform arbitrage.

=item * B<quote_currencies> => I<array[fiat_or_cryptocurrency]>

The currencies to exchange (buyE<sol>sell) the target currencies.

You can have fiat currencies as the quote currencies, to buy/sell the target
(base) currencies during arbitrage. For example, to arbitrage LTC against USD
and IDR, C<base_currencies> is ['BTC'] and C<quote_currencies> is ['USD', 'IDR'].

You can also arbitrage cryptocurrencies against other cryptocurrency (usually
BTC, "the USD of cryptocurrencies"). For example, to arbitrage XMR and LTC
against BTC, C<base_currencies> is ['XMR', 'LTC'] and C<quote_currencies> is
['BTC'].

=item * B<strategy> => I<str> (default: "merge_order_book")

Which strategy to use for arbitration.

Strategy is implemented in a C<App::cryp::arbit::Strategy::*> perl module.


=back

Returns an enveloped result (an array).

First element ($status_code) is an integer containing HTTP-like status code
(200 means OK, 4xx caller error, 5xx function error). Second element
($reason) is a string containing error message, or something like "OK" if status is
200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
element (%result_meta) is called result metadata and is optional, a hash
that contains extra information, much like how HTTP response headers provide additional metadata.

Return value:  (any)

=head1 HOMEPAGE

Please visit the project's homepage at L<https://metacpan.org/release/App-cryp-arbit>.

=head1 SOURCE

Source repository is at L<https://github.com/perlancar/perl-App-cryp-arbit>.

=head1 BUGS

Please report any bugs or feature requests on the bugtracker website L<https://github.com/perlancar/perl-App-cryp-arbit/issues>

When submitting a bug or request, please include a test-file or a
patch to an existing test-file that illustrates the bug or desired
feature.

=head1 SEE ALSO

=head1 AUTHOR

perlancar <perlancar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2021, 2018 by perlancar@cpan.org.



( run in 2.103 seconds using v1.01-cache-2.11-cpan-6fb7bf0f510 )