// Given a dictionary of starting balances, create min/max values based on given percentages (upPct/downPct)
        public static BalanceMinMaxMap CreatePercentageUpDown(IDictionary <string, ZAccountBalance> balances, decimal upPct, decimal downPct)
        {
            var limits = new BalanceMinMaxMap();

            foreach (var kv in balances)
            {
                var free = balances[kv.Key].Free;
                limits.Add(kv.Key, free - (downPct / 100 * free), free + (upPct / 100 * free));
            }
            return(limits);
        }
        // BINANCE ARBS #1
        public static void TestBinanceArbs1()
        {
            // Create Prowl messaging client
            var msg1 = new Tools.Messaging.Prowl.ProwlClient(Crypto.ApiKeys["PROWL"].ApiKey, "BINANCE", "MACD");

            var assetInfo = binance.GetAssetInfo();
            //assetInfo.Select(xkv => xkv).OrderBy(xkv => xkv.Key).ToList().ForEach(xai => cout(xai.Value.ToDisplay()));

            // Create a BalanceMinMaxMap that we will pass to ArbZero to set our Min/Max currency balance limits
            var balances = binance.GetAccountBalances();
            var limits   = BalanceMinMaxMap.CreatePercentageUpDown(balances, 50, 50);   // set limits to up or down 50% from current balances

            var args = new List <ArbZeroArgs>();

            args.AddRange(GetArgs("BCC", 0.01M));
            args.AddRange(GetArgs("LTC", 0.1M));
            args.AddRange(GetArgs("NEO", 0.1M));

            Task.Run(() => ArbOne(args, limits, true, 0.14M, 4001));
        }
        // BINANCE ARB #1
        public static void ArbOne(List <ArbZeroArgs> args, BalanceMinMaxMap balanceLimits, bool enableLiveOrders, decimal targetPct = 0.2M, int?threadSleepMillis = null)
        {
            var rnd = new Random();

            int sleepMillis = 0;

            if (threadSleepMillis == null)
            {
                sleepMillis = 5000 + rnd.Next(-2000, 2000);
            }
            else
            {
                sleepMillis = threadSleepMillis.Value;
            }

            cout("\nSLEEP_MILLIS = {0}\n", sleepMillis);

            var pairs     = GetBinancePairs();
            var assetInfo = binance.GetAssetInfo();

            binance.EnableLiveOrders = enableLiveOrders; // turn on/off live orders

            const int MAX_ORDERS = 100;                  //100000;
            int       norders    = 0;

            const int MIN_PRIME_COUNT = 2;
            int       nprime          = 0;

            // THREAD LOOP STARTS HERE
            ZTicker t1, t2, t3;

            while (true)
            {
                // Get the balances in each currency (we will check these against our balanceLimits Min/Max values)
                var balances = binance.GetAccountBalances();

                // Get all the tickers as a dictionary
                var bnd = binance.GetAllTickers().Result;

                var bxd = bittrex.GetAllTickers();

                // Loop through each symbol set that was passed as ArbZeroArgs
                foreach (var aza in args)
                {
                    var symbols = aza.symbols;
                    var size    = aza.size;

                    t1 = bnd[symbols[0]];
                    t2 = bnd[symbols[1]];
                    t3 = bnd[symbols[2]];


                    var buy1    = t1.Ask;
                    var buy2    = t2.Bid * t3.Ask;
                    var buypct  = (buy2 - buy1) / buy2 * 100;
                    var sell1   = t1.Bid;
                    var sell2   = t2.Ask * t3.Bid;
                    var sellpct = (sell1 - sell2) / sell1 * 100;

                    if (nprime >= MIN_PRIME_COUNT)
                    {
                        // Check tickers for BUY signal
                        if (buypct >= targetPct)
                        {
                            if (norders < MAX_ORDERS)
                            {
                                var pair0     = pairs[symbols[0]];
                                var lbalance0 = balances[pair0.Left];
                                var rbalance0 = balances[pair0.Right];
                                var llimit0   = balanceLimits[pair0.Left];
                                var rlimit0   = balanceLimits[pair0.Right];
                                if (lbalance0.Free < llimit0.Max && rbalance0.Free > rlimit0.Min)
                                {
                                    var pair1     = pairs[symbols[1]];
                                    var lbalance1 = balances[pair1.Left];
                                    var rbalance1 = balances[pair1.Right];
                                    var llimit1   = balanceLimits[pair1.Left];
                                    var rlimit1   = balanceLimits[pair1.Right];
                                    if (lbalance1.Free > llimit1.Min && rbalance1.Free < rlimit1.Max)
                                    {
                                        var pair2     = pairs[symbols[2]];
                                        var lbalance2 = balances[pair2.Left];
                                        var rbalance2 = balances[pair2.Right];
                                        var llimit2   = balanceLimits[pair2.Left];
                                        var rlimit2   = balanceLimits[pair2.Right];
                                        if (lbalance2.Free < llimit2.Max && rbalance2.Free > rlimit2.Min)
                                        {
                                            int places         = binance.RoundSize(symbols[2]);
                                            var calculatedSize = Math.Round(size * t2.Bid, places);
                                            binance.SubmitLimitOrder(symbols[0], OrderSide.Buy, t1.Ask, size);
                                            binance.SubmitLimitOrder(symbols[1], OrderSide.Sell, t2.Bid, size);
                                            binance.SubmitLimitOrder(symbols[2], OrderSide.Buy, t3.Ask, calculatedSize);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[0], OrderSide.Buy, t1.Ask, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[1], OrderSide.Sell, t2.Bid, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[2], OrderSide.Buy, t3.Ask, calculatedSize);
                                            // Adjust the balances for these orders/trades (assume they will be filled)
                                            lbalance0.Free += size;
                                            rbalance2.Free -= calculatedSize;
                                        }
                                        else
                                        {
                                            cout("+++Ignoring BUY signal due to {0} limits", symbols[2]);
                                        }
                                    }
                                    else
                                    {
                                        cout("+++Ignoring BUY signal due to {0} limits", symbols[1]);
                                    }
                                }
                                else
                                {
                                    cout("+++Ignoring BUY signal due to {0} limits", symbols[0]);
                                }
                            }
                            ++norders;
                        }

                        // Check tickers for SELL signal
                        if (sellpct >= targetPct)
                        {
                            if (norders < MAX_ORDERS)
                            {
                                var pair0     = pairs[symbols[0]];
                                var lbalance0 = balances[pair0.Left];
                                var rbalance0 = balances[pair0.Right];
                                var llimit0   = balanceLimits[pair0.Left];
                                var rlimit0   = balanceLimits[pair0.Right];
                                if (lbalance0.Free > llimit0.Min && rbalance0.Free < rlimit0.Max)
                                {
                                    var pair1     = pairs[symbols[1]];
                                    var lbalance1 = balances[pair1.Left];
                                    var rbalance1 = balances[pair1.Right];
                                    var llimit1   = balanceLimits[pair1.Left];
                                    var rlimit1   = balanceLimits[pair1.Right];
                                    if (lbalance1.Free < llimit1.Max && rbalance1.Free > rlimit1.Min)
                                    {
                                        var pair2     = pairs[symbols[2]];
                                        var lbalance2 = balances[pair2.Left];
                                        var rbalance2 = balances[pair2.Right];
                                        var llimit2   = balanceLimits[pair2.Left];
                                        var rlimit2   = balanceLimits[pair2.Right];
                                        if (lbalance2.Free > llimit2.Min && rbalance2.Free < rlimit2.Max)
                                        {
                                            int places         = binance.RoundSize(symbols[2]);
                                            var calculatedSize = Math.Round(size * t2.Ask, places);
                                            binance.SubmitLimitOrder(symbols[0], OrderSide.Sell, t1.Bid, size);
                                            binance.SubmitLimitOrder(symbols[1], OrderSide.Buy, t2.Ask, size);
                                            binance.SubmitLimitOrder(symbols[2], OrderSide.Sell, t3.Bid, calculatedSize);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[0], OrderSide.Sell, t1.Bid, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[1], OrderSide.Buy, t2.Ask, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[2], OrderSide.Sell, t3.Bid, calculatedSize);
                                            // Adjust the balances for these orders/trades (assume they will be filled)
                                            lbalance0.Free -= size;
                                            rbalance2.Free += calculatedSize;
                                        }
                                        else
                                        {
                                            cout("+++Ignoring SELL signal due to {0} limits", symbols[2]);
                                        }
                                    }
                                    else
                                    {
                                        cout("+++Ignoring SELL signal due to {0} limits", symbols[1]);
                                    }
                                }
                                else
                                {
                                    cout("+++Ignoring SELL signal due to {0} limits", symbols[0]);
                                }
                            }
                            ++norders;
                        }
                    }

                    //cout("{0}\n{1}\n{2}", t1.ToDisplay(), t2.ToDisplay(), t3.ToDisplay());

                    var    bsize1 = t1.AskSize;
                    var    bsize2 = Math.Min(t2.BidSize, t3.AskSize);
                    string btext  = (buy1 < buy2 ? "BUY" : "buy");
                    string bbtext = (buy1 < buy2 ? string.Format("BUY {0:#.000}%", buypct) : "      ");
                    var    ssize1 = t1.BidSize;
                    var    ssize2 = Math.Min(t2.AskSize, t3.BidSize);
                    string stext  = (sell1 > sell2 ? "SELL" : "sell");
                    string sstext = (sell1 > sell2 ? string.Format("SELL {0:#.000}%", sellpct) : "       ");
                    cout("{0}: {1} x {2} < {3} x {4}    {5}: {6} x {7} > {8} x {9}     {10} {11}", btext, bsize1, buy1, buy2, bsize2, stext, ssize1, sell1, sell2, ssize2, bbtext, sstext);
                }

                ++nprime;

                Thread.Sleep(sleepMillis);
            }
        }
        //var symbols = new string[] { "BCCUSDT", "BCCBTC", "BTCUSDT" };
        //decimal size = 0.01M;
        //int? threadSleepMillis = (int?)5000;
        public static void ArbZero(List <ArbZeroArgs> args, BalanceMinMaxMap balanceLimits, bool enableLiveOrders, decimal targetPct = 0.2M, int?threadSleepMillis = null)
        {
            var rnd = new Random();

            int sleepMillis = 0;

            if (threadSleepMillis == null)
            {
                sleepMillis = 5000 + rnd.Next(-2000, 2000);
            }
            else
            {
                sleepMillis = threadSleepMillis.Value;
            }

            cout("\nSLEEP_MILLIS = {0}\n", sleepMillis);

            var pairs     = GetBinancePairs();
            var assetInfo = binance.GetAssetInfo();

            binance.EnableLiveOrders = enableLiveOrders; // turn on/off live orders

            const int MAX_ORDERS = 100;                  //100000;
            int       norders    = 0;

            const int MIN_PRIME_COUNT = 2;
            int       nprime          = 0;

            // THREAD LOOP STARTS HERE
            //DateTime startTime;
            //double elapsed;
            //List<double> millis = new List<double>();
            ZTicker t1, t2, t3;

            while (true)
            {
                // Get the balances in each currency (we will check these against our balanceLimits Min/Max values)
                var balances = binance.GetAccountBalances();

                // Get all the tickers as a dictionary
                var d = binance.GetAllTickers().Result;

                // Loop through each symbol set that was passed as ArbZeroArgs
                foreach (var aza in args)
                {
                    var symbols = aza.symbols;
                    var size    = aza.size;

                    t1 = d[symbols[0]];
                    t2 = d[symbols[1]];
                    t3 = d[symbols[2]];

                    // THIS WHOLE SECTION WAS TO SPEED TEST VARIOUS GET TICKER METHODS

                    /*int calc = 2;
                     * if (calc == 1)          // use GetFastTicker
                     * {
                     *  //startTime = DateTime.Now;
                     *  t1 = binance.GetBookTicker(symbols[0]);
                     *  t2 = binance.GetBookTicker(symbols[1]);
                     *  t3 = binance.GetBookTicker(symbols[2]);
                     *  //elapsed = DateTime.Now.Subtract(startTime).TotalMilliseconds;
                     *  //millis.Add(elapsed);
                     * }
                     * else if (calc == 2)     // use GetAllTickers
                     * {
                     *  //startTime = DateTime.Now;
                     *  var d = binance.GetAllTickers();
                     *  t1 = d[symbols[0]];
                     *  t2 = d[symbols[1]];
                     *  t3 = d[symbols[2]];
                     *  //elapsed = DateTime.Now.Subtract(startTime).TotalMilliseconds;
                     *  //millis.Add(elapsed);
                     * }
                     * else                    // standard GetTicker
                     * {
                     *  //startTime = DateTime.Now;
                     *  t1 = binance.GetTicker(symbols[0]);
                     *  t2 = binance.GetTicker(symbols[1]);
                     *  t3 = binance.GetTicker(symbols[2]);
                     *  //elapsed = DateTime.Now.Subtract(startTime).TotalMilliseconds;
                     *  //millis.Add(elapsed);
                     * }*/
                    //cout("elapsed: {0:0.000} ms       avg:{1:0.000}  std:{2:0.000}", elapsed, millis.Average(), GMath.Std(millis));

                    var buy1    = t1.Ask;
                    var buy2    = t2.Bid * t3.Ask;
                    var buypct  = (buy2 - buy1) / buy2 * 100;
                    var sell1   = t1.Bid;
                    var sell2   = t2.Ask * t3.Bid;
                    var sellpct = (sell1 - sell2) / sell1 * 100;

                    if (nprime >= MIN_PRIME_COUNT)
                    {
                        // Check tickers for BUY signal
                        if (buypct >= targetPct)
                        {
                            if (norders < MAX_ORDERS)
                            {
                                var pair0     = pairs[symbols[0]];
                                var lbalance0 = balances[pair0.Left];
                                var rbalance0 = balances[pair0.Right];
                                var llimit0   = balanceLimits[pair0.Left];
                                var rlimit0   = balanceLimits[pair0.Right];
                                if (lbalance0.Free < llimit0.Max && rbalance0.Free > rlimit0.Min)
                                {
                                    var pair1     = pairs[symbols[1]];
                                    var lbalance1 = balances[pair1.Left];
                                    var rbalance1 = balances[pair1.Right];
                                    var llimit1   = balanceLimits[pair1.Left];
                                    var rlimit1   = balanceLimits[pair1.Right];
                                    if (lbalance1.Free > llimit1.Min && rbalance1.Free < rlimit1.Max)
                                    {
                                        var pair2     = pairs[symbols[2]];
                                        var lbalance2 = balances[pair2.Left];
                                        var rbalance2 = balances[pair2.Right];
                                        var llimit2   = balanceLimits[pair2.Left];
                                        var rlimit2   = balanceLimits[pair2.Right];
                                        if (lbalance2.Free < llimit2.Max && rbalance2.Free > rlimit2.Min)
                                        {
                                            int places = binance.RoundSize(symbols[2]);
                                            //int places = m_binanceRoundLotSize[assetInfo[symbols[2]].stepSize];
                                            var calculatedSize = Math.Round(size * t2.Bid, places);
                                            binance.SubmitLimitOrder(symbols[0], OrderSide.Buy, t1.Ask, size);
                                            binance.SubmitLimitOrder(symbols[1], OrderSide.Sell, t2.Bid, size);
                                            binance.SubmitLimitOrder(symbols[2], OrderSide.Buy, t3.Ask, calculatedSize);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[0], OrderSide.Buy, t1.Ask, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[1], OrderSide.Sell, t2.Bid, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[2], OrderSide.Buy, t3.Ask, calculatedSize);
                                            // Adjust the balances for these orders/trades (assume they will be filled)
                                            lbalance0.Free += size;
                                            rbalance2.Free -= calculatedSize;
                                        }
                                        else
                                        {
                                            cout("+++Ignoring BUY signal due to {0} limits", symbols[2]);
                                        }
                                    }
                                    else
                                    {
                                        cout("+++Ignoring BUY signal due to {0} limits", symbols[1]);
                                    }
                                }
                                else
                                {
                                    cout("+++Ignoring BUY signal due to {0} limits", symbols[0]);
                                }
                            }
                            ++norders;
                        }

                        // Check tickers for SELL signal
                        if (sellpct >= targetPct)
                        {
                            if (norders < MAX_ORDERS)
                            {
                                var pair0     = pairs[symbols[0]];
                                var lbalance0 = balances[pair0.Left];
                                var rbalance0 = balances[pair0.Right];
                                var llimit0   = balanceLimits[pair0.Left];
                                var rlimit0   = balanceLimits[pair0.Right];
                                if (lbalance0.Free > llimit0.Min && rbalance0.Free < rlimit0.Max)
                                {
                                    var pair1     = pairs[symbols[1]];
                                    var lbalance1 = balances[pair1.Left];
                                    var rbalance1 = balances[pair1.Right];
                                    var llimit1   = balanceLimits[pair1.Left];
                                    var rlimit1   = balanceLimits[pair1.Right];
                                    if (lbalance1.Free < llimit1.Max && rbalance1.Free > rlimit1.Min)
                                    {
                                        var pair2     = pairs[symbols[2]];
                                        var lbalance2 = balances[pair2.Left];
                                        var rbalance2 = balances[pair2.Right];
                                        var llimit2   = balanceLimits[pair2.Left];
                                        var rlimit2   = balanceLimits[pair2.Right];
                                        if (lbalance2.Free > llimit2.Min && rbalance2.Free < rlimit2.Max)
                                        {
                                            int places         = binance.RoundSize(symbols[2]);
                                            var calculatedSize = Math.Round(size * t2.Ask, places);
                                            binance.SubmitLimitOrder(symbols[0], OrderSide.Sell, t1.Bid, size);
                                            binance.SubmitLimitOrder(symbols[1], OrderSide.Buy, t2.Ask, size);
                                            binance.SubmitLimitOrder(symbols[2], OrderSide.Sell, t3.Bid, calculatedSize);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[0], OrderSide.Sell, t1.Bid, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[1], OrderSide.Buy, t2.Ask, size);
                                            cout("---Crypto::ArbZero=> '{0}' {1} {2} {3}", symbols[2], OrderSide.Sell, t3.Bid, calculatedSize);
                                            // Adjust the balances for these orders/trades (assume they will be filled)
                                            lbalance0.Free -= size;
                                            rbalance2.Free += calculatedSize;
                                        }
                                        else
                                        {
                                            cout("+++Ignoring SELL signal due to {0} limits", symbols[2]);
                                        }
                                    }
                                    else
                                    {
                                        cout("+++Ignoring SELL signal due to {0} limits", symbols[1]);
                                    }
                                }
                                else
                                {
                                    cout("+++Ignoring SELL signal due to {0} limits", symbols[0]);
                                }
                            }
                            ++norders;
                        }
                    }

                    //cout("{0}\n{1}\n{2}", t1.ToDisplay(), t2.ToDisplay(), t3.ToDisplay());

                    var    bsize1 = t1.AskSize;
                    var    bsize2 = Math.Min(t2.BidSize, t3.AskSize);
                    string btext  = (buy1 < buy2 ? "BUY" : "buy");
                    string bbtext = (buy1 < buy2 ? string.Format("BUY {0:#.000}%", buypct) : "      ");
                    var    ssize1 = t1.BidSize;
                    var    ssize2 = Math.Min(t2.AskSize, t3.BidSize);
                    string stext  = (sell1 > sell2 ? "SELL" : "sell");
                    string sstext = (sell1 > sell2 ? string.Format("SELL {0:#.000}%", sellpct) : "       ");
                    cout("{0}: {1} x {2} < {3} x {4}    {5}: {6} x {7} > {8} x {9}     {10} {11}", btext, bsize1, buy1, buy2, bsize2, stext, ssize1, sell1, sell2, ssize2, bbtext, sstext);

                    /*buy1 = t1.Bid;
                     * buy2 = t2.Bid * t3.Ask;
                     * bsize1 = t1.BidSize;
                     * bsize2 = Math.Min(t2.BidSize, t3.AskSize);
                     * btext = (buy1 < buy2 ? "BUY" : "buy");
                     * sell1 = t1.Ask;
                     * sell2 = t2.Ask * t3.Bid;
                     * ssize1 = t1.AskSize;
                     * ssize2 = Math.Min(t2.AskSize, t3.BidSize);
                     * stext = (sell1 > sell2 ? "SELL" : "sell");
                     * cout("{0}: {1} x {2} < {3} x {4}    {5}: {6} x {7} > {8} x {9}\n", btext, bsize1, buy1, buy2, bsize2, stext, ssize1, sell1, sell2, ssize2);
                     */
                }

                ++nprime;

                Thread.Sleep(sleepMillis);
            }
        }