| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package App::cryp::arbit::Strategy::merge_order_book; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
our $DATE = '2018-12-03'; # DATE |
|
4
|
|
|
|
|
|
|
our $VERSION = '0.009'; # VERSION |
|
5
|
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
99088
|
use 5.010001; |
|
|
1
|
|
|
|
|
14
|
|
|
7
|
1
|
|
|
1
|
|
5
|
use strict; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
31
|
|
|
8
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
24
|
|
|
9
|
1
|
|
|
1
|
|
2157
|
use Log::ger; |
|
|
1
|
|
|
|
|
51
|
|
|
|
1
|
|
|
|
|
6
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
require App::cryp::arbit; |
|
12
|
1
|
|
|
1
|
|
779
|
use Finance::Currency::FiatX; |
|
|
1
|
|
|
|
|
4006
|
|
|
|
1
|
|
|
|
|
77
|
|
|
13
|
1
|
|
|
1
|
|
9
|
use List::Util qw(min max shuffle); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
70
|
|
|
14
|
1
|
|
|
1
|
|
588
|
use Storable qw(dclone); |
|
|
1
|
|
|
|
|
3133
|
|
|
|
1
|
|
|
|
|
66
|
|
|
15
|
1
|
|
|
1
|
|
560
|
use Time::HiRes qw(time); |
|
|
1
|
|
|
|
|
1333
|
|
|
|
1
|
|
|
|
|
4
|
|
|
16
|
|
|
|
|
|
|
|
|
17
|
1
|
|
|
1
|
|
591
|
use Role::Tiny::With; |
|
|
1
|
|
|
|
|
5233
|
|
|
|
1
|
|
|
|
|
3794
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
with 'App::cryp::Role::ArbitStrategy'; |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
sub _calculate_order_pairs_for_base_currency { |
|
22
|
17
|
|
|
17
|
|
82023
|
my %args = @_; |
|
23
|
|
|
|
|
|
|
|
|
24
|
17
|
|
|
|
|
44
|
my $base_currency = $args{base_currency}; |
|
25
|
17
|
|
|
|
|
32
|
my $all_buy_orders = $args{all_buy_orders}; |
|
26
|
17
|
|
|
|
|
29
|
my $all_sell_orders = $args{all_sell_orders}; |
|
27
|
17
|
|
50
|
|
|
51
|
my $min_net_profit_margin = $args{min_net_profit_margin} // 0; |
|
28
|
17
|
|
|
|
|
26
|
my $max_order_quote_size = $args{max_order_quote_size}; |
|
29
|
17
|
|
|
|
|
32
|
my $max_order_pairs = $args{max_order_pairs}; |
|
30
|
17
|
|
100
|
|
|
64
|
my $max_order_size_as_book_item_size_pct = $args{max_order_size_as_book_item_size_pct} // 100; |
|
31
|
17
|
|
|
|
|
28
|
my $account_balances = $args{account_balances}; |
|
32
|
17
|
|
|
|
|
32
|
my $min_account_balances = $args{min_account_balances}; |
|
33
|
17
|
|
|
|
|
31
|
my $exchange_pairs = $args{exchange_pairs}; |
|
34
|
17
|
|
|
|
|
27
|
my $forex_spreads = $args{forex_spreads}; |
|
35
|
|
|
|
|
|
|
|
|
36
|
17
|
|
|
|
|
31
|
my @order_pairs; |
|
37
|
|
|
|
|
|
|
my $opportunity; |
|
38
|
|
|
|
|
|
|
|
|
39
|
17
|
|
|
|
|
26
|
for (@{ $all_buy_orders }, @{ $all_sell_orders }) { |
|
|
17
|
|
|
|
|
47
|
|
|
|
17
|
|
|
|
|
37
|
|
|
40
|
51
|
|
|
|
|
111
|
$_->{base_size} *= $max_order_size_as_book_item_size_pct/100; |
|
41
|
|
|
|
|
|
|
} |
|
42
|
|
|
|
|
|
|
|
|
43
|
17
|
100
|
100
|
|
|
73
|
if ($account_balances && $min_account_balances) { |
|
44
|
1
|
|
|
|
|
6
|
for my $e (keys %$account_balances) { |
|
45
|
2
|
|
|
|
|
4
|
my $balances = $account_balances->{$e}; |
|
46
|
2
|
|
|
|
|
7
|
for my $cur (keys %$balances) { |
|
47
|
2
|
|
|
|
|
4
|
my $curbalances = $balances->{$cur}; |
|
48
|
2
|
|
|
|
|
5
|
for my $rec (@$curbalances) { |
|
49
|
3
|
|
|
|
|
9
|
my $eacc = "$e/$rec->{account}"; |
|
50
|
3
|
100
|
66
|
|
|
17
|
if (defined $min_account_balances->{$eacc} && |
|
51
|
|
|
|
|
|
|
defined $min_account_balances->{$eacc}{$cur}) { |
|
52
|
2
|
|
|
|
|
6
|
$rec->{available} -= $min_account_balances->{$eacc}{$cur}; |
|
53
|
|
|
|
|
|
|
} |
|
54
|
|
|
|
|
|
|
} |
|
55
|
|
|
|
|
|
|
} |
|
56
|
|
|
|
|
|
|
} |
|
57
|
1
|
|
|
|
|
6
|
App::cryp::arbit::_sort_account_balances($account_balances); |
|
58
|
|
|
|
|
|
|
} |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
CREATE: |
|
61
|
17
|
|
|
|
|
30
|
while (1) { |
|
62
|
52
|
100
|
100
|
|
|
133
|
last CREATE if defined $max_order_pairs && |
|
63
|
|
|
|
|
|
|
@order_pairs >= $max_order_pairs; |
|
64
|
|
|
|
|
|
|
|
|
65
|
50
|
|
|
|
|
84
|
my ($sell, $sell_index); |
|
66
|
|
|
|
|
|
|
FIND_BUYER: |
|
67
|
|
|
|
|
|
|
{ |
|
68
|
50
|
|
|
|
|
66
|
$sell_index = 0; |
|
|
50
|
|
|
|
|
81
|
|
|
69
|
50
|
|
|
|
|
102
|
while ($sell_index < @$all_buy_orders) { |
|
70
|
45
|
|
|
|
|
75
|
$sell = $all_buy_orders->[$sell_index]; |
|
71
|
45
|
100
|
|
|
|
87
|
if ($account_balances) { |
|
72
|
|
|
|
|
|
|
# we don't have any inventory left to sell on this selling |
|
73
|
|
|
|
|
|
|
# exchange |
|
74
|
15
|
100
|
50
|
|
|
22
|
unless (@{ $account_balances->{ $sell->{exchange} }{$base_currency} // [] }) { |
|
|
15
|
|
|
|
|
54
|
|
|
75
|
6
|
|
|
|
|
10
|
$sell_index++; next; |
|
|
6
|
|
|
|
|
15
|
|
|
76
|
|
|
|
|
|
|
} |
|
77
|
|
|
|
|
|
|
} |
|
78
|
39
|
|
|
|
|
67
|
last; |
|
79
|
|
|
|
|
|
|
} |
|
80
|
|
|
|
|
|
|
# there are no more buyers left we can sell to |
|
81
|
50
|
100
|
|
|
|
106
|
last CREATE unless $sell_index < @$all_buy_orders; |
|
82
|
|
|
|
|
|
|
} |
|
83
|
|
|
|
|
|
|
|
|
84
|
39
|
|
|
|
|
62
|
my ($buy, $buy_index); |
|
85
|
|
|
|
|
|
|
FIND_SELLER: |
|
86
|
|
|
|
|
|
|
{ |
|
87
|
39
|
|
|
|
|
55
|
$buy_index = 0; |
|
|
39
|
|
|
|
|
55
|
|
|
88
|
39
|
|
|
|
|
76
|
while ($buy_index < @$all_sell_orders) { |
|
89
|
37
|
|
|
|
|
59
|
$buy = $all_sell_orders->[$buy_index]; |
|
90
|
|
|
|
|
|
|
# shouldn't happen though |
|
91
|
37
|
50
|
|
|
|
80
|
if ($buy->{exchange} eq $sell->{exchange}) { |
|
92
|
0
|
|
|
|
|
0
|
$buy_index++; next; |
|
|
0
|
|
|
|
|
0
|
|
|
93
|
|
|
|
|
|
|
} |
|
94
|
37
|
100
|
|
|
|
68
|
if ($account_balances) { |
|
95
|
|
|
|
|
|
|
# we don't have any inventory left to buy from this exchange |
|
96
|
9
|
100
|
50
|
|
|
15
|
unless (@{ $account_balances->{ $buy->{exchange} }{$buy->{quote_currency}} // [] }) { |
|
|
9
|
|
|
|
|
30
|
|
|
97
|
1
|
|
|
|
|
2
|
$buy_index++; next; |
|
|
1
|
|
|
|
|
6
|
|
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
} |
|
100
|
36
|
|
|
|
|
72
|
last; |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
# there are no more sellers left we can buy from |
|
103
|
39
|
100
|
|
|
|
82
|
last CREATE unless $buy_index < @$all_sell_orders; |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
my $gross_profit_margin = ($sell->{gross_price} - $buy->{gross_price}) / |
|
107
|
36
|
|
|
|
|
128
|
min($sell->{gross_price}, $buy->{gross_price}) * 100; |
|
108
|
|
|
|
|
|
|
my $trading_profit_margin = ($sell->{net_price} - $buy->{net_price}) / |
|
109
|
36
|
|
|
|
|
93
|
min($sell->{net_price}, $buy->{net_price}) * 100; |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
# record opportunity, the currently highest trading profit margin |
|
112
|
36
|
100
|
|
|
|
75
|
unless ($opportunity) { |
|
113
|
17
|
|
|
|
|
30
|
my $quote_currency = $sell->{quote_currency}; |
|
114
|
17
|
50
|
|
|
|
54
|
if (App::cryp::arbit::_is_fiat($quote_currency)) { |
|
115
|
17
|
|
|
|
|
35
|
$quote_currency = "USD"; |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
$opportunity = { |
|
118
|
|
|
|
|
|
|
time => time(), |
|
119
|
|
|
|
|
|
|
base_currency => $base_currency, |
|
120
|
|
|
|
|
|
|
quote_currency => $quote_currency, |
|
121
|
|
|
|
|
|
|
buy_exchange => $buy->{exchange}, |
|
122
|
|
|
|
|
|
|
buy_price => $buy->{gross_price}, |
|
123
|
|
|
|
|
|
|
sell_exchange => $sell->{exchange}, |
|
124
|
|
|
|
|
|
|
sell_price => $sell->{gross_price}, |
|
125
|
17
|
|
|
|
|
150
|
gross_profit_margin => $gross_profit_margin, |
|
126
|
|
|
|
|
|
|
trading_profit_margin => $trading_profit_margin, |
|
127
|
|
|
|
|
|
|
}; |
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
|
|
130
|
36
|
100
|
|
|
|
90
|
if ($trading_profit_margin < $min_net_profit_margin) { |
|
131
|
1
|
|
|
|
|
39
|
log_trace "Ending matching buy->sell because trading profit margin is too low (%.4f%%, wants >= %.4f%%%)", |
|
132
|
|
|
|
|
|
|
$trading_profit_margin, $min_net_profit_margin; |
|
133
|
1
|
|
|
|
|
7
|
last CREATE; |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
my $order_pair = { |
|
137
|
|
|
|
|
|
|
sell => { |
|
138
|
|
|
|
|
|
|
exchange => $sell->{exchange}, |
|
139
|
|
|
|
|
|
|
pair => "$base_currency/$sell->{quote_currency}", |
|
140
|
|
|
|
|
|
|
gross_price_orig => $sell->{gross_price_orig}, |
|
141
|
|
|
|
|
|
|
gross_price => $sell->{gross_price}, |
|
142
|
|
|
|
|
|
|
net_price_orig => $sell->{net_price_orig}, |
|
143
|
|
|
|
|
|
|
net_price => $sell->{net_price}, |
|
144
|
|
|
|
|
|
|
}, |
|
145
|
|
|
|
|
|
|
buy => { |
|
146
|
|
|
|
|
|
|
exchange => $buy->{exchange}, |
|
147
|
|
|
|
|
|
|
pair => "$base_currency/$buy->{quote_currency}", |
|
148
|
|
|
|
|
|
|
gross_price_orig => $buy->{gross_price_orig}, |
|
149
|
|
|
|
|
|
|
gross_price => $buy->{gross_price}, |
|
150
|
|
|
|
|
|
|
net_price_orig => $buy->{net_price_orig}, |
|
151
|
|
|
|
|
|
|
net_price => $buy->{net_price}, |
|
152
|
|
|
|
|
|
|
}, |
|
153
|
35
|
|
|
|
|
339
|
gross_profit_margin => $gross_profit_margin, |
|
154
|
|
|
|
|
|
|
trading_profit_margin => $trading_profit_margin, |
|
155
|
|
|
|
|
|
|
}; |
|
156
|
|
|
|
|
|
|
|
|
157
|
35
|
100
|
|
|
|
83
|
if ($account_balances) { |
|
158
|
8
|
|
|
|
|
27
|
$order_pair->{sell}{account} = $account_balances->{ $sell->{exchange} }{$base_currency}[0]{account}; |
|
159
|
8
|
|
|
|
|
38
|
$order_pair->{buy}{account} = $account_balances->{ $buy ->{exchange} }{$buy->{quote_currency}}[0]{account}; |
|
160
|
|
|
|
|
|
|
} |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
# limit maximum size of order |
|
163
|
|
|
|
|
|
|
my @sizes = ( |
|
164
|
|
|
|
|
|
|
{which => 'buy order' , size => $sell->{base_size}}, |
|
165
|
|
|
|
|
|
|
{which => 'sell order', size => $buy ->{base_size}}, |
|
166
|
35
|
|
|
|
|
146
|
); |
|
167
|
35
|
100
|
|
|
|
79
|
if (defined $max_order_quote_size) { |
|
168
|
|
|
|
|
|
|
push @sizes, ( |
|
169
|
7
|
|
|
|
|
30
|
{which => 'max_order_quote_size', size => $max_order_quote_size / max($sell->{gross_price}, $buy->{gross_price})}, |
|
170
|
|
|
|
|
|
|
); |
|
171
|
|
|
|
|
|
|
} |
|
172
|
35
|
100
|
|
|
|
72
|
if ($account_balances) { |
|
173
|
|
|
|
|
|
|
push @sizes, ( |
|
174
|
|
|
|
|
|
|
{ |
|
175
|
|
|
|
|
|
|
which => 'sell exchange balance', |
|
176
|
|
|
|
|
|
|
size => $account_balances->{ $sell->{exchange} }{$base_currency}[0]{available}, |
|
177
|
|
|
|
|
|
|
}, |
|
178
|
|
|
|
|
|
|
{ |
|
179
|
|
|
|
|
|
|
which => 'buy exchange balance', |
|
180
|
|
|
|
|
|
|
size => $account_balances->{ $buy ->{exchange} }{$buy->{quote_currency}}[0]{available} |
|
181
|
|
|
|
|
|
|
/ $buy->{gross_price_orig}, |
|
182
|
|
|
|
|
|
|
}, |
|
183
|
8
|
|
|
|
|
48
|
); |
|
184
|
|
|
|
|
|
|
} |
|
185
|
35
|
|
|
|
|
105
|
@sizes = sort { $a->{size} <=> $b->{size} } @sizes; |
|
|
76
|
|
|
|
|
176
|
|
|
186
|
35
|
|
|
|
|
67
|
my $order_size = $sizes[0]{size}; |
|
187
|
|
|
|
|
|
|
|
|
188
|
35
|
|
|
|
|
56
|
$order_pair->{base_size} = $order_size; |
|
189
|
|
|
|
|
|
|
$order_pair->{gross_profit} = $order_size * |
|
190
|
35
|
|
|
|
|
73
|
($order_pair->{sell}{gross_price} - $order_pair->{buy}{gross_price}); |
|
191
|
|
|
|
|
|
|
$order_pair->{trading_profit} = $order_size * |
|
192
|
35
|
|
|
|
|
73
|
($order_pair->{sell}{net_price} - $order_pair->{buy}{net_price}); |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
UPDATE_INVENTORY_BALANCES: |
|
195
|
35
|
|
|
|
|
83
|
for my $i (0..$#sizes) { |
|
196
|
93
|
|
|
|
|
150
|
my $size = $sizes[$i]{size}; |
|
197
|
93
|
|
|
|
|
137
|
my $which = $sizes[$i]{which}; |
|
198
|
93
|
|
|
|
|
162
|
my $used_up = $size - $order_size <= 1e-8; |
|
199
|
93
|
100
|
|
|
|
205
|
if ($which eq 'buy order') { |
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
200
|
35
|
100
|
|
|
|
67
|
if ($used_up) { |
|
201
|
11
|
|
|
|
|
26
|
splice @$all_buy_orders, $sell_index, 1; |
|
202
|
|
|
|
|
|
|
} else { |
|
203
|
24
|
|
|
|
|
52
|
$all_buy_orders->[$sell_index]{base_size} -= $order_size; |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
} elsif ($which eq 'sell order') { |
|
206
|
35
|
100
|
|
|
|
56
|
if ($used_up) { |
|
207
|
13
|
|
|
|
|
36
|
splice @$all_sell_orders, $buy_index, 1; |
|
208
|
|
|
|
|
|
|
} else { |
|
209
|
22
|
|
|
|
|
51
|
$all_sell_orders->[$buy_index]{base_size} -= $order_size; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
} elsif ($which eq 'sell exchange balance') { |
|
212
|
8
|
100
|
|
|
|
18
|
if ($used_up) { |
|
213
|
5
|
|
|
|
|
8
|
shift @{ $account_balances->{ $sell->{exchange} }{$base_currency} }; |
|
|
5
|
|
|
|
|
20
|
|
|
214
|
|
|
|
|
|
|
} else { |
|
215
|
|
|
|
|
|
|
$account_balances->{ $sell->{exchange} }{$base_currency}[0]{available} -= |
|
216
|
3
|
|
|
|
|
9
|
$order_size; |
|
217
|
|
|
|
|
|
|
} |
|
218
|
|
|
|
|
|
|
} elsif ($which eq 'buy exchange balance') { |
|
219
|
8
|
|
|
|
|
13
|
my $c = $buy->{quote_currency}; |
|
220
|
8
|
100
|
|
|
|
16
|
if ($used_up) { |
|
221
|
2
|
|
|
|
|
4
|
shift @{ $account_balances->{ $buy->{exchange} }{$c} }; |
|
|
2
|
|
|
|
|
8
|
|
|
222
|
|
|
|
|
|
|
} else { |
|
223
|
|
|
|
|
|
|
$account_balances->{ $buy->{exchange} }{$c}[0]{available} -= |
|
224
|
6
|
|
|
|
|
19
|
$order_size * $buy->{gross_price_orig}; |
|
225
|
|
|
|
|
|
|
} |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
} # UPDATE_INVENTORY_BALANCES |
|
228
|
|
|
|
|
|
|
|
|
229
|
35
|
100
|
|
|
|
72
|
if ($account_balances) { |
|
230
|
8
|
|
|
|
|
24
|
App::cryp::arbit::_sort_account_balances($account_balances); |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
CHECK_MINIMUM_BUY_SIZE: |
|
234
|
|
|
|
|
|
|
{ |
|
235
|
35
|
100
|
|
|
|
54
|
last unless $exchange_pairs; |
|
|
35
|
|
|
|
|
69
|
|
|
236
|
9
|
|
|
|
|
18
|
my $pair_recs = $exchange_pairs->{ $buy->{exchange} }; |
|
237
|
9
|
100
|
|
|
|
22
|
last unless $pair_recs; |
|
238
|
4
|
|
|
|
|
7
|
my $pair_rec; |
|
239
|
4
|
|
|
|
|
9
|
for (@$pair_recs) { |
|
240
|
4
|
50
|
|
|
|
13
|
if ($_->{base_currency} eq $base_currency) { |
|
241
|
4
|
|
|
|
|
8
|
$pair_rec = $_; last; |
|
|
4
|
|
|
|
|
32
|
|
|
242
|
|
|
|
|
|
|
} |
|
243
|
|
|
|
|
|
|
} |
|
244
|
4
|
50
|
|
|
|
12
|
last unless $pair_rec; |
|
245
|
4
|
100
|
100
|
|
|
18
|
if (defined($pair_rec->{min_base_size}) && $order_pair->{base_size} < $pair_rec->{min_base_size}) { |
|
246
|
|
|
|
|
|
|
log_trace "buy order base size is too small (%.4f < %.4f), skipping this order pair: %s", |
|
247
|
1
|
|
|
|
|
10
|
$order_pair->{base_size}, $pair_rec->{min_base_size}, $order_pair; |
|
248
|
1
|
|
|
|
|
9
|
next CREATE; |
|
249
|
|
|
|
|
|
|
} |
|
250
|
3
|
|
|
|
|
9
|
my $quote_size = $order_pair->{base_size}*$buy->{gross_price_orig}; |
|
251
|
3
|
100
|
100
|
|
|
17
|
if (defined($pair_rec->{min_quote_size}) && $quote_size < $pair_rec->{min_quote_size}) { |
|
252
|
|
|
|
|
|
|
log_trace "buy order quote size is too small (%.4f < %.4f), skipping this order pair: %s", |
|
253
|
1
|
|
|
|
|
6
|
$quote_size, $pair_rec->{min_quote_size}, $order_pair; |
|
254
|
1
|
|
|
|
|
7
|
next CREATE; |
|
255
|
|
|
|
|
|
|
} |
|
256
|
|
|
|
|
|
|
} # CHECK_MINIMUM_BUY_SIZE |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
CHECK_MINIMUM_SELL_SIZE: |
|
259
|
|
|
|
|
|
|
{ |
|
260
|
33
|
100
|
|
|
|
41
|
last unless $exchange_pairs; |
|
|
33
|
|
|
|
|
66
|
|
|
261
|
7
|
|
|
|
|
14
|
my $pair_recs = $exchange_pairs->{ $sell->{exchange} }; |
|
262
|
7
|
100
|
|
|
|
20
|
last unless $pair_recs; |
|
263
|
5
|
|
|
|
|
7
|
my $pair_rec; |
|
264
|
5
|
|
|
|
|
11
|
for (@$pair_recs) { |
|
265
|
5
|
50
|
|
|
|
17
|
if ($_->{base_currency} eq $base_currency) { |
|
266
|
5
|
|
|
|
|
9
|
$pair_rec = $_; last; |
|
|
5
|
|
|
|
|
8
|
|
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
} |
|
269
|
5
|
50
|
|
|
|
12
|
last unless $pair_rec; |
|
270
|
5
|
100
|
100
|
|
|
21
|
if (defined $pair_rec->{min_base_size} && $order_pair->{base_size} < $pair_rec->{min_base_size}) { |
|
271
|
|
|
|
|
|
|
log_trace "sell order base size is too small (%.4f < %.4f), skipping this order pair: %s", |
|
272
|
1
|
|
|
|
|
6
|
$order_pair->{base_size}, $pair_rec->{min_base_size}, $order_pair; |
|
273
|
1
|
|
|
|
|
8
|
next CREATE; |
|
274
|
|
|
|
|
|
|
} |
|
275
|
4
|
|
|
|
|
10
|
my $quote_size = $order_pair->{base_size}*$sell->{gross_price_orig}; |
|
276
|
4
|
100
|
100
|
|
|
19
|
if (defined $pair_rec->{min_quote_size} && $quote_size < $pair_rec->{min_quote_size}) { |
|
277
|
|
|
|
|
|
|
log_trace "sell order quote size is too small (%.4f < %.4f), skipping this order pair: %s", |
|
278
|
2
|
|
|
|
|
16
|
$quote_size, $pair_rec->{min_quote_size}, $order_pair; |
|
279
|
2
|
|
|
|
|
15
|
next CREATE; |
|
280
|
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
} # CHECK_MINIMUM_SELL_SIZE |
|
282
|
|
|
|
|
|
|
|
|
283
|
30
|
|
|
|
|
108
|
push @order_pairs, $order_pair; |
|
284
|
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
} # CREATE |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
ADJUST_FOREX_SPREAD: |
|
288
|
|
|
|
|
|
|
{ |
|
289
|
17
|
|
|
|
|
30
|
my @tmp = @order_pairs; |
|
|
17
|
|
|
|
|
35
|
|
|
290
|
17
|
|
|
|
|
31
|
@order_pairs = (); |
|
291
|
17
|
|
|
|
|
26
|
my $i = 0; |
|
292
|
|
|
|
|
|
|
ORDER_PAIR: |
|
293
|
17
|
|
|
|
|
30
|
for my $op (@tmp) { |
|
294
|
30
|
|
|
|
|
45
|
$i++; |
|
295
|
30
|
|
|
|
|
157
|
my ($bcur) = $op->{buy}{pair} =~ m!/(.+)!; |
|
296
|
30
|
|
|
|
|
106
|
my ($scur) = $op->{sell}{pair} =~ m!/(.+)!; |
|
297
|
|
|
|
|
|
|
|
|
298
|
30
|
50
|
|
|
|
103
|
if ($bcur eq $scur) { |
|
299
|
|
|
|
|
|
|
# there is no forex spread |
|
300
|
0
|
|
|
|
|
0
|
$op->{net_profit_margin} = $op->{trading_profit_margin}; |
|
301
|
0
|
|
|
|
|
0
|
$op->{net_profit} = $op->{trading_profit}; |
|
302
|
0
|
|
|
|
|
0
|
goto ADD; |
|
303
|
|
|
|
|
|
|
} |
|
304
|
|
|
|
|
|
|
|
|
305
|
30
|
|
|
|
|
42
|
my $spread; |
|
306
|
30
|
50
|
|
|
|
86
|
$spread = $forex_spreads->{"$bcur/$scur"} if $forex_spreads; |
|
307
|
|
|
|
|
|
|
|
|
308
|
30
|
50
|
|
|
|
58
|
unless (defined $spread) { |
|
309
|
|
|
|
|
|
|
log_warn "Order pair #%d (buy %s - sell %s): didn't find ". |
|
310
|
|
|
|
|
|
|
"forex spread for %s/%s, not adjusting for forex spread", |
|
311
|
0
|
|
|
|
|
0
|
$i, $op->{buy}{pair}, $op->{sell}{pair}, $bcur, $scur; |
|
312
|
0
|
|
|
|
|
0
|
next ORDER_PAIR; |
|
313
|
|
|
|
|
|
|
} |
|
314
|
|
|
|
|
|
|
log_trace "Order pair #%d (buy %s - sell %s, trading profit margin %.4f%%): adjusting ". |
|
315
|
|
|
|
|
|
|
"with %s/%s forex spread %.4f%%", |
|
316
|
30
|
|
|
|
|
181
|
$i, $op->{buy}{pair}, $op->{sell}{pair}, $op->{trading_profit_margin}, $bcur, $scur, $spread; |
|
317
|
30
|
|
|
|
|
127
|
$op->{forex_spread} = $spread; |
|
318
|
30
|
|
|
|
|
63
|
$op->{net_profit_margin} = $op->{trading_profit_margin} - $spread; |
|
319
|
30
|
|
|
|
|
58
|
$op->{net_profit} = $op->{trading_profit} * $op->{net_profit_margin} / $op->{trading_profit_margin}; |
|
320
|
30
|
100
|
|
|
|
65
|
if ($op->{net_profit_margin} < $min_net_profit_margin) { |
|
321
|
|
|
|
|
|
|
log_trace "Order pair #%d: After forex spread adjustment, net profit margin is too small (%.4f%%, wants >= %.4f%%), skipping this order pair", |
|
322
|
1
|
|
|
|
|
4
|
$i, $op->{net_profit_margin}, $min_net_profit_margin; |
|
323
|
1
|
|
|
|
|
6
|
next ORDER_PAIR; |
|
324
|
|
|
|
|
|
|
} |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
ADD: |
|
327
|
29
|
|
|
|
|
67
|
push @order_pairs, $op; |
|
328
|
|
|
|
|
|
|
} |
|
329
|
|
|
|
|
|
|
} # ADJUST_FOREX_SPREAD |
|
330
|
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
# re-sort |
|
332
|
17
|
|
|
|
|
36
|
@order_pairs = sort { $b->{net_profit_margin} <=> $a->{net_profit_margin} } @order_pairs; |
|
|
17
|
|
|
|
|
38
|
|
|
333
|
|
|
|
|
|
|
|
|
334
|
17
|
|
|
|
|
95
|
(\@order_pairs, $opportunity); |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub calculate_order_pairs { |
|
338
|
0
|
|
|
0
|
0
|
|
my ($pkg, %args) = @_; |
|
339
|
|
|
|
|
|
|
|
|
340
|
0
|
|
|
|
|
|
my $r = $args{r}; |
|
341
|
0
|
|
|
|
|
|
my $dbh = $r->{_stash}{dbh}; |
|
342
|
|
|
|
|
|
|
|
|
343
|
0
|
|
|
|
|
|
my @order_pairs; |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
GET_ACCOUNT_BALANCES: |
|
346
|
|
|
|
|
|
|
{ |
|
347
|
0
|
0
|
|
|
|
|
last if $r->{args}{ignore_balance}; |
|
|
0
|
|
|
|
|
|
|
|
348
|
0
|
|
|
|
|
|
App::cryp::arbit::_get_account_balances($r, 'no-cache'); |
|
349
|
|
|
|
|
|
|
} # GET_ACCOUNT_BALANCES |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
GET_FOREX_RATES: |
|
352
|
|
|
|
|
|
|
{ |
|
353
|
|
|
|
|
|
|
# get foreign fiat currency vs USD exchange rate. we'll use the average |
|
354
|
|
|
|
|
|
|
# rate for this first. but we'll adjust the price difference percentage |
|
355
|
|
|
|
|
|
|
# with the buy-sell spread later. |
|
356
|
|
|
|
|
|
|
|
|
357
|
0
|
|
|
|
|
|
my %seen; |
|
|
0
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
|
|
359
|
0
|
|
|
|
|
|
$r->{_stash}{forex_rates} = {}; |
|
360
|
|
|
|
|
|
|
|
|
361
|
0
|
|
|
|
|
|
for my $cur (@{ $r->{_stash}{quote_currencies} }) { |
|
|
0
|
|
|
|
|
|
|
|
362
|
0
|
0
|
|
|
|
|
next unless App::cryp::arbit::_is_fiat($cur); |
|
363
|
0
|
0
|
|
|
|
|
next if $cur eq 'USD'; |
|
364
|
0
|
0
|
|
|
|
|
next if $seen{$cur}++; |
|
365
|
|
|
|
|
|
|
|
|
366
|
0
|
|
|
|
|
|
require Finance::Currency::FiatX; |
|
367
|
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
|
my $fxres_low = Finance::Currency::FiatX::get_spot_rate( |
|
369
|
|
|
|
|
|
|
dbh => $dbh, from => $cur, to => 'USD', type => 'buy', source => ':lowest'); |
|
370
|
0
|
0
|
|
|
|
|
if ($fxres_low->[0] != 200) { |
|
371
|
0
|
|
|
|
|
|
return [412, "Couldn't get conversion rate (lowest buy) from ". |
|
372
|
|
|
|
|
|
|
"$cur to USD: $fxres_low->[0] - $fxres_low->[1]"]; |
|
373
|
|
|
|
|
|
|
} |
|
374
|
|
|
|
|
|
|
|
|
375
|
0
|
|
|
|
|
|
my $fxres_high = Finance::Currency::FiatX::get_spot_rate( |
|
376
|
|
|
|
|
|
|
dbh => $dbh, from => $cur, to => 'USD', type => 'sell', source => ':highest'); |
|
377
|
0
|
0
|
|
|
|
|
if ($fxres_high->[0] != 200) { |
|
378
|
0
|
|
|
|
|
|
return [412, "Couldn't get conversion rate (highest sell) ". |
|
379
|
|
|
|
|
|
|
"from $cur to USD: $fxres_high->[0] - ". |
|
380
|
|
|
|
|
|
|
"$fxres_high->[1]"]; |
|
381
|
|
|
|
|
|
|
} |
|
382
|
|
|
|
|
|
|
|
|
383
|
0
|
|
|
|
|
|
my $fxrate_avg = ($fxres_low->[2]{rate} + $fxres_high->[2]{rate})/2; |
|
384
|
0
|
|
|
|
|
|
$r->{_stash}{forex_rates}{"$cur/USD"} = $fxrate_avg; |
|
385
|
|
|
|
|
|
|
} |
|
386
|
|
|
|
|
|
|
} # GET_FOREX_RATES |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
GET_FOREX_SPREADS: |
|
389
|
|
|
|
|
|
|
{ |
|
390
|
|
|
|
|
|
|
# when we arbitrage using two different fiat currencies, e.g. BTC/USD |
|
391
|
|
|
|
|
|
|
# and BTC/IDR, we want to take into account the USD/IDR buy-sell spread |
|
392
|
|
|
|
|
|
|
# (the "forex spread") and subtract this from the price difference |
|
393
|
|
|
|
|
|
|
# percentage to be safer. |
|
394
|
|
|
|
|
|
|
|
|
395
|
0
|
|
|
|
|
|
$r->{_stash}{forex_spreads} = {}; |
|
|
0
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
|
|
397
|
0
|
|
|
|
|
|
my @curs; |
|
398
|
0
|
|
|
|
|
|
for my $cur (@{ $r->{_stash}{quote_currencies} }) { |
|
|
0
|
|
|
|
|
|
|
|
399
|
0
|
0
|
|
|
|
|
next unless App::cryp::arbit::_is_fiat($cur); |
|
400
|
0
|
0
|
|
|
|
|
push @curs, $cur unless grep { $cur eq $_ } @curs; |
|
|
0
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
} |
|
402
|
0
|
0
|
|
|
|
|
last unless @curs; |
|
403
|
|
|
|
|
|
|
|
|
404
|
0
|
|
|
|
|
|
require Finance::Currency::FiatX; |
|
405
|
|
|
|
|
|
|
|
|
406
|
0
|
|
|
|
|
|
for my $cur1 (@curs) { |
|
407
|
0
|
|
|
|
|
|
for my $cur2 (@curs) { |
|
408
|
0
|
0
|
|
|
|
|
next if $cur1 eq $cur2; |
|
409
|
|
|
|
|
|
|
|
|
410
|
0
|
|
|
|
|
|
my $fxres_low = Finance::Currency::FiatX::get_spot_rate( |
|
411
|
|
|
|
|
|
|
dbh => $dbh, from => $cur1, to => $cur2, type => 'buy', source => ':lowest'); |
|
412
|
0
|
0
|
|
|
|
|
if ($fxres_low->[0] != 200) { |
|
413
|
0
|
|
|
|
|
|
return [412, "Couldn't get conversion rate (lowest buy) for ". |
|
414
|
|
|
|
|
|
|
"$cur1/$cur2: $fxres_low->[0] - $fxres_low->[1]"]; |
|
415
|
|
|
|
|
|
|
} |
|
416
|
|
|
|
|
|
|
|
|
417
|
0
|
|
|
|
|
|
my $fxres_high = Finance::Currency::FiatX::get_spot_rate( |
|
418
|
|
|
|
|
|
|
dbh => $dbh, from => $cur1, to => $cur2, type => 'sell', source => ':highest'); |
|
419
|
0
|
0
|
|
|
|
|
if ($fxres_high->[0] != 200) { |
|
420
|
0
|
|
|
|
|
|
return [412, "Couldn't get conversion rate (highest sell) ". |
|
421
|
|
|
|
|
|
|
"for $cur1/$cur2: $fxres_high->[0] - ". |
|
422
|
|
|
|
|
|
|
"$fxres_high->[1]"]; |
|
423
|
|
|
|
|
|
|
} |
|
424
|
|
|
|
|
|
|
|
|
425
|
0
|
|
|
|
|
|
my $r1 = $fxres_low->[2]{rate}; |
|
426
|
0
|
|
|
|
|
|
my $r2 = $fxres_high->[2]{rate}; |
|
427
|
0
|
0
|
|
|
|
|
my $spread = $r1 > $r2 ? ($r1-$r2)/$r2*100 : ($r2-$r1)/$r1*100; |
|
428
|
0
|
|
|
|
|
|
$r->{_stash}{forex_spreads}{"$cur1/$cur2"} = abs $spread; |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
} |
|
431
|
|
|
|
|
|
|
} # GET_FOREX_SPREADS |
|
432
|
|
|
|
|
|
|
|
|
433
|
0
|
|
|
|
|
|
my %exchanges_for; # key="base currency"/"quote cryptocurrency or ':fiat'", value => [exchange, ...] |
|
434
|
|
|
|
|
|
|
my %fiat_for; # key=exchange safename, val=[fiat currency, ...] |
|
435
|
0
|
|
|
|
|
|
my %pairs_for; # key=exchange safename, val=[pair, ...] |
|
436
|
|
|
|
|
|
|
DETERMINE_SETS: |
|
437
|
0
|
|
|
|
|
|
for my $exchange (sort keys %{ $r->{_stash}{exchange_clients} }) { |
|
|
0
|
|
|
|
|
|
|
|
438
|
0
|
|
|
|
|
|
my $pair_recs = $r->{_stash}{exchange_pairs}{$exchange}; |
|
439
|
0
|
|
|
|
|
|
for my $pair_rec (@$pair_recs) { |
|
440
|
0
|
|
|
|
|
|
my $pair = $pair_rec->{name}; |
|
441
|
0
|
|
|
|
|
|
my ($basecur, $quotecur) = $pair =~ m!(.+)/(.+)!; |
|
442
|
0
|
0
|
|
|
|
|
next unless grep { $_ eq $basecur } @{ $r->{_stash}{base_currencies} }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
443
|
0
|
0
|
|
|
|
|
next unless grep { $_ eq $quotecur } @{ $r->{_stash}{quote_currencies} }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
|
|
445
|
0
|
|
|
|
|
|
my $key; |
|
446
|
0
|
0
|
|
|
|
|
if (App::cryp::arbit::_is_fiat($quotecur)) { |
|
447
|
0
|
|
|
|
|
|
$key = "$basecur/:fiat"; |
|
448
|
0
|
|
0
|
|
|
|
$fiat_for{$exchange} //= []; |
|
449
|
0
|
|
|
|
|
|
push @{ $fiat_for{$exchange} }, $quotecur |
|
450
|
0
|
0
|
|
|
|
|
unless grep { $_ eq $quotecur } @{ $fiat_for{$exchange} }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
} else { |
|
452
|
0
|
|
|
|
|
|
$key = "$basecur/$quotecur"; |
|
453
|
|
|
|
|
|
|
} |
|
454
|
0
|
|
0
|
|
|
|
$exchanges_for{$key} //= []; |
|
455
|
0
|
|
|
|
|
|
push @{ $exchanges_for{$key} }, $exchange; |
|
|
0
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
|
|
457
|
0
|
|
0
|
|
|
|
$pairs_for{$exchange} //= []; |
|
458
|
0
|
|
|
|
|
|
push @{ $pairs_for{$exchange} }, $pair |
|
459
|
0
|
0
|
|
|
|
|
unless grep { $_ eq $pair } @{ $pairs_for{$exchange} }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
} # DETERMINE_SETS |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
SET: |
|
464
|
0
|
|
|
|
|
|
for my $set (shuffle keys %exchanges_for) { |
|
465
|
0
|
|
|
|
|
|
my ($base_currency, $quote_currency0) = $set =~ m!(.+)/(.+)!; |
|
466
|
|
|
|
|
|
|
|
|
467
|
0
|
|
|
|
|
|
my %sell_orders; # key = exchange safename |
|
468
|
|
|
|
|
|
|
my %buy_orders ; # key = exchange safename |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
# the final merged order book. each entry will be a hashref containing |
|
471
|
|
|
|
|
|
|
# these keys: |
|
472
|
|
|
|
|
|
|
# |
|
473
|
|
|
|
|
|
|
# - currency (the base/target currency to arbitrage) |
|
474
|
|
|
|
|
|
|
# |
|
475
|
|
|
|
|
|
|
# - gross_price_orig (ask/bid price in exchange's original quote |
|
476
|
|
|
|
|
|
|
# currency) |
|
477
|
|
|
|
|
|
|
# |
|
478
|
|
|
|
|
|
|
# - gross_price (like gross_price_orig, but price will be converted to |
|
479
|
|
|
|
|
|
|
# USD if quote currency is fiat) |
|
480
|
|
|
|
|
|
|
# |
|
481
|
|
|
|
|
|
|
# - net_price_orig (net price after adding [if sell order, because we'll |
|
482
|
|
|
|
|
|
|
# be buying these] or subtracting [if buy order, because we'll be |
|
483
|
|
|
|
|
|
|
# selling these] trading fee from the original ask/bid price. in |
|
484
|
|
|
|
|
|
|
# exchange's original quote currency) |
|
485
|
|
|
|
|
|
|
# |
|
486
|
|
|
|
|
|
|
# - net_price (like net_price_orig, but price will be converted to USD |
|
487
|
|
|
|
|
|
|
# if quote currency is fiat) |
|
488
|
|
|
|
|
|
|
# |
|
489
|
|
|
|
|
|
|
# - exchange (exchange safename) |
|
490
|
|
|
|
|
|
|
|
|
491
|
0
|
|
|
|
|
|
my @all_buy_orders; |
|
492
|
0
|
|
|
|
|
|
my @all_sell_orders; |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# produce final merged order book. |
|
495
|
|
|
|
|
|
|
EXCHANGE: |
|
496
|
0
|
|
|
|
|
|
for my $exchange (sort keys %{ $r->{_stash}{exchange_clients} }) { |
|
|
0
|
|
|
|
|
|
|
|
497
|
0
|
|
|
|
|
|
my $eid = App::cryp::arbit::_get_exchange_id($r, $exchange); |
|
498
|
0
|
|
|
|
|
|
my $clients = $r->{_stash}{exchange_clients}{$exchange}; |
|
499
|
0
|
|
|
|
|
|
my $client = $clients->{ (sort keys %$clients)[0] }; |
|
500
|
|
|
|
|
|
|
|
|
501
|
0
|
|
|
|
|
|
my @pairs; |
|
502
|
0
|
0
|
|
|
|
|
if ($quote_currency0 eq ':fiat') { |
|
503
|
0
|
|
|
|
|
|
push @pairs, map { "$base_currency/$_" } @{ $fiat_for{$exchange} }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
} else { |
|
505
|
0
|
|
|
|
|
|
push @pairs, $set; |
|
506
|
|
|
|
|
|
|
} |
|
507
|
|
|
|
|
|
|
|
|
508
|
|
|
|
|
|
|
PAIR: |
|
509
|
0
|
|
|
|
|
|
for my $pair (@pairs) { |
|
510
|
0
|
|
|
|
|
|
my ($basecur, $quotecur) = split m!/!, $pair; |
|
511
|
0
|
0
|
|
|
|
|
next unless grep { $_ eq $pair } @{ $pairs_for{$exchange} }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
|
|
513
|
0
|
|
|
|
|
|
my $time = time(); |
|
514
|
0
|
|
|
|
|
|
log_debug "Getting orderbook %s on %s ...", $pair, $exchange; |
|
515
|
0
|
|
|
|
|
|
my $res = $client->get_order_book(pair => $pair); |
|
516
|
0
|
0
|
|
|
|
|
unless ($res->[0] == 200) { |
|
517
|
0
|
|
|
|
|
|
log_error "Couldn't get orderbook %s on %s: %s, skipping this pair", |
|
518
|
|
|
|
|
|
|
$pair, $exchange, $res; |
|
519
|
0
|
|
|
|
|
|
next PAIR; |
|
520
|
|
|
|
|
|
|
} |
|
521
|
|
|
|
|
|
|
#log_trace "orderbook %s on %s: %s", $pair, $exchange, $res->[2]; # too much info to log |
|
522
|
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# sanity checks |
|
524
|
0
|
0
|
|
|
|
|
unless (@{ $res->[2]{sell} }) { |
|
|
0
|
|
|
|
|
|
|
|
525
|
0
|
|
|
|
|
|
log_warn "No sell orders for %s on %s, skipping this pair", |
|
526
|
|
|
|
|
|
|
$pair, $exchange; |
|
527
|
0
|
|
|
|
|
|
next PAIR; |
|
528
|
|
|
|
|
|
|
} |
|
529
|
0
|
0
|
|
|
|
|
unless (@{ $res->[2]{buy} }) { |
|
|
0
|
|
|
|
|
|
|
|
530
|
0
|
|
|
|
|
|
log_warn "No buy orders for %s on %s, skipping this pair", |
|
531
|
|
|
|
|
|
|
$pair, $exchange; |
|
532
|
0
|
|
|
|
|
|
last PAIR; |
|
533
|
|
|
|
|
|
|
} |
|
534
|
|
|
|
|
|
|
|
|
535
|
0
|
|
|
|
|
|
my $buy_fee_pct = App::cryp::arbit::_get_trading_fee( |
|
536
|
|
|
|
|
|
|
$r, $exchange, $base_currency); |
|
537
|
0
|
|
|
|
|
|
for (@{ $res->[2]{buy} }) { |
|
|
0
|
|
|
|
|
|
|
|
538
|
0
|
|
|
|
|
|
push @{ $buy_orders{$exchange} }, { |
|
|
0
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
quote_currency => $quotecur, |
|
540
|
|
|
|
|
|
|
gross_price_orig => $_->[0], |
|
541
|
|
|
|
|
|
|
net_price_orig => $_->[0]*(1-$buy_fee_pct/100), |
|
542
|
|
|
|
|
|
|
base_size => $_->[1], |
|
543
|
|
|
|
|
|
|
}; |
|
544
|
|
|
|
|
|
|
} |
|
545
|
|
|
|
|
|
|
|
|
546
|
0
|
|
|
|
|
|
my $sell_fee_pct = App::cryp::arbit::_get_trading_fee( |
|
547
|
|
|
|
|
|
|
$r, $exchange, $base_currency); |
|
548
|
0
|
|
|
|
|
|
for (@{ $res->[2]{sell} }) { |
|
|
0
|
|
|
|
|
|
|
|
549
|
0
|
|
|
|
|
|
push @{ $sell_orders{$exchange} }, { |
|
|
0
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
quote_currency => $quotecur, |
|
551
|
|
|
|
|
|
|
gross_price_orig => $_->[0], |
|
552
|
|
|
|
|
|
|
net_price_orig => $_->[0]*(1+$sell_fee_pct/100), |
|
553
|
|
|
|
|
|
|
base_size => $_->[1], |
|
554
|
|
|
|
|
|
|
}; |
|
555
|
|
|
|
|
|
|
} |
|
556
|
|
|
|
|
|
|
|
|
557
|
0
|
0
|
0
|
|
|
|
if (!App::cryp::arbit::_is_fiat($quotecur) || $quotecur eq 'USD') { |
|
558
|
0
|
|
|
|
|
|
for (@{ $buy_orders{$exchange} }, @{ $sell_orders{$exchange} }) { |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
559
|
0
|
|
|
|
|
|
$_->{gross_price} = $_->{gross_price_orig}; |
|
560
|
0
|
|
|
|
|
|
$_->{net_price} = $_->{net_price_orig}; |
|
561
|
|
|
|
|
|
|
} |
|
562
|
|
|
|
|
|
|
$dbh->do("INSERT INTO price (time,base_currency,quote_currency,price,exchange_id,type) VALUES (?,?,?,?,?,?)", {}, |
|
563
|
0
|
|
|
|
|
|
$time, $base_currency, $quotecur, $buy_orders{$exchange}[0]{gross_price_orig}, $eid, "buy"); |
|
564
|
|
|
|
|
|
|
$dbh->do("INSERT INTO price (time,base_currency,quote_currency,price,exchange_id,type) VALUES (?,?,?,?,?,?)", {}, |
|
565
|
0
|
|
|
|
|
|
$time, $base_currency, $quotecur, $sell_orders{$exchange}[0]{gross_price_orig}, $eid, "sell"); |
|
566
|
|
|
|
|
|
|
} else { |
|
567
|
|
|
|
|
|
|
# convert fiat to USD |
|
568
|
0
|
0
|
|
|
|
|
my $fxrate = $r->{_stash}{forex_rates}{"$quotecur/USD"} |
|
569
|
|
|
|
|
|
|
or die "BUG: Didn't get forex rate for $quotecur/USD?"; |
|
570
|
|
|
|
|
|
|
|
|
571
|
0
|
|
|
|
|
|
for (@{ $buy_orders{$exchange} }) { |
|
|
0
|
|
|
|
|
|
|
|
572
|
0
|
|
|
|
|
|
$_->{gross_price} = $_->{gross_price_orig} * $fxrate; |
|
573
|
0
|
|
|
|
|
|
$_->{net_price} = $_->{net_price_orig} * $fxrate;; |
|
574
|
|
|
|
|
|
|
} |
|
575
|
|
|
|
|
|
|
|
|
576
|
0
|
|
|
|
|
|
my $fxrate_note = join( |
|
577
|
|
|
|
|
|
|
" ", |
|
578
|
|
|
|
|
|
|
sprintf("$quotecur/USD forex rate: %.8f", $fxrate), |
|
579
|
|
|
|
|
|
|
); |
|
580
|
|
|
|
|
|
|
|
|
581
|
0
|
|
|
|
|
|
for (@{ $sell_orders{$exchange} }) { |
|
|
0
|
|
|
|
|
|
|
|
582
|
0
|
|
|
|
|
|
$_->{gross_price} = $_->{gross_price_orig} * $fxrate; |
|
583
|
0
|
|
|
|
|
|
$_->{net_price} = $_->{net_price_orig} * $fxrate; |
|
584
|
|
|
|
|
|
|
} |
|
585
|
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
$dbh->do("INSERT INTO price (time,base_currency,quote_currency,price,exchange_id,type) VALUES (?,?,?,?,?,?)", {}, |
|
587
|
0
|
|
|
|
|
|
$time, $base_currency, $quotecur, $buy_orders{$exchange}[0]{gross_price_orig}, $eid, "buy"); |
|
588
|
|
|
|
|
|
|
$dbh->do("INSERT INTO price (time,base_currency,quote_currency,price,exchange_id,type) VALUES (?,?,?,?,?,?)", {}, |
|
589
|
0
|
|
|
|
|
|
$time, $base_currency, $quotecur, $sell_orders{$exchange}[0]{gross_price_orig}, $eid, "sell"); |
|
590
|
|
|
|
|
|
|
$dbh->do("INSERT INTO price (time,base_currency,quote_currency,price,exchange_id,type, note) VALUES (?,?,?,?,?,?, ?)", {}, |
|
591
|
0
|
|
|
|
|
|
$time, $base_currency, "USD", $buy_orders{$exchange}[0]{gross_price}, $eid, "buy", |
|
592
|
|
|
|
|
|
|
$fxrate_note); |
|
593
|
|
|
|
|
|
|
$dbh->do("INSERT INTO price (time,base_currency,quote_currency,price,exchange_id,type, note) VALUES (?,?,?,?,?,?, ?)", {}, |
|
594
|
0
|
|
|
|
|
|
$time, $base_currency, "USD", $sell_orders{$exchange}[0]{gross_price}, $eid, "sell", |
|
595
|
|
|
|
|
|
|
$fxrate_note); |
|
596
|
|
|
|
|
|
|
} # convert fiat currency to USD |
|
597
|
|
|
|
|
|
|
} # for pair |
|
598
|
|
|
|
|
|
|
} # for exchange |
|
599
|
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
# sanity checks |
|
601
|
0
|
0
|
|
|
|
|
if (keys(%buy_orders) < 2) { |
|
602
|
0
|
|
|
|
|
|
log_info "There are less than two exchanges that buy %s, ". |
|
603
|
|
|
|
|
|
|
"skipping this base currency"; |
|
604
|
0
|
|
|
|
|
|
next SET; |
|
605
|
|
|
|
|
|
|
} |
|
606
|
0
|
0
|
|
|
|
|
if (keys(%sell_orders) < 2) { |
|
607
|
0
|
|
|
|
|
|
log_debug "There are less than two exchanges that sell %s, skipping this base currency", |
|
608
|
|
|
|
|
|
|
$base_currency; |
|
609
|
0
|
|
|
|
|
|
next SET; |
|
610
|
|
|
|
|
|
|
} |
|
611
|
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
# merge all buys from all exchanges, sort from highest net price |
|
613
|
0
|
|
|
|
|
|
for my $exchange (keys %buy_orders) { |
|
614
|
0
|
|
|
|
|
|
for (@{ $buy_orders{$exchange} }) { |
|
|
0
|
|
|
|
|
|
|
|
615
|
0
|
|
|
|
|
|
$_->{exchange} = $exchange; |
|
616
|
0
|
|
|
|
|
|
push @all_buy_orders, $_; |
|
617
|
|
|
|
|
|
|
} |
|
618
|
|
|
|
|
|
|
} |
|
619
|
0
|
|
|
|
|
|
@all_buy_orders = sort { $b->{net_price} <=> $a->{net_price} } |
|
|
0
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
@all_buy_orders; |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
# merge all sells from all exchanges, sort from lowest price |
|
623
|
0
|
|
|
|
|
|
for my $exchange (keys %sell_orders) { |
|
624
|
0
|
|
|
|
|
|
for (@{ $sell_orders{$exchange} }) { |
|
|
0
|
|
|
|
|
|
|
|
625
|
0
|
|
|
|
|
|
$_->{exchange} = $exchange; |
|
626
|
0
|
|
|
|
|
|
push @all_sell_orders, $_; |
|
627
|
|
|
|
|
|
|
} |
|
628
|
|
|
|
|
|
|
} |
|
629
|
0
|
|
|
|
|
|
@all_sell_orders = sort { $a->{net_price} <=> $b->{net_price} } |
|
|
0
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
@all_sell_orders; |
|
631
|
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
#log_trace "all_buy_orders for %s: %s", $base_currency, \@all_buy_orders; |
|
633
|
|
|
|
|
|
|
#log_trace "all_sell_orders for %s: %s", $base_currency, \@all_sell_orders; |
|
634
|
|
|
|
|
|
|
|
|
635
|
0
|
|
|
|
|
|
my $account_balances = $r->{_stash}{account_balances}; |
|
636
|
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
my ($coin_order_pairs, $opportunity) = _calculate_order_pairs_for_base_currency( |
|
638
|
|
|
|
|
|
|
base_currency => $base_currency, |
|
639
|
|
|
|
|
|
|
all_buy_orders => \@all_buy_orders, |
|
640
|
|
|
|
|
|
|
all_sell_orders => \@all_sell_orders, |
|
641
|
|
|
|
|
|
|
min_net_profit_margin => $r->{args}{min_net_profit_margin}, |
|
642
|
|
|
|
|
|
|
max_order_quote_size => $r->{args}{max_order_quote_size}, |
|
643
|
|
|
|
|
|
|
max_order_size_as_book_item_size_pct => $r->{_cryp}{arbit_strategies}{merge_order_book}{max_order_size_as_book_item_size_pct}, |
|
644
|
|
|
|
|
|
|
max_order_pairs => $r->{args}{max_order_pairs_per_round}, |
|
645
|
|
|
|
|
|
|
(account_balances => $account_balances) x !$r->{args}{ignore_balance}, |
|
646
|
|
|
|
|
|
|
min_account_balances => $r->{args}{min_account_balances}, |
|
647
|
|
|
|
|
|
|
(exchange_pairs => $r->{_stash}{exchange_pairs}) x !$r->{args}{ignore_min_order_size}, |
|
648
|
|
|
|
|
|
|
forex_spreads => $r->{_stash}{forex_spreads}, |
|
649
|
0
|
|
|
|
|
|
); |
|
650
|
0
|
|
|
|
|
|
for (@$coin_order_pairs) { |
|
651
|
0
|
|
|
|
|
|
$_->{base_currency} = $base_currency; |
|
652
|
|
|
|
|
|
|
} |
|
653
|
0
|
|
|
|
|
|
push @order_pairs, @$coin_order_pairs; |
|
654
|
0
|
0
|
|
|
|
|
if ($opportunity) { |
|
655
|
|
|
|
|
|
|
$dbh->do("INSERT INTO arbit_opportunity |
|
656
|
|
|
|
|
|
|
(time,base_currency,quote_currency,buy_exchange_id,buy_price,sell_exchange_id,sell_price,gross_profit_margin,trading_profit_margin) VALUES |
|
657
|
|
|
|
|
|
|
(? ,? ,? ,? ,? ,? ,? ,? ,? )", {}, |
|
658
|
|
|
|
|
|
|
$opportunity->{time}, |
|
659
|
|
|
|
|
|
|
$opportunity->{base_currency}, |
|
660
|
|
|
|
|
|
|
$opportunity->{quote_currency}, |
|
661
|
|
|
|
|
|
|
App::cryp::arbit::_get_exchange_id($r, $opportunity->{buy_exchange}), |
|
662
|
|
|
|
|
|
|
$opportunity->{buy_price}, |
|
663
|
|
|
|
|
|
|
App::cryp::arbit::_get_exchange_id($r, $opportunity->{sell_exchange}), |
|
664
|
|
|
|
|
|
|
$opportunity->{sell_price}, |
|
665
|
|
|
|
|
|
|
$opportunity->{gross_profit_margin}, |
|
666
|
|
|
|
|
|
|
$opportunity->{trading_profit_margin}, |
|
667
|
0
|
|
|
|
|
|
); |
|
668
|
|
|
|
|
|
|
} |
|
669
|
|
|
|
|
|
|
} # for set (base currency) |
|
670
|
|
|
|
|
|
|
|
|
671
|
0
|
|
|
|
|
|
[200, "OK", \@order_pairs]; |
|
672
|
|
|
|
|
|
|
} |
|
673
|
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
1; |
|
675
|
|
|
|
|
|
|
# ABSTRACT: Using merged order books for arbitration |
|
676
|
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
__END__ |
|
678
|
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
=pod |
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
=encoding UTF-8 |
|
682
|
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=head1 NAME |
|
684
|
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
App::cryp::arbit::Strategy::merge_order_book - Using merged order books for arbitration |
|
686
|
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
=head1 VERSION |
|
688
|
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
This document describes version 0.009 of App::cryp::arbit::Strategy::merge_order_book (from Perl distribution App-cryp-arbit), released on 2018-12-03. |
|
690
|
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
692
|
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
=head2 Using this strategy |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
In your F<cryp.conf>: |
|
696
|
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
[program=cryp-arbit arbit] |
|
698
|
|
|
|
|
|
|
strategy=merge-order-book |
|
699
|
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
or in your F<cryp-arbit.conf>: |
|
701
|
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
[arbit] |
|
703
|
|
|
|
|
|
|
strategy=merge-order-book |
|
704
|
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
This is actually the default strategy, so you don't have to explicitly set |
|
706
|
|
|
|
|
|
|
C<strategy> to this strategy. |
|
707
|
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
=head2 Configuration |
|
709
|
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
In your F<cryp.conf>: |
|
711
|
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
[arbit-strategy/merge-order-book] |
|
713
|
|
|
|
|
|
|
... |
|
714
|
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
716
|
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
This arbitration strategy uses information from merged order books. Below is the |
|
718
|
|
|
|
|
|
|
description of the algorithm. Suppose we are arbitraging the pair BTC/USD. |
|
719
|
|
|
|
|
|
|
I<E1>, I<E2>, ... I<En> are exchanges. I<P*> are prices. I<S*> are sizes. I<i> |
|
720
|
|
|
|
|
|
|
denotes exchange index. |
|
721
|
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
B<First step:> get order books from all of the involved exchanges, for example: |
|
723
|
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
# buy orders on E1 # sell orders on E1 |
|
725
|
|
|
|
|
|
|
price size price size |
|
726
|
|
|
|
|
|
|
----- ---- ----- ---- |
|
727
|
|
|
|
|
|
|
P1b1 S1b1 P1s1 S1s1 |
|
728
|
|
|
|
|
|
|
P1b2 S1b2 P1s2 S1s2 |
|
729
|
|
|
|
|
|
|
P1b3 S1b3 P1s3 S1s3 |
|
730
|
|
|
|
|
|
|
... ... |
|
731
|
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
# buy orders on E2 # sell orders on E2 |
|
733
|
|
|
|
|
|
|
price size price size |
|
734
|
|
|
|
|
|
|
----- ---- ----- ---- |
|
735
|
|
|
|
|
|
|
P2b1 S2b1 P2s1 S2s1 |
|
736
|
|
|
|
|
|
|
P2b2 S2b2 P2s2 S2s2 |
|
737
|
|
|
|
|
|
|
P2b3 S2b3 P2s3 S2s3 |
|
738
|
|
|
|
|
|
|
... ... |
|
739
|
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
... |
|
741
|
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
Note that buy orders are sorted from highest to lowest price (I<Pib1> > I<Pib2> |
|
743
|
|
|
|
|
|
|
> I<Pib3> > ...) while sell orders are sorted from lowest to highest price |
|
744
|
|
|
|
|
|
|
(I<Pis1> < I<Pis2> < I<Pis3> < ...). Also note that I<P1b*> < I<P1s*>, unless |
|
745
|
|
|
|
|
|
|
something weird is going on. |
|
746
|
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
B<Second step:> merge all the orders from exchanges into just two lists: buy and |
|
748
|
|
|
|
|
|
|
sell orders. Sort buy orders, as usual, from highest to lowest price. Sort sell |
|
749
|
|
|
|
|
|
|
orders, as usual, from lowest to highest. For example: |
|
750
|
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
# buy orders # sell orders |
|
752
|
|
|
|
|
|
|
price size price size |
|
753
|
|
|
|
|
|
|
----- ---- ----- ---- |
|
754
|
|
|
|
|
|
|
P1b1 S1b1 P2s1 S2s1 |
|
755
|
|
|
|
|
|
|
P2b1 S2b1 P3s1 S3s1 |
|
756
|
|
|
|
|
|
|
P2b2 S2b2 P3s2 S3s2 |
|
757
|
|
|
|
|
|
|
P1b2 S1b2 P1s1 S1s1 |
|
758
|
|
|
|
|
|
|
... |
|
759
|
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
Arbitrage can happen if we can buy cheap bitcoin and sell our expensive bitcoin. |
|
761
|
|
|
|
|
|
|
This means I<P1b1> must be I<above> I<P2s1>, because we want to buy bitcoins on |
|
762
|
|
|
|
|
|
|
I<E1> from trader that is willing to sell at I<P2s1> then sell it on I<E1> to |
|
763
|
|
|
|
|
|
|
the trader that is willing to buy the bitcoins at I<P2b1>. Pocketing the |
|
764
|
|
|
|
|
|
|
difference (minus trading fees) as profit. |
|
765
|
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
No actual bitcoins will be transferred from I<E2> to I<E1> as that would take a |
|
767
|
|
|
|
|
|
|
long time and incurs relatively high network fees. Instead, we maintain bitcoin |
|
768
|
|
|
|
|
|
|
and USD balances on each exchange to be able to buy/sell quickly. The balances |
|
769
|
|
|
|
|
|
|
serve as "working capital" or "inventory". |
|
770
|
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
The minimum net profit margin is I<min_net_profit_margin>. We create buy/sell |
|
772
|
|
|
|
|
|
|
order pairs starting from the topmost of the merged order book, until we can't |
|
773
|
|
|
|
|
|
|
get I<min_net_profit_margin> anymore. |
|
774
|
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
Then we monitor our order pairs and cancel them if they remain unfilled for a |
|
776
|
|
|
|
|
|
|
while. |
|
777
|
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
Then we retrieve order books from the exchanges and start the process again. |
|
779
|
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
=head2 Strengths |
|
781
|
|
|
|
|
|
|
|
|
782
|
|
|
|
|
|
|
Order books contain information about prices and volumes at each price level. |
|
783
|
|
|
|
|
|
|
This serves as a guide on what size our orders should be, so we do not have to |
|
784
|
|
|
|
|
|
|
explicitly set order size. This is especially useful if we are not familiar with |
|
785
|
|
|
|
|
|
|
the typical volume of the pair on an exchange. |
|
786
|
|
|
|
|
|
|
|
|
787
|
|
|
|
|
|
|
By sorting the buy and sell orders, we get maximum price difference. |
|
788
|
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
=for Pod::Coverage ^(.+)$ |
|
790
|
|
|
|
|
|
|
|
|
791
|
|
|
|
|
|
|
=head1 Weaknesses |
|
792
|
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
Order books are changing rapidly. By the time we get the order book from the |
|
794
|
|
|
|
|
|
|
exchange API, that information is already stale. In the course of milliseconds, |
|
795
|
|
|
|
|
|
|
the order book can change, sometimes significantly. So when we submit an order |
|
796
|
|
|
|
|
|
|
to buy X BTC at price P, it might not get fulfilled completely or at all because |
|
797
|
|
|
|
|
|
|
the market price has moved above P, for example. |
|
798
|
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
=head1 CONFIGURATION |
|
800
|
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=over |
|
802
|
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
=item * max_order_size_as_book_item_size_pct |
|
804
|
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
Number 0-100. Default is 100. This setting is used for more safety since order |
|
806
|
|
|
|
|
|
|
books are rapidly changing. For example, there is an item in the merged order |
|
807
|
|
|
|
|
|
|
book as follows: |
|
808
|
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
type exchange price size item# |
|
810
|
|
|
|
|
|
|
---- -------- ----- ---- ----- |
|
811
|
|
|
|
|
|
|
buy exchange1 800.1 12 B1 |
|
812
|
|
|
|
|
|
|
buy exchange1 798.1 24 B2 |
|
813
|
|
|
|
|
|
|
... |
|
814
|
|
|
|
|
|
|
sell exchange2 780.1 5 S1 |
|
815
|
|
|
|
|
|
|
sell exchange2 782.9 8 S2 |
|
816
|
|
|
|
|
|
|
... |
|
817
|
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
If `max_order_size_as_book_item_size_pct` is set to 100, then this will create |
|
819
|
|
|
|
|
|
|
order pairs as follows: |
|
820
|
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
size buy from buy price sell to sell price item# |
|
822
|
|
|
|
|
|
|
---- -------- --------- ------- ---------- ----- |
|
823
|
|
|
|
|
|
|
5 exchange2 780.1 exchange1 800.1 OP1 |
|
824
|
|
|
|
|
|
|
7 exchange2 782.9 exchange1 800.1 OP2 |
|
825
|
|
|
|
|
|
|
... |
|
826
|
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
The OP1 will use up (100%) of item #S1 from the order book, then OP2 will use up |
|
828
|
|
|
|
|
|
|
(100%) item #B1 from the order book. |
|
829
|
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
However, if `max_order_size_as_book_item_size_pct` is set to 75, then this will |
|
831
|
|
|
|
|
|
|
create order pairs as follows: |
|
832
|
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
size buy from buy price sell to sell price item# |
|
834
|
|
|
|
|
|
|
---- -------- --------- ------- ---------- ----- |
|
835
|
|
|
|
|
|
|
3.75 exchange2 780.1 exchange1 800.1 OP1 |
|
836
|
|
|
|
|
|
|
5.25 exchange2 782.9 exchange1 800.1 OP2 |
|
837
|
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
OP1 will use 75% item S1 from the order book, then the strategy will move on to |
|
839
|
|
|
|
|
|
|
the next sell order (S2). OP2 will also use only 75% of item B1 (3.75 + 5.25 = |
|
840
|
|
|
|
|
|
|
9, which is 75% of 12) before moving on to the next buy order. |
|
841
|
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
=back |
|
843
|
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
=head1 BUGS |
|
845
|
|
|
|
|
|
|
|
|
846
|
|
|
|
|
|
|
Please report all bug reports or feature requests to L<mailto:stevenharyanto@gmail.com>. |
|
847
|
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
849
|
|
|
|
|
|
|
|
|
850
|
|
|
|
|
|
|
L<App::cryp::arbit> |
|
851
|
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
Other C<App::cryp::arbit::Strategy::*> modules. |
|
853
|
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
=head1 AUTHOR |
|
855
|
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
perlancar <perlancar@cpan.org> |
|
857
|
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
859
|
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
This software is copyright (c) 2018 by perlancar@cpan.org. |
|
861
|
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or modify it under |
|
863
|
|
|
|
|
|
|
the same terms as the Perl 5 programming language system itself. |
|
864
|
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
=cut |