Sometimes I received a {:error, %Binance.InsufficientBalanceError{reason: %{code: -2010, msg: "Account has insufficient balance for requested action."}}} message from Binance API while trying your solution for trading crypto in production environment.
I am using the source code of chapter 17 and I have sufficient balance in my Binance account.
0:38:39.505 [info] Trader(1633078420000) finished trade cycle for ADAEUR
10:38:39.505 [info] ADAEUR trader finished trade - restarting
10:38:39.505 [info] Initializing new trader(1633081119505) for ADAEUR
10:38:42.780 [info] The trader(1633081119505) is placing a BUY order for ADAEUR @ 1.86600000, quantity: 12.80000000
10:49:07.846 [info] Trader's(1633081119505 ADAEUR buy order got partially filled
10:49:08.661 [info] The trader(1633081119505) is placing a SELL order for ADAEUR @ 1.87100000, quantity: 12.80000000.
10:49:09.479 [error] GenServer #PID<0.3395.0> terminating
** (MatchError) no match of right hand side value: {:error, %Binance.InsufficientBalanceError{reason: %{code: -2010, msg: "Account has insufficient balance for requested action."}}}
(naive 0.1.0) lib/naive/trader.ex:142: Naive.Trader.handle_info/2
(stdlib 3.15.2) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.15.2) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.15.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: %Core.Struct.TradeEvent{buyer_market_maker: true, buyer_order_id: 216896572, event_time: 1633081748113, event_type: "trade", price: "1.86600000", quantity: "7.00000000", seller_order_id: 216897971, symbol: "ADAEUR", trade_id: 16058384, trade_time: 1633081748112}
10:49:09.497 [error] GenServer :"Elixir.Naive.Leader-ADAEUR" terminating
** (Protocol.UndefinedError) protocol String.Chars not implemented for {{:badmatch, {:error, %Binance.InsufficientBalanceError{reason: %{code: -2010, msg: "Account has insufficient balance for requested action."}}}}, [{Naive.Trader, :handle_info, 2, [file: 'lib/naive/trader.ex', line: 142]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 695]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 771]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]} of type Tuple. This protocol is implemented for the following type(s): Postgrex.Query, Postgrex.Copy, Decimal, BitString, Atom, Integer, NaiveDateTime, DateTime, Version.Requirement, Time, List, Float, Date, URI, Version
(elixir 1.12.2) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir 1.12.2) lib/string/chars.ex:22: String.Chars.to_string/1
(naive 0.1.0) lib/naive/leader.ex:180: Naive.Leader.handle_info/2
(stdlib 3.15.2) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.15.2) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.15.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: {:DOWN, #Reference<0.3730660033.40632321.49514>, :process, #PID<0.3395.0>, {{:badmatch, {:error, %Binance.InsufficientBalanceError{reason: %{code: -2010, msg: "Account has insufficient balance for requested action."}}}}, [{Naive.Trader, :handle_info, 2, [file: 'lib/naive/trader.ex', line: 142]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 695]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 771]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}}
10:49:13.640 [info] Initializing new trader(1633081753640) for ADAEUR
10:49:18.591 [info] The trader(1633081753640) is placing a BUY order for ADAEUR @ 1.86200000, quantity: 12.80000000
11:25:11.583 [error] GenServer #PID<0.4469.0> terminating
** (MatchError) no match of right hand side value: {:error, %Binance.InsufficientBalanceError{reason: %{code: -2010, msg: "Account has insufficient balance for requested action."}}}
(naive 0.1.0) lib/naive/trader.ex:79: Naive.Trader.handle_info/2
(stdlib 3.15.2) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.15.2) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.15.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: %Core.Struct.TradeEvent{buyer_market_maker: false, buyer_order_id: 216902085, event_time: 1633083910940, event_type: "trade", price: "1.86600000", quantity: "88.80000000", seller_order_id: 216901857, symbol: "ADAEUR", trade_id: 16059034, trade_time: 1633083910940}
11:25:11.584 [error] GenServer :"Elixir.Naive.Leader-ADAEUR" terminating
** (Protocol.UndefinedError) protocol String.Chars not implemented for {{:badmatch, {:error, %Binance.InsufficientBalanceError{reason: %{code: -2010, msg: "Account has insufficient balance for requested action."}}}}, [{Naive.Trader, :handle_info, 2, [file: 'lib/naive/trader.ex', line: 79]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 695]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 771]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]} of type Tuple. This protocol is implemented for the following type(s): Postgrex.Query, Postgrex.Copy, Decimal, BitString, Atom, Integer, NaiveDateTime, DateTime, Version.Requirement, Time, List, Float, Date, URI, Version
(elixir 1.12.2) lib/string/chars.ex:3: String.Chars.impl_for!/1
(elixir 1.12.2) lib/string/chars.ex:22: String.Chars.to_string/1
(naive 0.1.0) lib/naive/leader.ex:180: Naive.Leader.handle_info/2
(stdlib 3.15.2) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.15.2) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.15.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: {:DOWN, #Reference<0.3730660033.40632322.112313>, :process, #PID<0.4469.0>, {{:badmatch, {:error, %Binance.InsufficientBalanceError{reason: %{code: -2010, msg: "Account has insufficient balance for requested action."}}}}, [{Naive.Trader, :handle_info, 2, [file: 'lib/naive/trader.ex', line: 79]}, {:gen_server, :try_dispatch, 4, [file: 'gen_server.erl', line: 695]}, {:gen_server, :handle_msg, 6, [file: 'gen_server.erl', line: 771]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}}
As you can see, I am trading successfully and then suddenly I start getting errors due to insufficient balance but when I checked the Binance app I noticed that I have enough balance for the operation.
When the error occurs in {:ok, %Binance.OrderResponse{} = order} = @binance_client.order_limit_buy(symbol, quantity, price, "GTC") call the supervisor keeps restarting a new trader and eventually the trader receives a {:ok, %Binance.OrderResponse{} = order} message and continues.
In the other case, when the error occurs in {:ok, %Binance.OrderResponse{} = order} = @binance_client.order_limit_sell(symbol, quantity, sell_price, "GTC") the supervisor restarts a new trader which expects a trade event to place a new sell order but this trade event has already broadcasted when the error firstly occur. Consequently, the trader get stuck in that state and I have to manually sell the crypto in Binance app and to restart everything.
I think this error occurs due to some synchronization problem within the Binance data warehouses, so I modified the code to acknowledge this problem.
def handle_info(
%TradeEvent{price: price},
%State{
id: id,
symbol: symbol,
budget: budget,
buy_order: nil,
buy_down_interval: buy_down_interval,
tick_size: tick_size,
step_size: step_size
} = state
) do
price = calculate_buy_price(price, buy_down_interval, tick_size)
quantity = calculate_quantity(budget, price, step_size)
@logger.info(
"The trader(#{id}) is placing a BUY order " <>
"for #{symbol} @ #{price}, quantity: #{quantity}"
)
new_state = case @binance_client.order_limit_buy(symbol, quantity, price, "GTC") do # => BEGIN UPDATE
{:ok, %Binance.OrderResponse{} = order} ->
:ok = broadcast_order(order)
new_state = %{state | buy_order: order}
@leader.notify(:trader_state_updated, new_state)
new_state
{:error, %Binance.InsufficientBalanceError{reason: reason}} ->
@logger.info(
"The trader(#{id}) recived an ERROR: " <>
"#{reason}"
)
state
end
{:noreply, new_state}
end
...
def handle_info(
%TradeEvent{
buyer_order_id: order_id
} = trade_event,
%State{
id: id,
symbol: symbol,
buy_order:
%Binance.OrderResponse{
price: buy_price,
order_id: order_id,
orig_qty: quantity,
transact_time: timestamp
} = buy_order,
profit_interval: profit_interval,
tick_size: tick_size
} = state
) do
{:ok, %Binance.Order{} = current_buy_order} =
@binance_client.get_order(
symbol,
timestamp,
order_id
)
:ok = broadcast_order(current_buy_order)
buy_order = %{buy_order | status: current_buy_order.status}
{operation_status, new_state} =
if buy_order.status == "FILLED" do
sell_price = calculate_sell_price(buy_price, profit_interval, tick_size)
@logger.info(
"The trader(#{id}) is placing a SELL order for " <>
"#{symbol} @ #{sell_price}, quantity: #{quantity}."
)
case @binance_client.order_limit_sell(symbol, quantity, sell_price, "GTC") do # => BEGIN UPDATE
{:ok, %Binance.OrderResponse{} = order} ->
:ok = broadcast_order(order)
{:ok, %{state | buy_order: buy_order, sell_order: order}}
{:error, %Binance.InsufficientBalanceError{reason: reason}} ->
@logger.info(
"The trader(#{id}) recived an ERROR: " <>
"#{reason}"
)
{:error, state}
end
else
@logger.info("Trader's(#{id}) #{symbol} buy order got partially filled")
{:ok, %{state | buy_order: buy_order}}
end
case operation_status do
:ok -> @leader.notify(:trader_state_updated, new_state)
:error -> @leader.notify(:rebroadcast_trade_event, trade_event)
end
{:noreply, new_state}
end
So, let me know your thoughts about this kind of problem and if you have another solution to solve it.