Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.Collections.Gene;
- using System.Li
- using System.Threadi
- using MySql.Data.MySqlCli
- using ServiceStack.Redi
- using ServiceStack.
- using ExchangeShared.Type
- using ExchangeShared.E;
- using ExchangeShared.B
- using AppServerShared;
- namespace MatchingEngine
- {
- class ChangedOrders : Dictionary<uint, Dictionary<ulong, DbOrder>> { }
- public class Matcher
- {
- MatchingEngineConfig m_config;
- ChangedOrders m_changedOrders;
- static bool m_gProcess;
- /// <summary>
- ///
- /// </summary>
- /// <param name="args"></param>
- static void Main(string[] args)
- {
- ExchangeDatabase dataBase = new ExchangeDatabase(Thread.CurrentThread.ManagedThreadId);
- Matcher matcher = new Matcher();
- object exchangeUid = dataBase.QueryScalar("SELECT COUNT(*) FROM users WHERE uid=1;");
- WildLog.Assert(exchangeUid != null && (long)exchangeUid == 1);
- // register a tick
- RedisWrapper.RegisterServiceTick(ServiceTickType.MatchingEngine, true);
- while (m_gProcess)
- {
- List<DbMarket> markets = dataBase.GetMarkets();
- var currencies = Helpers.GetMarketMapInt(dataBase);
- foreach (DbMarket market in markets)
- {
- matcher.MatchOrdersCycle(dataBase, market, currencies);
- }
- TimeSpan delta = end - start;
- if (delta.TotalSeconds > 2)
- {
- RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineSlow, "Tick time = " + delta.TotalSeconds, "");
- }
- }
- {
- RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineException, "Main Loop", e.ToString());
- }
- Thread.Sleep(1000);
- }
- // send an email or something
- RedisWrapper.UnregisterServiceTick(ServiceTickType.MatchingEngine);
- }
- /// <summary>
- ///
- /// </summary>
- public Matcher(MatchingEngineConfig config = null)
- {
- Helpers.ConfigureServiceStackJson();
- if (m_config == null)
- {
- m_changedOrders = new ChangedOrders();
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="dataBase"></param>
- /// <param name="baseCurrency"></param>
- /// <param name="quoteCurrency"></param>
- public void MatchOrdersCycle(ExchangeDatabase dataBase, DbMarket market, Dictionary<int, DbCurrency> currencies)
- {
- 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",
- (int)market.base_currency, (i
- List<DbOrder> bids, asks;
- SeparateIntoBidsAndAsks(orders, out bids, out asks);
- MatchOrd
- /// <summary>
- ///
- /// </summary>
- /// <param name="o"></param>
- /// <param name="dataBase"
- orders.Remove(o);
- Orders.RemoveOrder(dataBase, o, reason);
- // mark that it's been deleted
- DbOrder clone = o.Clone();
- clone.volume = 0;
- ///
- /// </summary>
- /// <param name="o"></param>
- void StoreChangedOrder(DbOrder o)
- {
- // keep track of removed orders by user and the reea
- if (!o.m_IsMarketOrder)
- {
- // store
- m_changedOrders[o.user][o.uid] = o;
- }
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="orders"></param>yOrder)
- {
- foreach (DbOrder o in orders)
- {
- if (o.type == DbOrderType.buyLimit && buyOrder)
- {
- return o;
- }
- else if (o.type == DbOrderType.sellLimit && !buyOrder)
- {
- return o;
- }
- }
- /// </summary>
- /// <param name="dataBase"></param>
- /// <param name="bids"></param>
- /// <param name="asks"></param>
- void MatchOrders(ExchangeDatabase dataBase, List<DbOrder> bids, List<DbOrder> asks, DbMarket market, Dictionary<int, DbCurrency> currencies)
- {
- // bids[0].price >= asks[0].price
- string baseCurrency = currencies[market.base_currency].name;
- string quoteCurrency = currencies[market.quote_currency].name;
- using (var client = RedisWrapper.GetClient())
- {
- using (client.AcquireLock( RedisWrapper.BuildLockKey("matcher:" + market) ))
- {
- ulong transUid = 0;
- HashSet<uint> uniqueUsers = new HashSet<uint>();
- List<DbTransaction> recentTransactions = new List<DbTransaction>();
- DateTime startTime = DateTime.UtcNow;
- while (bids.Count
- DbOrder bid = bids[0];
- DbOrder firstBuyLimit = FindFirstLimitOrder(bids, true);
- DbOrder firstSellLimit = FindFirstLimitOrder(asks, false);
- if ((firstBuyLimit != null || ask.type == DbOrderType.sellLimit) && (firstSellLimit != null || bid.type == DbOrderType.buyLimit))
- {
- // market orders find the first opposite limit order and set price to that, then proceed as normal.
- // all market orders to be
- if (bid.type == DbOrderType.buy)
- {
- bid.price = firstSellLimit.price;
- }
- // attempt trade!
- decimal tradeAmount = Math.Min(bid.volume, ask.volume);
- // give the best price to the oldest order
- decimal tradePrice = (bid.uid < ask.uid) ? bid.price : ask.price;
- decimal tradeCost = Helpers.Normalise(tradeAmount * tradePrice);
- decimal askVolume = ask.volume;
- if (bid.volume == 0)
- {
- RemoveOrder(bid, dataBase, bid
- {
- RemoveOrder(bid, dataBase, asks, DbCancelledOrderReason.invalidVolume);
- }
- else
- {
- // lock all things pertaining to this transaction
- DbOrder pendingRemoveBid =
- try
- {
- // keep track of u
- List<DbOrder> orders = dataBase.Query<DbOrder>("SELECT * FROM orders WHERE uid IN(@uidBid, @uidAsk) FOR UPDATE;", bid.uid, ask.uid);
- if (orders.Count != 2)
- {
- throw new OrderWasAlteredException(orders);
- }
- // do the seller and buyer both have enough in their respective balances?
- List<DbBalance> balances = dataBase.Query<DbBalance>("SELECT * FROM balances WHERE user IN(@userBid, @userAsk, @exchange) AND currency IN(@base, @quote) FOR UPDATE;",
- bid.user, ask.user, ExchangeDatabase.kExchangeUser,
- (int)market.base_currency, (int)market.quote_currency);
- // does the buyer have enough base currency?
- DbBalance buyerBase = balances.Find((b) => { return b.user == bid.user && b.currency == market.base_currency; });
- DbBalance buyerQuote = balances.Find((b) => { return b.user == bid.user && b.currency == market.quote_currency; });
- DbBalance sellern(balances);
- }
- bool tradeOk = true;
- if (buyerQuote.amount < tradeCost)
- {
- // roll existing transaction back
- dataBase.RollbackTransaction();
- // remove this bid as buyer doesn't have enough money - creates new db transaction
- RemoveOrder(bid,
- {
- if (tradeOk)
- {
- // roll existing transaction back
- dataBase.RollbackTransaction();
- }
- // re
- }
- if (tradeOk)
- {
- // both buyer and seller have enough, finally do the trade!
- bid.volume -= tradeAmount;
- ask.volume -= tradeAmount;
- if (bid.volume > 0)
- {
- // update order quantities
- dataBase.Statement("UPDATE orders SET volume=volume-@tradeAmount WHERE uid=@bidUid;", tradeAmount, bid.uid);
- }der!
- pendingRemoveBid = bid;
- }
- else
- {
- throw new
- {
- // update order quantities
- dataBase.Statement("UPDATE orders SET volume=volume-@tradeAmount WHERE uid=@askUid;", tradeAmount, ask.uid);
- }
- else if (ask.volume == 0)
- {
- // remove the filled order!
- pendingRemoveAsk = ask;
- }
- else
- decimal feeCost, feeVolume;
- m_config.CalculateFees(tradeCost, tradeAmount, sellerBase, buyerQuote, out feeCost, out feeVolume);
- feeCost = Helpers.Normalise(feeCost);
- feeVolume = Helpers.Normalise(feeVolume);
- WildLog.Assert(feeCost < tradeCost, "Matcher.MatchOrders(): feeCost is >= tradeCost!");
- WildLog.Assert(feeVolume < tradeAmount, "Matcher.MatchOrders(): feeVolume is >= tradeAmount!");
- // update balances for seller
- dataBase.Statement("UPDATE balances SET amount=amount-@tradeAmount WHERE currency=@base AND user=@user;",
- tradeAmount, (int)market.base_currency, ask.user);
- dataBase.Statement("UPDATE balances SET amount=amount+(@tradeCost-@feeCost) WHERE currency=@quote AND user=@user;",
- tradeCost, feeCost, (int)market.quote_currency, ask.user);
- // subtract full amount from seller
- sellerBase.amount -= tradeAmount;
- // reward sel
- // update balances for buyer
- dataBase.Statement("UPDATE balances SET amount=amount+(@tradeAmount-@feeVolume) WHERE currency=@base AND user=@user;",
- tradeAmount, feeVolume, (int)market.base_currency, bid.user);
- dataBase.Statement("UPDATE balances SET amount=amount-@tradeCost WHERE currency=@quote AND user=@user;",
- tradeCost, (int)market.quote_currency, bid.user);
- // update balances for exchange
- int rowsA = dataBase.Statement("UPDATE balances SET amount=amount+@feeVolume WHERE currency=@base AND user=@exchange;",
- E balances SET amount=amount+@feeCost WHERE currency=@quote AND user=@exchange;",
- feeCost, (int)market.quote_currency, ExchangeDatabase.kExchangeUser);
- if (rowsA == 0 && rowsB == 0)
- {
- throw new FatalExceptionHalt();
- }
- // reward buyer with amount minus fees
- buyerBase.amount += tradeAmount - feeVolume;
- // subtract full cost from buyer
- buyerQuote.amount -= tradeCost;
- WildLog.Assert(buyerBase.amount >= 0);
- WildLog.Assert(buyerQuote.amount >= 0);
- WildLog.Assert(sellerBase.amount >= 0);
- WildLog.Assert(sellerQuote.amount >= 0);
- // either bid or ask volume will == 0 here
- WildLog.Assert(bid.volume == 0 || ask.volume == 0, "one of bid/ask must have been filled completely!");
- int buyOrder = (ask.volume == 0) ? 1 : 0;
- int sell
- // insert transaction for this order
- transUid = dataBase.InsertTransaction(tradePrice, tradeAmount, bid.type, ask.type, now, market.base_currency, market.quote_currency, bid.user, ask.user, feeVolume, feeCost, direction);
- // and we're done!
- dataBase.EndTransaction();
- // keep track of new transactions for streaming purposes
- recentTransactions.Add(new DbTransaction
- {
- base_currency = market.base_currency,
- buy_fee = feeVolume,
- buy_type = bid.type,
- buyer_uid = bid.user,
- date = now,
- direction = direction,
- price = tradePrice,
- quote_currency = market.quote_currency,
- rolled_back = false,
- }
- }
- catch (BalanceRecordMissingException e)
- {
- dataBase.RollbackTransaction();
- // which user(s) have a missing balance
- bool askHasQuote = e.m_balances.Exists((b) => { return b.user == ask.user && b.currency == market.quote_currency; });
- bool askHasBase = e.m_balances.Exists((b) => { return b.user == ask.user && b.currency == market.base_currency; });
- if (!askHasQuote || !askHasBase)
- {
- // ask is invalid
- RemoveOrder(ask, dataBase, asks, DbCancelledOrderReason.balanceForOrderCurrencyMissing);
- // should log this
- RedisWrapper.LogException(ExchangeExceptionType.BalanceRecordMissingException, "user " + ask.user + ", askHasQuote=" + askHasQuote + ", askHasBase=" + askHasBase, e.ToString());
- }
- if (!bidHasQuote || !bidHasBase)
- {
- // bid is invalid
- RemoveOrder(bid, dataBase, bids, DbCancelledOrderReason.balanceForOrderCurrencyMissing);
- // should lo
- }
- catch (OrderWasAlteredException e)
- {
- dataBase.RollbackTransaction();
- // figure out which order(s) where modified
- if (e.m_orders.Count == 0)
- {
- // both
- bids.Remove(bid);
- asks.Remove(ask);
- }
- else if (e.m_orders[0].uid == bid.uid)
- {
- // ask was modified
- asks.Remove(ask);
- }
- else
- {
- // bid was modified
- bids.Remove(bid);
- }
- }
- catch (Exception e)
- {
- dataBase.RollbackTransaction();
- // log this
- RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineException, "", e.ToString());
- // roll these back as well!
- bid.volume = bidVolume;
- ask.volume = askVolume;
- pendingRemoveAsk = null;
- pendingRemoveBid = null;
- if ((DateTime.UtcNow - startTime).TotalMinutes > kMaxMinutesInOneMatch)
- {
- // we are stuck here, probably experiencing a repeating exception
- break;
- }
- if (e is FatalExceptionHalt)
- {
- // stop execution
- m_gProcess = false;
- throw;
- }
- }
- // remove any orders marked for pending remove
- }
- // reset the price on market orders so we continue to loop
- if (ask.type == DbOrderType.sell)
- {
- ask.price = 0;
- }
- // order was changed!
- StoreChangedOrder(bid);
- }
- if (ask.volume != askVolume && !ask.m_IsMarketOrder)
- {
- // remove offending market orders and resume matching
- if (firstBuyLimit == null && ask.type == DbOrderType.sell)
- {
- RemoveOrder(ask, dataBase, asks, DbCancelledOrderReason.insufficientLiquidity);
- }
- if (firstSellLimit == nul, DbCancelledOrderReason.insufficientLiquidity);
- }
- }
- }
- // remove all market orders still in the order-book
- string uidString = "";
- }
- foreach (DbOrder o in bids)
- {
- if (o.m_IsMarketOrder)
- {
- uidString += "," + o.uid;
- }
- }
- if (uidString.Length > 0)
- {celled_orders SELECT o.*,@no_reason FROM orders o WHERE uid IN (" + uidString + ");", DbCancelledOrderReason.insufficientLiquidity);
- dataBase.Statement("DELETE FROM orders WHERE uid IN (" + uidString + ");");
- dataBase.EndTransaction();
- }
- catch (Exception e)
- {
- dataBase.RollbackTransaction();
- // log this
- RedisWrapper.LogException(ExchangeExceptionType.MatchingEngineException, "", e.ToString());
- }
- asks.RemoveAll((o) => o.m_IsMarketOrder);
- bids.RemoveAll((o) => o.m_IsMarketOrder);
- }
- // keep the public orderbook up to date
- RedisWrapper.UpdatePublicOrderbook(bids, asks, baseCurrency, quoteCurrency, Routes.kGetOrderbook);
- // update recent transactions
- if (transUid != 0)
- {
- RedisWrapper.InvalidatePublicCacheForCurrency(baseCurrency, quoteCurrency, Routes.kGetTransactionsPublic);
- RedisWrapper.InvalidatePublicCacheForCurrency(baseCurrency, quoteCurrency, Routes.kGetOhlcv);
- RedisWrapper.UpdateStreamingTransactions(recentTransactions, baseCurrency, quoteCurrency);
- }
- // invalidate private caches
- foreach (uint user in uniqueUsers)
- {
- RedisWrapper.InvalidatePrivateCacheForUser(user, Routes.kGetBalances);
- RedisWrapper.InvalidatePrivateCacheForCurrency(user, baseCurrency, quoteCurrency, Routes.kGetOrdersPrivate);
- RedisWrapper.InvalidatePrivateCacheForCurrency(user, baseCurrency, quoteCurrency, Routes.kGetTransactionsPrivate);
- // store any changes in open orders for the streaming system
- if (m_changedOrders.ContainsKey(user))
- /// <summary>
- ///
- /// </summary>
- /// <param name="allOrders"></param>
- /// <param name="bids"></param>
- /// <param name="asks"></param>
- void SeparateIntoBidsAndAsks(List<DbOrder> allOrders, out List<DbOrder> bids, out List<DbOrder> asks)
- {
- bids = new List<DbOrder>();
- asks = new List<DbOrder>();
- // sorted from high to low by mysql, so asks first, then bids
- asks[i].price)
- {
- i++;
- }
- asks.Insert(i, o);
- }
- else
- {
- asks.Insert(0, o);
- }
- }
- else
- {
- bids.Add(o);
- }
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement