benonymus
Two lists merging/unifying
Hey, I have a list of inputs, that can be a buy or a sell.
I sort these according to being a sell or a buy and merge the ones with the same price(to be one unit with the sum of the amounts). Also I can keep them in one list with the same result.
What I want to do that with in mind of the order, I want the buys to hit out the sells, if the price is equal or lower, a transaction to happen sort of.
I can’t really wrap my head around it so far,
as I imagine I would need to go on item basis, like take the first from buy and sell, compare them → make a decision that can be transaction and update the values/remove them → or keep both of the items. But then on the next round I would need to keep the result of this in mind, and check on it first.
Any thoughts?
Sorry, for not giving context, so,
yes the other question is related, but this introduces a new problem, but that one was solved.
The problem:
I have this json:
{
"orders": [
{"command": "sell", "price": 100.003, "amount": 2.4},
{"command": "buy", "price": 90.394, "amount": 3.445},
{"command": "buy", "price": 89.394, "amount": 4.3},
{"command": "sell", "price": 100.013, "amount": 2.2},
{"command": "buy", "price": 90.15, "amount": 1.305},
{"command": "buy", "price": 90.394, "amount": 1.0},
{"command": "sell", "price": 90.394, "amount": 2.2}
]
}
and the expected output is:
{
"buy": [
{
"price": 90.394,
"volume": 2.245
},
{
"price": 90.15,
"volume": 1.305
},
{
"price": 89.394,
"volume": 4.3
},
],
"sell": [
{
"price": 100.003,
"volume": 2.4
},
{
"price": 100.013,
"volume": 2.2
}
]
}
all the sorting aside, as you see 7 goes in and 5 comes out.
the ones with the same price are added up or if there is a sell and a buy with the same price those take care of each other too.
I solved this by doing this after creating the orders:
my code here works on one list, and then i split that into the buy and sell lists for displaying, but if i split the 2 lists and then pass that list to this function we in the same spot
defp transactions(limit_orders) do
for order <- limit_orders do
Enum.reduce(
limit_orders,
order,
fn x, y ->
cond do
Decimal.cmp(x.price, y.price) == :eq and x.id != y.id and x.command == y.command ->
new_amount = Decimal.add(x.amount, y.amount)
y
|> Map.put(:amount, new_amount)
|> Map.put(:id, x.id)
Decimal.cmp(x.price, y.price) == :eq and x.command != y.command ->
case Decimal.cmp(x.amount, y.amount) do
:gt ->
new_amount = Decimal.sub(x.amount, y.amount)
Map.put(x, :amount, new_amount)
:lt ->
new_amount = Decimal.sub(y.amount, x.amount)
Map.put(y, :amount, new_amount)
:eq ->
nil
end
true ->
y
end
end
)
end
|> Enum.filter(&(!is_nil(&1)))
|> Enum.uniq_by(fn x -> x.id end)
end
The new problem:
let’s say I extend the previous order list with a few new orders:
{
"orders": [
{"command": "sell", "price": 100.003, "amount": 2.4},
{"command": "buy", "price": 90.394, "amount": 3.445},
{"command": "buy", "price": 89.394, "amount": 4.3},
{"command": "sell", "price": 100.013, "amount": 2.2},
{"command": "buy", "price": 90.15, "amount": 1.305},
{"command": "buy", "price": 90.394, "amount": 1.0},
{"command": "sell", "price": 90.394, "amount": 2.2},
new one below:
{"command": "sell", "price": 90.15, "amount": 3.4}
]
}
As you can see from the output of the previous one the the lowest buy price is higher than the lowest sell price 90.15 here. So that transaction should happen as well.
After that, we compare volume of 90.394 with amount of the sell command. volume of 90.394 is 2.245 and amount of 90.15 is 3.4. So that, we’ve amount left is 3.4 - 2.245 = 1.155 There is amount left. Then, this is partially matched! This command has to continue matching next buy price.
Continue next price, We compare 90.15 with 90.15 on Buy side. It is matched again. because the price on both side is equal. Let’s check the volume on buy side with the amount left which is 1.155 - 1.305 = -0.15. It means there is no amount left. And now volume on buy side would be 0.15.
So the expect output after this one is this:
{
"buy": [
{
"price": 90.15,
"volume": 0.15
},
{
"price": 89.394,
"volume": 4.3
},
],
"sell": [
{
"price": 100.003,
"volume": 2.4
},
{
"price": 100.013,
"volume": 2.2
}
]
}
my code in it’s current state puts out this:
{
"buy": [
{
"price": "90.394",
"volume": "2.245"
},
{
"price": "89.394",
"volume": "4.3"
}
],
"sell": [
{
"price": "90.15",
"volume": "2.095"
},
{
"price": "100.003",
"volume": "2.4"
},
{
"price": "100.013",
"volume": "2.2"
}
]
}
since it doesn’t match on higher buying price, only if it matches exactly.
I made another case where it would match if there is a higher buying price in a similar fashion to the matching price, but if i add other orders as well:
{
"orders": [
{"command": "sell", "price": 100.003, "amount": 2.4},
{"command": "buy", "price": 90.394, "amount": 3.445},
{"command": "buy", "price": 89.394, "amount": 4.3},
{"command": "sell", "price": 100.013, "amount": 2.2},
{"command": "buy", "price": 90.15, "amount": 1.305},
{"command": "buy", "price": 90.394, "amount": 1.0},
{"command": "sell", "price": 90.394, "amount": 2.2},
{"command": "sell", "price": 90.15, "amount": 3.4},
{"command": "buy", "price": 91.33, "amount": 1.8},
{"command": "buy", "price": 100.01, "amount": 4.0},
{"command": "sell", "price": 100.15, "amount": 3.8}
]
}
th3 3 bottom ones,
since my “design” doesn’t respect the order of the commands strictly put, it wont give the desired output:
{
"buy": [
{
"price": 100.01,
"volume": 1.6
},
{
"price": 91.33,
"volume": 1.8
},
{
"price": 90.15,
"volume": 0.15
},
{
"price": 89.394,
"volume": 4.3
},
],
"sell": [
{
"price": 100.013,
"volume": 2.2
},
{
"price": 100.15,
"volume": 3.8
}
]
}
because it would match on the exactly matching prices first
First Post!
ericgray
Most Liked
benwilson512
This seems like this is part of this thread: Merging maps in list based on matching value - #6 by dimitarvp ?
benwilson512
Let’s step back from the details of the grouping / buy / sell algorithm for a second and look at this more generally. @benonymus you have a clear idea in your head about what the rules are. However you were unclear about how to work with the two lists. Hopefully these responses have provided some insight into how to work with multiple lists.
What I’d recommend at this point is instead of trying to communicate the exact details of the algorithm to people here so that they can write out an exact solution, instead focus on any remaining questions you have about the Elixir mechanics of dealing with these lists, and then take a crack at it on your own. We’ll be here to review code or questions you may have.
peerreynders
I don’t recall that your initial statement mentioned that order was significant.
So just brute force it:
Session:
$ elixir demo.exs
data
{[
%{amount: 2245, command: :buy, price: 90394},
%{amount: 4300, command: :buy, price: 89394},
%{amount: 1305, command: :buy, price: 90150}
],
[
%{amount: 2400, command: :sell, price: 100003},
%{amount: 2200, command: :sell, price: 100013}
]}
data2
{[
%{amount: 4300, command: :buy, price: 89394},
%{amount: 150, command: :buy, price: 90150}
],
[
%{amount: 2400, command: :sell, price: 100003},
%{amount: 2200, command: :sell, price: 100013}
]}
data3
{[
%{amount: 4300, command: :buy, price: 89394},
%{amount: 150, command: :buy, price: 90150},
%{amount: 1800, command: :buy, price: 91330},
%{amount: 1600, command: :buy, price: 100010}
],
[
%{amount: 2200, command: :sell, price: 100013},
%{amount: 3800, command: :sell, price: 100150}
]}
Code:
defmodule Demo do
def run(data),
do: List.foldl(data, {[], []}, &reducer/2)
defp reducer(%{command: :sell} = sell, {buys, sells}) do
case apply_sell(sell, buys, []) do
{nil, new_buys} ->
{new_buys, sells}
{new_sell, new_buys} ->
{new_buys, combine(sells, new_sell, [])}
end
end
defp reducer(%{command: :buy} = buy, {buys, sells}) do
case apply_buy(buy, sells, []) do
{nil, new_sells} ->
{buys, new_sells}
{new_buy, new_sells} ->
{combine(buys, new_buy, []), new_sells}
end
end
defp apply_buy(buy, [], other) do
{buy, :lists.reverse(other)}
end
defp apply_buy(
%{price: buy_price, amount: buy_amount} = buy,
[%{price: sell_price, amount: sell_amount} = sell | tail],
other
)
when buy_price >= sell_price do
cond do
buy_amount > sell_amount ->
apply_buy(%{buy | amount: buy_amount - sell_amount}, tail, other)
buy_amount < sell_amount ->
{nil, :lists.reverse(other, [%{sell | amount: sell_amount - buy_amount} | tail])}
true ->
{nil, :lists.reverse(other, tail)}
end
end
defp apply_buy(buy, [sell | tail], other) do
# buy price too low
apply_buy(buy, tail, [sell | other])
end
defp apply_sell(sell, [], other) do
{sell, :lists.reverse(other)}
end
defp apply_sell(
%{price: sell_price, amount: sell_amount} = sell,
[%{price: buy_price, amount: buy_amount} = buy | tail],
other
)
when sell_price <= buy_price do
cond do
sell_amount > buy_amount ->
apply_sell(%{sell | amount: sell_amount - buy_amount}, tail, other)
sell_amount < buy_amount ->
{nil, :lists.reverse(other, [%{buy | amount: buy_amount - sell_amount} | tail])}
true ->
{nil, :lists.reverse(other, tail)}
end
end
defp apply_sell(sell, [buy | tail], other) do
# sell price too high
apply_sell(sell, tail, [buy | other])
end
defp combine([], item, other),
do: :lists.reverse(other, [item])
defp combine(
[%{price: head_price, amount: head_amount} = head | tail],
%{price: item_price, amount: item_amount},
other
)
when head_price === item_price,
do: :lists.reverse(other, [%{head | amount: head_amount + item_amount} | tail])
defp combine([head | tail], item, other),
do: combine(tail, item, [head | other])
end
data = [
%{command: :sell, price: 100_003, amount: 2_400},
%{command: :buy, price: 90_394, amount: 3_445},
%{command: :buy, price: 89_394, amount: 4_300},
%{command: :sell, price: 100_013, amount: 2_200},
%{command: :buy, price: 90_150, amount: 1_305},
%{command: :buy, price: 90_394, amount: 1_000},
%{command: :sell, price: 90_394, amount: 2_200}
]
IO.puts("data")
IO.inspect(Demo.run(data))
data2 =
data ++
[%{command: :sell, price: 90_150, amount: 3_400}]
IO.puts("data2")
IO.inspect(Demo.run(data2))
data3 =
data2 ++
[
%{command: :buy, price: 91_330, amount: 1_800},
%{command: :buy, price: 100_010, amount: 4_000},
%{command: :sell, price: 100_150, amount: 3_800}
]
IO.puts("data3")
IO.inspect(Demo.run(data3))
Last Post!
benonymus
After some research I figured out that what I need is a matching/trading engine:
https://medium.com/lgogroup/a-matching-engine-for-our-values-part-1-795a29b400fa
Does anyone have an example?
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex









