protected override PriceCalculationRow[] GetMaximumVolumeRows(PriceCalculationRow[] table) { decimal maxVolume = 0; foreach (PriceCalculationRow row in table) { row.TradeableQuantity = Math.Min(row.AggregateBuyQuantity, row.AggregateSellQuantity); if (row.TradeableVolume > maxVolume) maxVolume = row.TradeableVolume; } return table.Where(r => r.TradeableVolume == maxVolume).ToArray(); }
protected override PriceCalculationRow[] GetMinimumOrderImbalanceRows(PriceCalculationRow[] table) { decimal minImbalance = decimal.MaxValue; foreach (PriceCalculationRow row in table) { row.NormalOrderImbalance = Math.Abs(row.AggregateBuyQuantity - row.AggregateSellQuantity) * row.Price; if (row.NormalOrderImbalance < minImbalance) minImbalance = row.NormalOrderImbalance; } return table.Where(r => r.NormalOrderImbalance == minImbalance).ToArray(); }
protected override PriceCalculationRow[] GetMaximumQuantityRows(PriceCalculationRow[] table) { int maxQuantity = 0; foreach (PriceCalculationRow row in table) { row.TradeableQuantity = Math.Min(row.AggregateBuyQuantity, row.AggregateSellQuantity); if (row.TradeableQuantity > maxQuantity) maxQuantity = row.TradeableQuantity; } return table.Where(r => r.TradeableQuantity == maxQuantity).ToArray(); }
/// <summary> /// První kritérium pro výpočet ceny. Pro všechny ceny v tabulce spočítá objem, který by za /// danou cenu mohl být zobchodován, a vybere cenu, pro niž je zobchodovatelný objem největší. /// Pokud je taková cena jedinečná, vrátí ji jako výsledek, v opačném případě předá vhodné /// kandidáty metodě <code>GetPriceByNormalOrderImbalance</code> hodnotící druhé kritérium. /// </summary> /// <param name="table"></param> /// <returns></returns> private decimal GetPriceByMaximumTradeableVolume(PriceCalculationRow[] table) { PriceCalculationRow[] maxVolumeRows = GetMaximumVolumeRows(table); if (Debug) { Recorder.Instance.WriteLine("\r\nChecking MaximumTradeableVolume Criterion..."); Recorder.Instance.Print(table); } switch (maxVolumeRows.Length) { case 0: throw new NullReferenceException("maxVolumeRows.Length == 0"); case 1: return maxVolumeRows[0].Price; default: return GetPriceByNormalOrderImbalance(maxVolumeRows); } }
/// <summary> /// Čtvrté a páté kritérium pro výpočet ceny. Spočítá průmernou cenu ze (sub)tabulky; pokud je /// násobkem základní jednotky tickSize, vrátí ji jako výsledek. /// V opačném případě se rozhoduje podle předchozí ceny: pokud byla vyšší než nyní spočítaná /// průměrná cena, zaokrouhlí se průměrná cena na nejbližší základní jednotku směrem nahoru, /// v opačném případě směrem dolů. /// </summary> /// <param name="table"></param> /// <returns></returns> private decimal GetPriceByAverage(PriceCalculationRow[] table) { if (Debug) { Recorder.Instance.WriteLine("\r\nChecking Average Criterion..."); Recorder.Instance.Print(table); } decimal averagePrice = table.Average(r => r.Price); if (averagePrice % tickSize == 0) return averagePrice; if (lastPrice > averagePrice) return tickSize * Math.Ceiling(averagePrice / tickSize); return tickSize * Math.Floor(averagePrice / tickSize); }
/// <summary> /// Vloží do tabulky <code>table</code> objednávky na prodej seřazené VZESTUPNĚ. /// Tato metoda může být volána po vložení objednávek na nákup. /// Výsledkem je sestupně seřazená tabulka podle ceny. /// </summary> /// <param name="sellList">Seznam objednávek na prodej.</param> /// <param name="table">Tabulka, do které mají být objednávky vloženy.</param> private void InsertSellOrders(Order[] sellList, IList <PriceCalculationRow> table) { int rowIndex = table.Count - 1; PriceCalculationRow lastRow = table[rowIndex]; int aggregatePreviousRowsQuantity = 0; foreach (Order sellOrder in sellList) { decimal price = sellOrder.ProposedPrice; int amount = sellOrder.Amount; /// Zapsani prodejniho mnozstvi, posun v tabulce nahoru (tj. k vyssi cene) while (lastRow.Price < price && rowIndex > 0) { lastRow.AggregateSellQuantity = aggregatePreviousRowsQuantity; rowIndex--; lastRow = table[rowIndex]; } /// Narazili jsme na cenu, ktera nebyla obsazena v "buy" tabulce, musi se vlozit if (lastRow.Price > price) { PriceCalculationRow newRow = new PriceCalculationRow(price); newRow.AggregateBuyQuantity = lastRow.AggregateBuyQuantity; // nakup jako za vyssi cenu rowIndex++; // posunem se o krok zpatky, niz v tabulce lastRow = newRow; table.Insert(rowIndex, newRow); } /// Cenu jsme v tabulce nasli, nebo ji vlozili. V kazdem pripade pro ni zvysime mnozstvi. /// Zapis mnozstvi probehne v cyklu posunu po tabulce aggregatePreviousRowsQuantity += amount; } /// Na zaver vyplnime mnozstvi pro vsechny zbyvajiji nejvyssi ceny z "buy" tabulky for (int index = 0; index <= rowIndex; index++) { table[index].AggregateSellQuantity = aggregatePreviousRowsQuantity; } }
/// <summary> /// Vloží do (prázdné) tabulky <code>table</code> objednávky na nákup seřazené SESTUPNĚ. /// Tato metoda musí být volána před vložením objednávek na prodej! /// </summary> /// <param name="buyList">Seznam objednávek na nákup.</param> /// <param name="table">Tabulka, do které mají být objednávky vloženy.</param> private void InsertBuyOrders(Order[] buyList, IList <PriceCalculationRow> table) { PriceCalculationRow previousRow = null; int aggregatePreviousRowsQuantity = 0; foreach (Order buyOrder in buyList) { decimal price = buyOrder.ProposedPrice; int amount = buyOrder.Amount; if (previousRow == null || previousRow.Price != price) // vkladame novou cenu { PriceCalculationRow row = new PriceCalculationRow(price); row.AggregateBuyQuantity = amount + aggregatePreviousRowsQuantity; table.Add(row); previousRow = row; } else // takova cena uz existuje { previousRow.AggregateBuyQuantity += amount; } aggregatePreviousRowsQuantity = previousRow.AggregateBuyQuantity; } }
/// <summary> /// Vloží do (prázdné) tabulky <code>table</code> objednávky na nákup seřazené SESTUPNĚ. /// Tato metoda musí být volána před vložením objednávek na prodej! /// </summary> /// <param name="buyList">Seznam objednávek na nákup.</param> /// <param name="table">Tabulka, do které mají být objednávky vloženy.</param> private void InsertBuyOrders(Order[] buyList, IList<PriceCalculationRow> table) { PriceCalculationRow previousRow = null; int aggregatePreviousRowsQuantity = 0; foreach (Order buyOrder in buyList) { decimal price = buyOrder.ProposedPrice; int amount = buyOrder.Amount; if (previousRow == null || previousRow.Price != price) // vkladame novou cenu { PriceCalculationRow row = new PriceCalculationRow(price); row.AggregateBuyQuantity = amount + aggregatePreviousRowsQuantity; table.Add(row); previousRow = row; } else // takova cena uz existuje { previousRow.AggregateBuyQuantity += amount; } aggregatePreviousRowsQuantity = previousRow.AggregateBuyQuantity; } }
/// <summary> /// Vloží do tabulky <code>table</code> objednávky na prodej seřazené VZESTUPNĚ. /// Tato metoda může být volána po vložení objednávek na nákup. /// Výsledkem je sestupně seřazená tabulka podle ceny. /// </summary> /// <param name="sellList">Seznam objednávek na prodej.</param> /// <param name="table">Tabulka, do které mají být objednávky vloženy.</param> private void InsertSellOrders(Order[] sellList, IList<PriceCalculationRow> table) { int rowIndex = table.Count - 1; PriceCalculationRow lastRow = table[rowIndex]; int aggregatePreviousRowsQuantity = 0; foreach (Order sellOrder in sellList) { decimal price = sellOrder.ProposedPrice; int amount = sellOrder.Amount; /// Zapsani prodejniho mnozstvi, posun v tabulce nahoru (tj. k vyssi cene) while (lastRow.Price < price && rowIndex > 0) { lastRow.AggregateSellQuantity = aggregatePreviousRowsQuantity; rowIndex--; lastRow = table[rowIndex]; } /// Narazili jsme na cenu, ktera nebyla obsazena v "buy" tabulce, musi se vlozit if (lastRow.Price > price) { PriceCalculationRow newRow = new PriceCalculationRow(price); newRow.AggregateBuyQuantity = lastRow.AggregateBuyQuantity; // nakup jako za vyssi cenu rowIndex++; // posunem se o krok zpatky, niz v tabulce lastRow = newRow; table.Insert(rowIndex, newRow); } /// Cenu jsme v tabulce nasli, nebo ji vlozili. V kazdem pripade pro ni zvysime mnozstvi. /// Zapis mnozstvi probehne v cyklu posunu po tabulce aggregatePreviousRowsQuantity += amount; } /// Na zaver vyplnime mnozstvi pro vsechny zbyvajiji nejvyssi ceny z "buy" tabulky for (int index = 0; index <= rowIndex; index++) { table[index].AggregateSellQuantity = aggregatePreviousRowsQuantity; } }
protected abstract PriceCalculationRow[] GetMinimumOrderImbalanceRows(PriceCalculationRow[] table);
protected abstract PriceCalculationRow[] GetMaximumVolumeRows(PriceCalculationRow[] table);
protected abstract PriceCalculationRow[] GetMaximumQuantityRows(PriceCalculationRow[] table);
/// <summary> /// Třetí kritérium pro výpočet ceny. Pro všechny ceny v (sub)tabulce zkontroluje, zda převažuje /// nabídka nad poptávkou, nebo naopak. Pokud ve všech případech převažuje poptávka, vybere /// z možných cen největší. Pokud ve všech případech převažuje nabídka, vybere nejnižší cenu. /// Ve zbývajících případech (TEMP!) spočítá cenu jako průměr ze zvažovaných cen. /// </summary> /// <param name="table"></param> /// <returns></returns> private decimal GetPriceByOrderImbalanceDirection(PriceCalculationRow[] table) { if (Debug) { Recorder.Instance.WriteLine("\r\nChecking OrderImbalanceDirection Criterion..."); Recorder.Instance.Print(table); } if (table.All(r => r.AggregateBuyQuantity > r.AggregateSellQuantity)) return table.Max(r => r.Price); if (table.All(r => r.AggregateBuyQuantity < r.AggregateSellQuantity)) return table.Min(r => r.Price); return GetPriceByAverage(table); }
/// <summary> /// Druhé kritérium pro výpočet ceny. Pro všechny ceny v (sub)tabulce spočítá nezobchodovatelný /// objem odpovídající dané ceně, a vybere cenu, pro niž je tato hodnota nejmenší. /// Pokud je taková cena jedinečná, vrátí ji jako výsledek, v opačném případě předá vhodné /// kandidáty metodě <code>GetPriceByOrderImbalanceDirection</code> hodnotící třetí kritérium. /// </summary> /// <param name="table"></param> /// <returns></returns> private decimal GetPriceByNormalOrderImbalance(PriceCalculationRow[] table) { PriceCalculationRow[] minImbalanceRows = GetMinimumOrderImbalanceRows(table); if (Debug) { Recorder.Instance.WriteLine("\r\nChecking NormalOrderImbalance Criterion..."); Recorder.Instance.Print(table); } switch (minImbalanceRows.Length) { case 0: throw new NullReferenceException("minImbalanceRows.Length == 0"); case 1: return minImbalanceRows[0].Price; default: return GetPriceByOrderImbalanceDirection(minImbalanceRows); } }