Advertisement
Guest User

SwiftMatchEngine

a guest
Nov 15th, 2014
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 16.48 KB | None | 0 0
  1. using System;
  2. using System.Collections.Gene;
  3. using System.Li
  4. using System.Threadi
  5. using MySql.Data.MySqlCli
  6. using ServiceStack.Redi
  7. using ServiceStack.
  8. using ExchangeShared.Type
  9. using ExchangeShared.E;
  10. using ExchangeShared.B
  11.  
  12. using AppServerShared;
  13.  
  14. namespace MatchingEngine
  15. {
  16.     class ChangedOrders : Dictionary<uint, Dictionary<ulong, DbOrder>> { }
  17.  
  18.     public class Matcher
  19.     {
  20.         MatchingEngineConfig m_config;
  21.         ChangedOrders m_changedOrders;
  22.         static bool m_gProcess;
  23.  
  24.         /// <summary>
  25.         ///
  26.         /// </summary>
  27.         /// <param name="args"></param>
  28.         static void Main(string[] args)
  29.         {
  30.             ExchangeDatabase dataBase = new ExchangeDatabase(Thread.CurrentThread.ManagedThreadId);
  31.             Matcher matcher = new Matcher();
  32.             object exchangeUid = dataBase.QueryScalar("SELECT COUNT(*) FROM users WHERE uid=1;");
  33.             WildLog.Assert(exchangeUid != null && (long)exchangeUid == 1);
  34.  
  35.  
  36.             // register a tick
  37.             RedisWrapper.RegisterServiceTick(ServiceTickType.MatchingEngine, true);
  38.  
  39.             while (m_gProcess)
  40.             {
  41.  
  42.                     List<DbMarket> markets = dataBase.GetMarkets();
  43.                     var currencies = Helpers.GetMarketMapInt(dataBase);
  44.  
  45.                     foreach (DbMarket market in markets)
  46.                     {
  47.                         matcher.MatchOrdersCycle(dataBase, market, currencies);
  48.                     }
  49.  
  50.                     TimeSpan delta = end - start;
  51.  
  52.                     if (delta.TotalSeconds > 2)
  53.                     {
  54.                         RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineSlow, "Tick time = " + delta.TotalSeconds, "");
  55.                     }
  56.                 }
  57.                 {
  58.                     RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineException, "Main Loop", e.ToString());
  59.                 }
  60.  
  61.                 Thread.Sleep(1000);
  62.             }
  63.  
  64.             // send an email or something
  65.             RedisWrapper.UnregisterServiceTick(ServiceTickType.MatchingEngine);
  66.         }
  67.  
  68.  
  69.         /// <summary>
  70.         ///
  71.         /// </summary>
  72.         public Matcher(MatchingEngineConfig config = null)
  73.         {
  74.             Helpers.ConfigureServiceStackJson();
  75.  
  76.             if (m_config == null)
  77.             {
  78.             m_changedOrders = new ChangedOrders();
  79.         }
  80.  
  81.         /// <summary>
  82.         ///
  83.         /// </summary>
  84.         /// <param name="dataBase"></param>
  85.         /// <param name="baseCurrency"></param>
  86.         /// <param name="quoteCurrency"></param>
  87.         public void MatchOrdersCycle(ExchangeDatabase dataBase, DbMarket market, Dictionary<int, DbCurrency> currencies)
  88.         {
  89.             List<DbOrder> orders = dataBase.Query<DbOrder>("SELECT * FROM orders WHERE base_currency=@base AND quote_currency=@quote ORDER BY price DESC, uid LOCK IN SHARE MODE",
  90.                                                             (int)market.base_currency, (i
  91.             List<DbOrder> bids, asks;
  92.             SeparateIntoBidsAndAsks(orders, out bids, out asks);
  93.             MatchOrd
  94.         /// <summary>
  95.         ///
  96.         /// </summary>
  97.         /// <param name="o"></param>
  98.         /// <param name="dataBase"
  99.             orders.Remove(o);
  100.             Orders.RemoveOrder(dataBase, o, reason);
  101.            
  102.             // mark that it's been deleted
  103.             DbOrder clone = o.Clone();
  104.             clone.volume = 0;
  105.         ///
  106.         /// </summary>
  107.         /// <param name="o"></param>
  108.         void StoreChangedOrder(DbOrder o)
  109.         {
  110.             // keep track of removed orders by user and the reea
  111.             if (!o.m_IsMarketOrder)
  112.             {
  113.  
  114.                 // store
  115.                 m_changedOrders[o.user][o.uid] = o;
  116.             }
  117.         }
  118.  
  119.         /// <summary>
  120.         ///
  121.         /// </summary>
  122.         /// <param name="orders"></param>yOrder)
  123.         {
  124.             foreach (DbOrder o in orders)
  125.             {
  126.                 if (o.type == DbOrderType.buyLimit && buyOrder)
  127.                 {
  128.                     return o;
  129.                 }
  130.                 else if (o.type == DbOrderType.sellLimit && !buyOrder)
  131.                 {
  132.                     return o;
  133.                 }
  134.             }
  135.         /// </summary>
  136.         /// <param name="dataBase"></param>
  137.         /// <param name="bids"></param>
  138.         /// <param name="asks"></param>
  139.         void MatchOrders(ExchangeDatabase dataBase, List<DbOrder> bids, List<DbOrder> asks, DbMarket market, Dictionary<int, DbCurrency> currencies)
  140.         {
  141.             // bids[0].price >= asks[0].price
  142.             string baseCurrency = currencies[market.base_currency].name;
  143.             string quoteCurrency = currencies[market.quote_currency].name;
  144.  
  145.             using (var client = RedisWrapper.GetClient())
  146.             {
  147.                 using (client.AcquireLock( RedisWrapper.BuildLockKey("matcher:" + market) ))
  148.                 {
  149.                     ulong transUid = 0;
  150.                     HashSet<uint> uniqueUsers = new HashSet<uint>();
  151.                     List<DbTransaction> recentTransactions = new List<DbTransaction>();
  152.  
  153.                     DateTime startTime = DateTime.UtcNow;
  154.  
  155.                     while (bids.Count
  156.                         DbOrder bid = bids[0];
  157.  
  158.                         DbOrder firstBuyLimit = FindFirstLimitOrder(bids, true);
  159.                         DbOrder firstSellLimit = FindFirstLimitOrder(asks, false);
  160.  
  161.                         if ((firstBuyLimit != null || ask.type == DbOrderType.sellLimit) && (firstSellLimit != null || bid.type == DbOrderType.buyLimit))
  162.                         {
  163.                             // market orders find the first opposite limit order and set price to that, then proceed as normal.
  164.                             // all market orders to be
  165.                             if (bid.type == DbOrderType.buy)
  166.                             {
  167.                                 bid.price = firstSellLimit.price;
  168.                             }
  169.  
  170.                             // attempt trade!
  171.                             decimal tradeAmount = Math.Min(bid.volume, ask.volume);
  172.  
  173.                             // give the best price to the oldest order
  174.                             decimal tradePrice = (bid.uid < ask.uid) ? bid.price : ask.price;
  175.                             decimal tradeCost = Helpers.Normalise(tradeAmount * tradePrice);
  176.  
  177.                             decimal askVolume = ask.volume;
  178.  
  179.                             if (bid.volume == 0)
  180.                             {
  181.                                 RemoveOrder(bid, dataBase, bid
  182.                             {
  183.                                 RemoveOrder(bid, dataBase, asks, DbCancelledOrderReason.invalidVolume);
  184.                             }
  185.                             else
  186.                             {
  187.                                 // lock all things pertaining to this transaction
  188.                                 DbOrder pendingRemoveBid =
  189.  
  190.                                 try
  191.                                 {
  192.                                     // keep track of u
  193.  
  194.  
  195.                                     List<DbOrder> orders = dataBase.Query<DbOrder>("SELECT * FROM orders WHERE uid IN(@uidBid, @uidAsk) FOR UPDATE;", bid.uid, ask.uid);
  196.  
  197.                                     if (orders.Count != 2)
  198.                                     {
  199.                                         throw new OrderWasAlteredException(orders);
  200.                                     }
  201.  
  202.                                     // do the seller and buyer both have enough in their respective balances?
  203.                                     List<DbBalance> balances = dataBase.Query<DbBalance>("SELECT * FROM balances WHERE user IN(@userBid, @userAsk, @exchange) AND currency IN(@base, @quote) FOR UPDATE;",
  204.                                                                                             bid.user, ask.user, ExchangeDatabase.kExchangeUser,
  205.                                                                                             (int)market.base_currency, (int)market.quote_currency);
  206.  
  207.                                     // does the buyer have enough base currency?
  208.                                     DbBalance buyerBase = balances.Find((b) => { return b.user == bid.user && b.currency == market.base_currency; });
  209.                                     DbBalance buyerQuote = balances.Find((b) => { return b.user == bid.user && b.currency == market.quote_currency; });
  210.                                     DbBalance sellern(balances);
  211.                                     }
  212.  
  213.                                     bool tradeOk = true;
  214.  
  215.                                     if (buyerQuote.amount < tradeCost)
  216.                                     {
  217.                                         // roll existing transaction back
  218.                                         dataBase.RollbackTransaction();
  219.  
  220.                                         // remove this bid as buyer doesn't have enough money - creates new db transaction
  221.                                         RemoveOrder(bid,
  222.                                     {
  223.                                         if (tradeOk)
  224.                                         {
  225.                                             // roll existing transaction back
  226.                                             dataBase.RollbackTransaction();
  227.                                         }
  228.  
  229.                                         // re
  230.                                     }
  231.  
  232.                                     if (tradeOk)
  233.                                     {
  234.                                         // both buyer and seller have enough, finally do the trade!
  235.                                         bid.volume -= tradeAmount;
  236.                                         ask.volume -= tradeAmount;
  237.  
  238.                                         if (bid.volume > 0)
  239.                                         {
  240.                                             // update order quantities
  241.                                             dataBase.Statement("UPDATE orders SET volume=volume-@tradeAmount WHERE uid=@bidUid;", tradeAmount, bid.uid);
  242.                                         }der!
  243.                                             pendingRemoveBid = bid;
  244.                                         }
  245.                                         else
  246.                                         {
  247.                                             throw new
  248.                                         {
  249.                                             // update order quantities
  250.                                             dataBase.Statement("UPDATE orders SET volume=volume-@tradeAmount WHERE uid=@askUid;", tradeAmount, ask.uid);
  251.                                         }
  252.                                         else if (ask.volume == 0)
  253.                                         {
  254.                                             // remove the filled order!
  255.                                             pendingRemoveAsk = ask;
  256.                                         }
  257.                                         else
  258.                                         decimal feeCost, feeVolume;
  259.                                         m_config.CalculateFees(tradeCost, tradeAmount, sellerBase, buyerQuote, out feeCost, out feeVolume);
  260.  
  261.                                         feeCost = Helpers.Normalise(feeCost);
  262.                                         feeVolume = Helpers.Normalise(feeVolume);
  263.  
  264.                                         WildLog.Assert(feeCost < tradeCost, "Matcher.MatchOrders(): feeCost is >= tradeCost!");
  265.                                         WildLog.Assert(feeVolume < tradeAmount, "Matcher.MatchOrders(): feeVolume is >= tradeAmount!");
  266.  
  267.                                         // update balances for seller
  268.                                         dataBase.Statement("UPDATE balances SET amount=amount-@tradeAmount WHERE currency=@base AND user=@user;",
  269.                                                             tradeAmount, (int)market.base_currency, ask.user);
  270.                                         dataBase.Statement("UPDATE balances SET amount=amount+(@tradeCost-@feeCost) WHERE currency=@quote AND user=@user;",
  271.                                                             tradeCost, feeCost, (int)market.quote_currency, ask.user);
  272.  
  273.                                         // subtract full amount from seller
  274.                                         sellerBase.amount -= tradeAmount;
  275.  
  276.                                         // reward sel
  277.                                         // update balances for buyer
  278.                                         dataBase.Statement("UPDATE balances SET amount=amount+(@tradeAmount-@feeVolume) WHERE currency=@base AND user=@user;",
  279.                                                             tradeAmount, feeVolume, (int)market.base_currency, bid.user);
  280.                                         dataBase.Statement("UPDATE balances SET amount=amount-@tradeCost WHERE currency=@quote AND user=@user;",
  281.                                                             tradeCost, (int)market.quote_currency, bid.user);
  282.  
  283.                                         // update balances for exchange
  284.                                         int rowsA = dataBase.Statement("UPDATE balances SET amount=amount+@feeVolume WHERE currency=@base AND user=@exchange;",
  285.                                                         E balances SET amount=amount+@feeCost WHERE currency=@quote AND user=@exchange;",
  286.                                                             feeCost, (int)market.quote_currency, ExchangeDatabase.kExchangeUser);
  287.  
  288.                                         if (rowsA == 0 && rowsB == 0)
  289.                                         {
  290.                                             throw new FatalExceptionHalt();
  291.                                         }
  292.  
  293.                                         // reward buyer with amount minus fees
  294.                                         buyerBase.amount += tradeAmount - feeVolume;
  295.  
  296.                                         // subtract full cost from buyer
  297.                                         buyerQuote.amount -= tradeCost;
  298.  
  299.                                         WildLog.Assert(buyerBase.amount >= 0);
  300.                                         WildLog.Assert(buyerQuote.amount >= 0);
  301.                                         WildLog.Assert(sellerBase.amount >= 0);
  302.                                         WildLog.Assert(sellerQuote.amount >= 0);
  303.  
  304.                                         // either bid or ask volume will == 0 here
  305.                                         WildLog.Assert(bid.volume == 0 || ask.volume == 0, "one of bid/ask must have been filled completely!");
  306.                                         int buyOrder = (ask.volume == 0) ? 1 : 0;
  307.                                         int sell
  308.  
  309.                                         // insert transaction for this order
  310.                                         transUid = dataBase.InsertTransaction(tradePrice, tradeAmount, bid.type, ask.type, now, market.base_currency, market.quote_currency, bid.user, ask.user, feeVolume, feeCost, direction);
  311.  
  312.                                         // and we're done!
  313.                                         dataBase.EndTransaction();
  314.  
  315.                                         // keep track of new transactions for streaming purposes
  316.                                         recentTransactions.Add(new DbTransaction
  317.                                                                 {
  318.                                                                     base_currency = market.base_currency,
  319.                                                                     buy_fee = feeVolume,
  320.                                                                     buy_type = bid.type,
  321.                                                                     buyer_uid = bid.user,
  322.                                                                     date = now,
  323.                                                                     direction = direction,
  324.                                                                     price = tradePrice,
  325.                                                                     quote_currency = market.quote_currency,
  326.                                                                     rolled_back = false,
  327.                                                                
  328.                                     }
  329.                                 }
  330.                                 catch (BalanceRecordMissingException e)
  331.                                 {
  332.                                     dataBase.RollbackTransaction();
  333.  
  334.                                     // which user(s) have a missing balance
  335.                                     bool askHasQuote = e.m_balances.Exists((b) => { return b.user == ask.user && b.currency == market.quote_currency; });
  336.                                     bool askHasBase = e.m_balances.Exists((b) => { return b.user == ask.user && b.currency == market.base_currency; });
  337.                                
  338.                                     if (!askHasQuote || !askHasBase)
  339.                                     {
  340.                                         // ask is invalid
  341.                                         RemoveOrder(ask, dataBase, asks, DbCancelledOrderReason.balanceForOrderCurrencyMissing);
  342.  
  343.                                         // should log this
  344.                                         RedisWrapper.LogException(ExchangeExceptionType.BalanceRecordMissingException, "user " + ask.user + ", askHasQuote=" + askHasQuote + ", askHasBase=" + askHasBase, e.ToString());
  345.                                     }
  346.                                     if (!bidHasQuote || !bidHasBase)
  347.                                     {
  348.                                         // bid is invalid
  349.                                         RemoveOrder(bid, dataBase, bids, DbCancelledOrderReason.balanceForOrderCurrencyMissing);
  350.  
  351.                                         // should lo
  352.                                 }
  353.                                 catch (OrderWasAlteredException e)
  354.                                 {
  355.                                     dataBase.RollbackTransaction();
  356.  
  357.                                     // figure out which order(s) where modified
  358.                                     if (e.m_orders.Count == 0)
  359.                                     {
  360.                                         // both
  361.                                         bids.Remove(bid);
  362.                                         asks.Remove(ask);
  363.                                     }
  364.                                     else if (e.m_orders[0].uid == bid.uid)
  365.                                     {
  366.                                         // ask was modified
  367.                                         asks.Remove(ask);
  368.                                     }
  369.                                     else
  370.                                     {
  371.                                         // bid was modified
  372.                                         bids.Remove(bid);
  373.                                     }
  374.                                 }
  375.                                 catch (Exception e)
  376.                                 {
  377.                                     dataBase.RollbackTransaction();
  378.  
  379.                                     // log this
  380.                                     RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineException, "", e.ToString());
  381.  
  382.                                     // roll these back as well!
  383.                                     bid.volume = bidVolume;
  384.                                     ask.volume = askVolume;
  385.                                     pendingRemoveAsk = null;
  386.                                     pendingRemoveBid = null;
  387.  
  388.                                     if ((DateTime.UtcNow - startTime).TotalMinutes > kMaxMinutesInOneMatch)
  389.                                     {
  390.                                         // we are stuck here, probably experiencing a repeating exception
  391.                                         break;
  392.                                     }
  393.  
  394.                                     if (e is FatalExceptionHalt)
  395.                                     {
  396.                                         // stop execution
  397.                                         m_gProcess = false;
  398.                                         throw;
  399.                                     }
  400.                                 }
  401.  
  402.                                 // remove any orders marked for pending remove
  403.                                
  404.                             }
  405.  
  406.                             // reset the price on market orders so we continue to loop
  407.                             if (ask.type == DbOrderType.sell)
  408.                             {
  409.                                 ask.price = 0;
  410.                             }
  411.                                 // order was changed!
  412.                                 StoreChangedOrder(bid);
  413.                             }
  414.                             if (ask.volume != askVolume && !ask.m_IsMarketOrder)
  415.                             {
  416.                             // remove offending market orders and resume matching
  417.                             if (firstBuyLimit == null && ask.type == DbOrderType.sell)
  418.                             {
  419.                                 RemoveOrder(ask, dataBase, asks, DbCancelledOrderReason.insufficientLiquidity);
  420.                             }
  421.                             if (firstSellLimit == nul, DbCancelledOrderReason.insufficientLiquidity);
  422.                             }
  423.                         }
  424.                     }
  425.  
  426.                     // remove all market orders still in the order-book
  427.                     string uidString = "";
  428.                     }
  429.                     foreach (DbOrder o in bids)
  430.                     {
  431.                         if (o.m_IsMarketOrder)
  432.                         {
  433.                             uidString += "," + o.uid;
  434.                         }
  435.                     }
  436.                     if (uidString.Length > 0)
  437.                     {celled_orders SELECT o.*,@no_reason FROM orders o WHERE uid IN (" + uidString + ");", DbCancelledOrderReason.insufficientLiquidity);
  438.                             dataBase.Statement("DELETE FROM orders WHERE uid IN (" + uidString + ");");
  439.                             dataBase.EndTransaction();
  440.                         }
  441.                         catch (Exception e)
  442.                         {
  443.                             dataBase.RollbackTransaction();
  444.  
  445.                             // log this
  446.                             RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineException, "", e.ToString());
  447.                         }
  448.  
  449.                         asks.RemoveAll((o) => o.m_IsMarketOrder);
  450.                         bids.RemoveAll((o) => o.m_IsMarketOrder);
  451.                     }
  452.  
  453.                     // keep the public orderbook up to date
  454.                     RedisWrapper.UpdatePublicOrderbook(bids, asks, baseCurrency, quoteCurrency, Routes.kGetOrderbook);
  455.                    
  456.                     // update recent transactions
  457.                     if (transUid != 0)
  458.                     {
  459.                         RedisWrapper.InvalidatePublicCacheForCurrency(baseCurrency, quoteCurrency, Routes.kGetTransactionsPublic);
  460.                         RedisWrapper.InvalidatePublicCacheForCurrency(baseCurrency, quoteCurrency, Routes.kGetOhlcv);
  461.                         RedisWrapper.UpdateStreamingTransactions(recentTransactions, baseCurrency, quoteCurrency);
  462.                     }
  463.  
  464.                     // invalidate private caches
  465.                     foreach (uint user in uniqueUsers)
  466.                     {
  467.                         RedisWrapper.InvalidatePrivateCacheForUser(user, Routes.kGetBalances);
  468.                         RedisWrapper.InvalidatePrivateCacheForCurrency(user, baseCurrency, quoteCurrency, Routes.kGetOrdersPrivate);
  469.                         RedisWrapper.InvalidatePrivateCacheForCurrency(user, baseCurrency, quoteCurrency, Routes.kGetTransactionsPrivate);
  470.  
  471.                         // store any changes in open orders for the streaming system
  472.                         if (m_changedOrders.ContainsKey(user))
  473.  
  474.  
  475.         /// <summary>
  476.         ///
  477.         /// </summary>
  478.         /// <param name="allOrders"></param>
  479.         /// <param name="bids"></param>
  480.         /// <param name="asks"></param>
  481.         void SeparateIntoBidsAndAsks(List<DbOrder> allOrders, out List<DbOrder> bids, out List<DbOrder> asks)
  482.         {
  483.             bids = new List<DbOrder>();
  484.             asks = new List<DbOrder>();
  485.  
  486.             // sorted from high to low by mysql, so asks first, then bids
  487.              asks[i].price)
  488.                         {
  489.                             i++;
  490.                         }
  491.                         asks.Insert(i, o);
  492.                     }
  493.                     else
  494.                     {
  495.                         asks.Insert(0, o);
  496.                     }
  497.                 }
  498.                 else
  499.                 {
  500.                     bids.Add(o);
  501.                 }
  502.             }
  503.         }
  504.     }
  505. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement