Skip to content

Instantly share code, notes, and snippets.

@redink
Last active August 2, 2018 16:05
Show Gist options
  • Save redink/60559d86886f8923ad70262bf704eff4 to your computer and use it in GitHub Desktop.
Save redink/60559d86886f8923ad70262bf704eff4 to your computer and use it in GitHub Desktop.
defmodule Trade.MatchServer do
use GenServer
require Logger
@buy_queue :buy_queue
@sell_queue :sell_queue
@unhandled 0
@before_generate_tx 1
@after_generate_tx 2
@created_new_order 3
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def order_buy(%{price: buy_price, timestamp: timestamp} = order) do
:ets.insert(@buy_queue, {{buy_price, -timestamp, order}, @unhandled})
end
def order_sell(%{price: sell_price, timestamp: timestamp} = order) do
:ets.insert(@sell_queue, {{sell_price, timestamp, order}, @unhandled})
end
def init(_) do
# create buy queue and sell queue
:ets.new(@buy_queue, [:named_table, :ordered_set, :public])
:ets.new(@sell_queue, [:named_table, :ordered_set, :public])
Process.send_after(self(), :try_match, get_match_interval())
{:ok, %{}}
end
def handle_info(:try_match, state) do
try_handle_match_order(last_buy_queue(), first_sell_queue())
Process.send_after(self(), :try_match, get_match_interval())
{:noreply, state}
end
defp get_match_interval do
Application.get_env(:trade, :match_server_interval, 10)
end
defp try_handle_match_order(:"$end_of_table", _), do: nil
defp try_handle_match_order(_, :"$end_of_table"), do: nil
defp try_handle_match_order({buy_price, _, _} = last_buy, {sell_price, _, _} = first_sell)
when buy_price >= sell_price do
handle_match_order(lookup_buy_order(last_buy), lookup_sell_order(first_sell))
end
defp try_handle_match_order(_, _), do: nil
defp handle_match_order({last_buy, buy_tag}, {first_sell, sell_tag})
when (buy_tag == @unhandled and sell_tag == @unhandled) or
(buy_tag == @before_generate_tx and sell_tag == @unhandled) or
(buy_tag == @unhandled and sell_tag == @before_generate_tx) or
(buy_tag == @before_generate_tx and sell_tag == @before_generate_tx) do
{_buy_price, _buy_time, %{amount: buy_amount} = buy_order} = last_buy
{_sell_price, _sell_time, %{amount: sell_amount} = sell_order} = first_sell
prepare_generate_tx(last_buy, first_sell)
# maybe tx had been generated, be careful duplicate tx
generate_tx(last_buy, first_sell)
generate_tx_done(last_buy, first_sell)
cond do
buy_amount == sell_amount -> nil
buy_amount > sell_amount -> order_buy(%{buy_order | amount: buy_amount - sell_amount})
buy_amount < sell_amount -> order_sell(%{sell_order | amount: sell_amount - buy_amount})
end
created_new_order(last_buy, first_sell)
delete_last_buy_queue(last_buy)
delete_first_sell_queue(first_sell)
end
defp handle_match_order({last_buy, buy_tag}, {first_sell, sell_tag})
when buy_tag == @after_generate_tx or sell_tag == @after_generate_tx do
{_buy_price, _buy_time, %{amount: buy_amount} = buy_order} = last_buy
{_sell_price, _sell_time, %{amount: sell_amount} = sell_order} = first_sell
generate_tx_done(last_buy, first_sell)
cond do
buy_amount == sell_amount -> nil
buy_amount > sell_amount -> order_buy(%{buy_order | amount: buy_amount - sell_amount})
buy_amount < sell_amount -> order_sell(%{sell_order | amount: sell_amount - buy_amount})
end
created_new_order(last_buy, first_sell)
delete_last_buy_queue(last_buy)
delete_first_sell_queue(first_sell)
end
defp handle_match_order({last_buy, buy_tag}, {first_sell, sell_tag}) do
{_buy_price, _buy_time, %{amount: buy_amount} = buy_order} = last_buy
{_sell_price, _sell_time, %{amount: sell_amount} = sell_order} = first_sell
cond do
buy_amount == sell_amount ->
nil
buy_amount > sell_amount ->
if buy_tag == @created_new_order do
nil
else
order_buy(%{buy_order | amount: buy_amount - sell_amount})
end
buy_amount < sell_amount ->
if sell_tag == @created_new_order do
nil
else
order_sell(%{sell_order | amount: sell_amount - buy_amount})
end
end
created_new_order(last_buy, first_sell)
delete_last_buy_queue(last_buy)
delete_first_sell_queue(first_sell)
end
defp last_buy_queue do
:ets.last(@buy_queue)
end
defp first_sell_queue do
:ets.first(@sell_queue)
end
defp lookup_buy_order(last_buy) do
@buy_queue
|> :ets.lookup(last_buy)
|> List.first()
end
defp lookup_sell_order(first_sell) do
@sell_queue
|> :ets.lookup(first_sell)
|> List.first()
end
defp delete_last_buy_queue(last) do
:ets.delete(@buy_queue, last)
end
defp delete_first_sell_queue(first) do
:ets.delete(@sell_queue, first)
end
defp prepare_generate_tx(last_buy, first_sell) do
:ets.insert(@buy_queue, {last_buy, @before_generate_tx})
:ets.insert(@sell_queue, {first_sell, @before_generate_tx})
end
defp generate_tx(last_buy, first_sell) do
Logger.info("last_buy: #{inspect(last_buy)}, first_sell: #{inspect(first_sell)}")
end
defp generate_tx_done(last_buy, first_sell) do
:ets.insert(@buy_queue, {last_buy, @after_generate_tx})
:ets.insert(@sell_queue, {first_sell, @after_generate_tx})
end
defp created_new_order(last_buy, first_sell) do
:ets.insert(@buy_queue, {last_buy, @created_new_order})
:ets.insert(@sell_queue, {first_sell, @created_new_order})
end
#
end
@redink
Copy link
Author

redink commented Jul 31, 2018

iex(2)> Trade.MatchServer.order_buy(%{price: 0.1, timestamp: :erlang.system_time(), amount: 10})
true
iex(3)> Trade.MatchServer.order_buy(%{price: 0.1, timestamp: :erlang.system_time(), amount: 1})
true
iex(4)> Trade.MatchServer.order_buy(%{price: 0.1, timestamp: :erlang.system_time(), amount: 1})
true
iex(5)> Trade.MatchServer.order_buy(%{price: 0.2, timestamp: :erlang.system_time(), amount: 1})
true
iex(6)> Trade.MatchServer.order_buy(%{price: 0.5, timestamp: :erlang.system_time(), amount: 1})
true
iex(7)> Trade.MatchServer.order_buy(%{price: 0.6, timestamp: :erlang.system_time(), amount: 20})
true
iex(8)> :ets.tab2list(:buy_queue)
[
  {{0.1, -1533053828025397000,
    %{amount: 1, price: 0.1, timestamp: 1533053828025397000}}},
  {{0.1, -1533053828024794000,
    %{amount: 1, price: 0.1, timestamp: 1533053828024794000}}},
  {{0.1, -1533053828024112000,
    %{amount: 10, price: 0.1, timestamp: 1533053828024112000}}},
  {{0.2, -1533053828025981000,
    %{amount: 1, price: 0.2, timestamp: 1533053828025981000}}},
  {{0.5, -1533053828026648000,
    %{amount: 1, price: 0.5, timestamp: 1533053828026648000}}},
  {{0.6, -1533053828184293000,
    %{amount: 20, price: 0.6, timestamp: 1533053828184293000}}}
]
iex(9)> Trade.MatchServer.order_sell(%{price: 0.7, timestamp: :erlang.system_time(), amount: 1})
true
iex(10)> :ets.tab2list(:sell_queue)                                                              
[
  {{0.7, 1533053843535814000,
    %{amount: 1, price: 0.7, timestamp: 1533053843535814000}}}
]
iex(11)> Trade.MatchServer.order_sell(%{price: 0.6, timestamp: :erlang.system_time(), amount: 1})
true
iex(12)> 
00:17:33.027 [info]  last_buy: {0.6, -1533053828184293000, %{amount: 20, price: 0.6, timestamp: 1533053828184293000}}, first_sell: {0.6, 1533053852943574000, %{amount: 1, price: 0.6, timestamp: 1533053852943574000}}
iex(12)> :ets.tab2list(:sell_queue)                                                              
[
  {{0.7, 1533053843535814000,
    %{amount: 1, price: 0.7, timestamp: 1533053843535814000}}}
]
iex(13)> :ets.tab2list(:buy_queue)                                                               
[
  {{0.1, -1533053828025397000,
    %{amount: 1, price: 0.1, timestamp: 1533053828025397000}}},
  {{0.1, -1533053828024794000,
    %{amount: 1, price: 0.1, timestamp: 1533053828024794000}}},
  {{0.1, -1533053828024112000,
    %{amount: 10, price: 0.1, timestamp: 1533053828024112000}}},
  {{0.2, -1533053828025981000,
    %{amount: 1, price: 0.2, timestamp: 1533053828025981000}}},
  {{0.5, -1533053828026648000,
    %{amount: 1, price: 0.5, timestamp: 1533053828026648000}}},
  {{0.6, -1533053828184293000,
    %{amount: 19, price: 0.6, timestamp: 1533053828184293000}}}
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment