/// <summary>There's a dump on market, best time to SELL.</summary> private static bool dump(TradeStatisticsResponse tradeHistory) { //Safety in case Huobi returned broken response if (null == tradeHistory.trades) return false; //TODO: do lot of vain runs, watch carefully and tune the magic numbers here //Activity criterion: if mean time between recent trades is 5 seconds or less, it's high activity const int MAX_SPEED = 5; var uniqueTradesCount = 1; var upTrades = 0; var downTrades = 0; var lastTime = tradeHistory.trades.Last().TimeTyped; var lastPrice = tradeHistory.trades.Last().price; double totalSeconds = 0.0; for (int i = tradeHistory.trades.Count-1; i >= 0; i--) { var trade = tradeHistory.trades[i]; if (trade.TimeTyped != lastTime) { totalSeconds += (trade.TimeTyped - lastTime).TotalSeconds; if (trade.price < lastPrice) downTrades++; else if (trade.price > lastPrice) upTrades++; else if (TradeType.SELL == trade.Type) downTrades++; else upTrades++; uniqueTradesCount++; lastTime = trade.TimeTyped; lastPrice = trade.price; } } double avgSeconds = totalSeconds/uniqueTradesCount; if (avgSeconds > MAX_SPEED) return false; //Trend criterion: at least 75% of recent trades are down trend, means lower price than previous trade const double MIN_DOWN_TRADES = 0.75; double downPercentage = (double)downTrades / (upTrades + downTrades); if (downPercentage < MIN_DOWN_TRADES) return false; //Price criterion: price has fallen enough, that is at least 6 CNY per BTC const double MIN_PRICE_DIFF = 6.0; var startPrice = tradeHistory.trades.First().price; var endPrice = tradeHistory.trades.Last().price; return (startPrice > endPrice && startPrice - endPrice >= MIN_PRICE_DIFF); }
/// <summary> /// Returns non-NULL description of BUY reason for bearish bot. /// </summary> internal string ReasonToBuyBack(List<Candle> candles, TradeStatisticsResponse tradeHistory) { if (pump(tradeHistory)) return "PUMP"; if (isRising(candles)) return "Significant rise"; return null; }
/// <summary> /// Returns NULL if market doesn't give enough SELL signals, description of SELL reason otherwise. Good for BEAR strategy. /// </summary> internal string ReasonToSell(List<Candle> candles, TradeStatisticsResponse tradeHistory) { if (dump(tradeHistory)) return "DUMP"; if (volumeDeclineAfterPriceRise(candles)) return "Volume and price decline after price rise"; //TODO: recognition of all the other patterns return null; }
/// <summary>Returns numeric indicator of market activity. Higher value means higher activity (i.e. lot of trades with higher volume).</summary> /// <param name="tradeStats">Huobi statistics about trading activity</param> /// <param name="now">Current local time of the exchange</param> /// <returns>Coeficient in [0.0, 1.0] where 0.0 means totally peacefull market, 1.0 is wild.</returns> internal static float GetMadness(TradeStatisticsResponse tradeStats, DateTime now) { //For case we have broken data returned from server if (null == tradeStats || null == tradeStats.trades || !tradeStats.trades.Any() || DateTime.MinValue == now) return 0.75f; //There's always exactly 60 past trades var trades = tradeStats.trades.Where(trade => trade.TimeTyped >= now.AddSeconds(-120)).ToList(); if (!trades.Any()) return 0.0f; //Group by time, so that single trade with big volume doesn't look like many trades var groupped = new Dictionary<string, Trade>(); foreach (var trade in trades) { var key = trade.time + "_" + trade.type; if (!groupped.ContainsKey(key)) groupped.Add(key, new Trade { time = trade.time, type = trade.type, amount = trade.amount, price = trade.price }); else { groupped[key].amount += trade.amount; if (TradeType.BUY == trade.Type && trade.amount > groupped[key].amount) groupped[key].amount = trade.amount; else if (TradeType.SELL == trade.Type && trade.amount < groupped[key].amount) groupped[key].amount = trade.amount; } } // Console.WriteLine("DEBUG: past {0} trades, {1} groupped by time", tradeStats.trades.Count, groupped.Count); var firstTradeTime = tradeStats.trades.Last().TimeTyped; var lastTradeTime = tradeStats.trades.First().TimeTyped; var tradesTimeRange = lastTradeTime - firstTradeTime; var MIN_SPEED = new TimeSpan(0, 6, 0); //All the trades spread in 6 minutes var MAX_SPEED = new TimeSpan(0, 2, 0); float intenseCoef; if (tradesTimeRange > MIN_SPEED) //Trading speed (trades/time) too low intenseCoef = 0.0f; else if (tradesTimeRange < MAX_SPEED) intenseCoef = 1.0f; else intenseCoef = 1.0f - (float) ((tradesTimeRange - MAX_SPEED).TotalSeconds / (MIN_SPEED - MAX_SPEED).TotalSeconds); const double MIN_AVG_VOLUME = 0.8; const double MAX_AVG_VOLUME = 20.0; float volumeCoef; double avgVolume = groupped.Sum(trade => trade.Value.amount) / groupped.Count; // Console.WriteLine("DEBUG: avgVolume={0}", avgVolume); if (avgVolume < MIN_AVG_VOLUME) volumeCoef = 0.0f; else if (avgVolume >= MAX_AVG_VOLUME) volumeCoef = 1.0f; else volumeCoef = (float)((avgVolume - MIN_AVG_VOLUME) / (MAX_AVG_VOLUME - MIN_AVG_VOLUME)); // Console.WriteLine("DEBUG: intensityCoef={0}, volumeCoef={1}", intenseCoef, volumeCoef); //Average of volume and frequency coeficients return (intenseCoef + volumeCoef) / 2; }
/* * TODO: * - connect to Huobi * * - one (or few) out-of-trend intervals can't fool the overall trend * */ /// <summary> /// Market sentiment indicator in [-1.0, 1.0]. Closer to -1.0 indicates BTC price is falling, 1.0 means it's rising, /// around 0.0 is peaceful market /// </summary> internal float GetSentiment(TradeStatisticsResponse tradeHistory) { throw new NotImplementedException("TODO"); }