//private void MinuteCandleByTicks()
        //{
        //    // 설정값
        //    var coinName = Coin.Ticker;
        //    var candleType = CandleType.Name;
        //    var candleMinute = CandleType.Minute;
        //    var candleCount = CandleCount;
        //    var feeRate = FeeRate;
        //    var tradeRate = OrderRate;
        //    var triggerRate = TriggerRate;

        //    // 캔들 갯수 많큼 캔들 가져오기
        //    var candles = ApiData.getCandle<List<Candle>>(coinName, candleType, candleCount * 2);   // 캔들 조회 (2배로 여유롭게)
        //    var currPrice = candles.First().Close;                                                  // 현재가
        //    var prevPrice = candles[1].Low;                                                         // 직전저가
        //    var highPrice = candles.GetRange(1, candleCount - 1).Max(x => x.High);                  // 최고가
        //    var downPrice = highPrice * (triggerRate + (feeRate * (candleCount - 1))) / 100;        // 하락가
        //    var triggerDownPrice = highPrice - downPrice;                                           // 매수 하락 촉발가
        //    var downRate = Math.Min(0D, (currPrice - highPrice) * 100 / highPrice);                 // 하락율
        //    var upPrice = prevPrice * (feeRate * (candleCount - 1)) / 100;                          // 반등가
        //    var triggerUpPrice = prevPrice + upPrice;                                               // 매수 반등 촉발가
        //    var upRate = (currPrice - prevPrice) * 100 / prevPrice;                                 // 반등 상승율
        //    var downUpRate = upRate <= 0 && downRate <= 0 ? 0D : (-downRate / upRate);              // 반등율

        //    // 보유현금 및 주문 정보
        //    var orderChance = ApiData.getOrdersChance(coinName);                                    // 주문 가능 정보
        //    var bid = orderChance["bid_account"];
        //    var ask = orderChance["ask_account"];
        //    var krwBalance = bid.Value<double>("balance");                                          // 보유 현금
        //    var coinVol = ask.Value<double?>("balance") ?? 0D;                                      // 보유 코인 수량
        //    var avgPrice = ask.Value<double?>("avg_buy_price") ?? 0D;                               // 매수 평단가
        //    var buyLock = bid.Value<double?>("locked") ?? 0D;                                       // 매수 대기 금액
        //    var sellLock = ask.Value<double?>("locked") ?? 0D;                                      // 매도 대기 금액
        //    var coinBuyBalance = avgPrice * coinVol;                                                // 코인 매수금
        //    var coinBalance = currPrice * coinVol;                                                  // 코인 평가금
        //    var totalBalance = krwBalance + coinBalance;                                            // 현재 자산
        //    var minTradeKRW = Settings.Default.minTradeKRW;                                         // 최소 거래 금액

        //    // 거래 쿨다운 타임
        //    var buyTs = (DateTime.Now - LastSellDate).TotalSeconds;                                 // 마지막 매도 이후 경과 시간 (초)
        //    var buyCoolDownSec = CandleType.Minute * 60 * (candleCount - 2) / (candleCount - 1);      // 매수 쿨다운 시간 (초)
        //    var buyRemainCoolDownSec = buyCoolDownSec - buyTs;                                      // 매수 쿨다운 시간 (초)

        //    var sellTs = (DateTime.Now - LastBuyDate).TotalSeconds;                                 // 마지막 매수 이후 경과 시간 (초)
        //    var sellCoolDownSec = (CandleType.Minute + (1 / candleCount)) * 60;                       // 매도 쿨다운 시간 (초)
        //    var sellRemainCoolDownSec = sellCoolDownSec - sellTs;                                   // 매도 까지 남은 시간 (초)
        //    var targetRate = ((triggerRate / 2) + (feeRate * 2)) / 100;                             // 목표 수익율
        //    var targetSellPrice = Math.Round(avgPrice * (1 + targetRate) / 10) * 10;                // 매도 목표가

        //    var now = DateTime.Now;
        //    var line1 = $"[{now:T}] {coinName}";
        //    line1 += $" : 현재/직전/최고 {currPrice:N0}/{prevPrice:N0}/{highPrice:N0}";
        //    line1 += $", 하락율/반등율 {downRate:F2}/{upRate:F2}, 평단가 {avgPrice:N0}";
        //    line1 += (sellRemainCoolDownSec > 0) ? $", 매수쿨 {sellRemainCoolDownSec:N0}(초)" : "";
        //    line1 += (buyRemainCoolDownSec > 0) ? $", 매도쿨 {buyRemainCoolDownSec:N0}(초)" : "";
        //    var line2 = $"[{now:T}] {coinName}";
        //    line2 += $" : 시작자산 {StartKRW:N0}, 현재자산 {totalBalance:N0}";
        //    line2 += $", 매수 하락 촉발가 {triggerDownPrice:N0}, 매수 반등 촉발가 {triggerUpPrice:N0}, 매도 목표가 {targetSellPrice:N0}";

        //    WriteCurrent("{0}\r\n{1}", line1, line2);
        //    //WriteLog("orderChance {0}", orderChance.ToString(Formatting.Indented));

        //    if (StartKRW < minTradeKRW && krwBalance > minTradeKRW && coinBalance < minTradeKRW)
        //    {
        //        // 거래 시작 금액
        //        StartKRW = krwBalance;
        //    }

        //    txtKRWBalance.Text = krwBalance.ToString("N0");
        //    txtCoinBalance.Text = coinBalance.ToString("N0");
        //    txtBalance.Text = totalBalance.ToString("N0");
        //    txtStartKRW.Text = StartKRW.ToString("N0");

        //    try
        //    {
        //        var orderResult = null as JObject;
        //        var order = null as JObject;
        //        var uuid = "";
        //        var side = "";
        //        var state = "";

        //        if (coinBalance <= minTradeKRW && krwBalance <= minTradeKRW)
        //        {
        //            // 보유현금과 보유 코인이 최소 거래금액 보다 적으면 거래 없음
        //            WriteLog("#### 거래 불가(금액 부족) : 보유현금 {0}, 코인보유금 {1}, 매도 대기 금액 {2}, 매수 대기 금액 {3}, 최소 거래 금액 {4}", krwBalance, coinBalance, buyLock, sellLock, minTradeKRW);
        //            return;
        //        }

        //        if (IsWaiting)
        //        {
        //            // 거래 대기 중일때
        //            uuid = LastOrderResult.Value<string>("uuid");
        //            side = LastOrderResult.Value<string>("side");
        //            state = LastOrderResult.Value<string>("state");
        //            WriteLog("#### 거래 불가(거래 대기중) : side {0}, state {1}, uuid {2}", side, state, uuid);
        //            return;
        //        }

        //        if (krwBalance > minTradeKRW                                                                        // 보유 현금이 최소거래 금액 보다 클때
        //            && buyRemainCoolDownSec <= 0                                                                    // 매도 유예시간이 지났을때
        //            && currPrice <= Math.Truncate(triggerDownPrice) && currPrice >= Math.Truncate(triggerUpPrice)   // 현재가가 촉발 금액 사이에서 반등하고
        //            && downUpRate <= candleCount * 2)                                                               // 반등율이 캔들갯수의 2배수가 넘지 않아야 함
        //        {
        //            // BUY
        //            var total = ToOrderPrice(krwBalance);
        //            var avgBuyPrice = currPrice;
        //            orderResult = React.executeDeal(true, false, coinName, 0, 0, total);
        //            LastBuyDate = DateTime.Now;
        //            WriteLog("#### BUY : {0}", orderResult.ToString(Formatting.Indented));

        //        }
        //        else if (coinBalance > minTradeKRW                                                                  // 코인 보유금이 최소거래 금액 보다 클때
        //            && (sellRemainCoolDownSec <= 0 || currPrice >= Math.Truncate(targetSellPrice)))                 // 매수 유예시간이 지나거나 현재가가 목표 매수 금액보다 클때
        //        {
        //            // SELL
        //            var vol = coinVol;
        //            vol = Math.Truncate(vol * 100000) / 100000;
        //            orderResult = React.executeDeal(false, false, coinName, vol, 0, 0);
        //            LastSellDate = DateTime.Now;
        //            WriteLog("#### SELL : {0}", orderResult.ToString(Formatting.Indented));
        //        }

        //        if (orderResult != null)
        //        {
        //            IsWaiting = true;
        //            LastOrderResult = orderResult;
        //            uuid = orderResult.Value<string>("uuid");
        //            side = orderResult.Value<string>("side");
        //            state = orderResult.Value<string>("state");

        //            System.Threading.Thread.Sleep(1500);

        //            order = ApiData.checkOrder(uuid);

        //            if (order != null)
        //            {
        //                IsWaiting = false;
        //                WriteLog("#### TRADE RESULT : {0}", order.ToString(Formatting.Indented));

        //                var orderState = order.Value<string>("state");
        //                var orderPrice = order.Value<double?>("price") ?? 0D;
        //                var orderVolume = order.Value<double?>("volume") ?? 0D;
        //                var tradeCount = order.Value<double>("trade_count");
        //                var trades = order["trades"] as JArray;
        //                var tradePrice = trades[0].Value<double?>("price") ?? 0D;
        //                var tradeVolume = trades[0].Value<double?>("volume") ?? 0D;
        //                var tradeFunds = trades[0].Value<double?>("funds") ?? 0D;

        //                if ("done".Equals(orderState))
        //                {
        //                    if ("bid".Equals(side))
        //                    {
        //                        // BUY
        //                        WriteLog("#### {0} BUY : 매수금 {1:N0} : 매수 평단가 {2:N0} : 수량 {3:F6}", coinName, tradeFunds, tradePrice, tradeVolume);

        //                    }
        //                    else if ("ask".Equals(side))
        //                    {
        //                        // SELL
        //                        WriteLog("#### {0} SELL : 매도금 {1:N0} : 매도 평단가 {2:N0} : 수량 {3:F6}", coinName, tradeFunds, tradePrice, tradeVolume);
        //                    }

        //                    // 수익
        //                    var balance = GetBalance(coinName);
        //                    krwBalance = balance.KRWBalance;
        //                    coinBalance = currPrice * balance.CoinVol;
        //                    totalBalance = krwBalance + coinBalance;
        //                    var profit = totalBalance - StartKRW;
        //                    var profitRate = (StartKRW == 0) ? 0D : profit / StartKRW * 100;

        //                    txtKRWBalance.Text = krwBalance.ToString("N0");
        //                    txtCoinBalance.Text = coinBalance.ToString("N0");
        //                    txtBalance.Text = totalBalance.ToString("N0");
        //                    txtProfitPrice.Text = profit.ToString("N0");
        //                    txtProfitRate.Text = profitRate.ToString("F4");

        //                    var args = new object[] { coinName, StartKRW, totalBalance, profit, profitRate, krwBalance, coinBalance };
        //                    WriteLog("#### {0} 수익 : 거래시작금액 {1:N0}, 현재평가 금액 {2:N0}, 수익금액 {3:N0}, 수익율 {4:F6}, 보유현금 {5:N0}, 코인 평가 {6:N0}", args);
        //                }
        //            }
        //            else
        //            {
        //                WriteLog("#### 거래 결과를 가져올수 없습니다.");
        //            }
        //        }

        //    }
        //    catch (Exception ex)
        //    {
        //        WriteLog(ex.StackTrace);
        //    }
        //}


        private async Task MinuteCandleByTicksAsync()
        {
            // 설정값
            var coinName     = Coin.Ticker;
            var candleType   = CandleType.Name;
            var candleMinute = CandleType.Minute;
            var candleCount  = CandleCount;
            var feeRate      = FeeRate;
            var tradeRate    = OrderRate;
            var triggerRate  = TriggerRate;

            // 캔들 갯수 많큼 캔들 가져오기
            var candles          = ApiData.getCandle <List <Candle> >(coinName, candleType, candleCount * 2); // 캔들 조회 (2배로 여유롭게)
            var currPrice        = candles.First().Close;                                                     // 현재가
            var prevPrice        = candles[1].Low;                                                            // 직전저가
            var highPrice        = candles.GetRange(1, candleCount - 1).Max(x => x.High);                     // 최고가
            var downPrice        = highPrice * (triggerRate + (feeRate * (candleCount - 1))) / 100;           // 하락가
            var triggerDownPrice = highPrice - downPrice;                                                     // 매수 하락 촉발가
            var downRate         = Math.Min(0D, (currPrice - highPrice) * 100 / highPrice);                   // 하락율
            var upPrice          = prevPrice * (feeRate * (candleCount - 1)) / 100;                           // 반등가
            var triggerUpPrice   = prevPrice + upPrice;                                                       // 매수 반등 촉발가
            var upRate           = (currPrice - prevPrice) * 100 / prevPrice;                                 // 반등 상승율
            var downUpRate       = upRate <= 0 && downRate <= 0 ? 0D : (-downRate / upRate);                  // 반등율

            // 보유현금 및 주문 정보
            var orderChance    = ApiData.getOrdersChance(coinName);                                 // 주문 가능 정보
            var bid            = orderChance["bid_account"];
            var ask            = orderChance["ask_account"];
            var krwBalance     = bid.Value <double>("balance");                                     // 보유 현금
            var coinVol        = ask.Value <double?>("balance") ?? 0D;                              // 보유 코인 수량
            var avgPrice       = ask.Value <double?>("avg_buy_price") ?? 0D;                        // 매수 평단가
            var buyLock        = bid.Value <double?>("locked") ?? 0D;                               // 매수 대기 금액
            var sellLock       = ask.Value <double?>("locked") ?? 0D;                               // 매도 대기 금액
            var coinBuyBalance = avgPrice * coinVol;                                                // 코인 매수금
            var coinBalance    = currPrice * coinVol;                                               // 코인 평가금
            var totalBalance   = krwBalance + coinBalance;                                          // 현재 자산
            var minTradeKRW    = Settings.Default.minTradeKRW;                                      // 최소 거래 금액
            var fee            = currPrice * feeRate / 100;                                         // 거래 수수료
            var sellFee        = coinBalance * feeRate / 100;                                       // 매도 총 수수료
            var buyFee         = totalBalance * feeRate / 100;                                      // 매수 총 수수료
            var buyUnitPrice   = GetOrderUnitPrice(krwBalance);                                     // 매수 주문 가격 단위
            var sellUnitPrice  = GetOrderUnitPrice(coinBalance);                                    // 매도 주문 가격 단위
            //var orderUintPrice = GetOrderUnitPrice(currPrice);                                      // 캔들 가격 단위

            // 거래 쿨다운 타임
            var buyTs                = (DateTime.Now - LastSellDate).TotalSeconds;                     // 마지막 매도 이후 경과 시간 (초)
            var buyCoolDownSec       = CandleType.Minute * 60 * (candleCount - 2) / (candleCount - 1); // 매수 쿨다운 시간 (초)
            var buyRemainCoolDownSec = buyCoolDownSec - buyTs;                                         // 매수 쿨다운 시간 (초)

            var sellTs                = (DateTime.Now - LastBuyDate).TotalSeconds;                     // 마지막 매수 이후 경과 시간 (초)
            var sellCoolDownSec       = (CandleType.Minute + (1 / candleCount)) * 60;                  // 매도 쿨다운 시간 (초)
            var sellRemainCoolDownSec = sellCoolDownSec - sellTs;                                      // 매도 까지 남은 시간 (초)
            var targetRate            = ((triggerRate / 2) + (feeRate * 2)) / 100;                     // 목표 수익율
            var targetSellPrice       = Math.Round(avgPrice * (1 + targetRate) / 10) * 10;             // 매도 목표가

            var now   = DateTime.Now;
            var line1 = $"[{now:T}] {coinName}";

            line1 += $" : 현재/직전/최고 {currPrice:N0}/{prevPrice:N0}/{highPrice:N0}";
            line1 += $", 하락율/반등율 {downRate:F2}/{upRate:F2}, 평단가 {avgPrice:N0}";
            line1 += (sellRemainCoolDownSec > 0) ? $", 매수쿨 {sellRemainCoolDownSec:N0}(초)" : "";
            line1 += (buyRemainCoolDownSec > 0) ? $", 매도쿨 {buyRemainCoolDownSec:N0}(초)" : "";
            var line2 = $"[{now:T}] {coinName}";

            line2 += $" : 시작자산 {StartKRW:N0}, 현재자산 {totalBalance:N0}";
            line2 += $", 매수촉발가(하락/반등) {triggerDownPrice:N0} / {triggerUpPrice:N0}, 매도 목표가 {targetSellPrice:N0}, 매수 수수료 {totalBalance * feeRate / 100:F2}";

            WriteCurrent("{0}\r\n{1}", line1, line2);
            //WriteLog("orderChance {0}", orderChance.ToString(Formatting.Indented));

            if (StartKRW < minTradeKRW && krwBalance > minTradeKRW && coinBalance < minTradeKRW)
            {
                // 거래 시작 금액
                StartKRW = krwBalance;
            }

            if (InvokeRequired)
            {
                BeginInvoke(new Action(() =>
                {
                    txtKRWBalance.Text  = krwBalance.ToString("N0");
                    txtCoinBalance.Text = coinBalance.ToString("N0");
                    txtBalance.Text     = totalBalance.ToString("N0");
                    txtStartKRW.Text    = StartKRW.ToString("N0");
                }));
            }
            else
            {
                txtKRWBalance.Text  = krwBalance.ToString("N0");
                txtCoinBalance.Text = coinBalance.ToString("N0");
                txtBalance.Text     = totalBalance.ToString("N0");
                txtStartKRW.Text    = StartKRW.ToString("N0");
            }

            try
            {
                var orderResult = null as JObject;
                var order       = null as JObject;
                var uuid        = "";
                var side        = "";
                var state       = "";

                if (coinBalance <= minTradeKRW && krwBalance <= minTradeKRW)
                {
                    // 보유현금과 보유 코인이 최소 거래금액 보다 적으면 거래 없음
                    WriteLog("#### 거래 불가(금액 부족) : 보유현금 {0}, 코인보유금 {1}, 매도 대기 금액 {2}, 매수 대기 금액 {3}, 최소 거래 금액 {4}", krwBalance, coinBalance, buyLock, sellLock, minTradeKRW);
                    return;
                }

                if (IsWaiting)
                {
                    // 거래 대기 중일때
                    uuid  = LastOrderResult.Value <string>("uuid");
                    side  = LastOrderResult.Value <string>("side");
                    state = LastOrderResult.Value <string>("state");
                    WriteLog("#### 거래 불가(거래 대기중) : side {0}, state {1}, uuid {2}", side, state, uuid);
                    return;
                }

                if (krwBalance > minTradeKRW &&                                                                     // 보유 현금이 최소거래 금액 보다 클때
                    buyRemainCoolDownSec <= 0 &&                                                                    // 매도 유예시간이 지났을때
                    currPrice <= Math.Truncate(triggerDownPrice) && currPrice >= Math.Truncate(triggerUpPrice) &&   // 현재가가 촉발 금액 사이에서 반등하고
                    downUpRate <= candleCount * 2)                                                                  // 반등율이 캔들갯수의 2배수가 넘지 않아야 함
                {
                    // BUY
                    var unitPrice   = GetOrderUnitPrice(currPrice);
                    var total       = Math.Truncate(coinBalance / unitPrice) * unitPrice;
                    var avgBuyPrice = currPrice;
                    orderResult = React.executeDeal(true, false, coinName, 0, 0, total);
                    IsWaiting   = true;
                    LastBuyDate = DateTime.Now;
                    WriteLog("#### BUY : {0}", orderResult.ToString(Formatting.Indented));

                    var buyUUID        = orderResult.Value <string>("uuid");
                    var buyOrderResult = await GetBuyMarketResultAsync(buyUUID);

                    // SELL
                    if (buyOrderResult != null)
                    {
                        var buyPrice        = buyOrderResult.Value <double>("price");
                        var targetBuyPrice  = GetOrderUnitPrice(buyPrice + Math.Max((buyPrice * 0.5 / 100) + (fee * 2), 10));
                        var buyVol          = buyOrderResult.Value <double>("executed_volume");
                        var sellOrderResult = await GetSellOrderResultAsync(coinName, targetBuyPrice, buyVol);

                        IsWaiting   = false;
                        LastBuyDate = DateTime.Now;
                        WriteLog("#### SELL : {0}", sellOrderResult.ToString(Formatting.Indented));
                    }
                }
                //else if (coinBalance > minTradeKRW                                                                  // 코인 보유금이 최소거래 금액 보다 클때
                //    && (sellRemainCoolDownSec <= 0 || currPrice >= Math.Truncate(targetSellPrice)))                 // 매수 유예시간이 지나거나 현재가가 목표 매수 금액보다 클때
                //{
                //    // SELL
                //    var vol = coinVol;
                //    vol = Math.Truncate(vol * 100000) / 100000;
                //    orderResult = React.executeDeal(false, false, coinName, vol, 0, 0);
                //    LastSellDate = DateTime.Now;
                //    WriteLog("#### SELL : {0}", orderResult.ToString(Formatting.Indented));
                //}

                if (orderResult != null)
                {
                    IsWaiting       = true;
                    LastOrderResult = orderResult;
                    uuid            = orderResult.Value <string>("uuid");
                    side            = orderResult.Value <string>("side");
                    state           = orderResult.Value <string>("state");

                    await Task.Delay(1500);

                    order = ApiData.checkOrder(uuid);

                    if (order != null)
                    {
                        IsWaiting = false;
                        WriteLog("#### CHECK ORDER RESULT : {0}", order.ToString(Formatting.Indented));

                        var orderState  = order.Value <string>("state");
                        var orderPrice  = order.Value <double?>("price") ?? 0D;
                        var orderVolume = order.Value <double?>("volume") ?? 0D;
                        var tradeCount  = order.Value <double>("trade_count");
                        var trades      = order["trades"] as JArray;
                        var tradePrice  = 0D;
                        var tradeVolume = 0D;
                        var tradeFunds  = 0D;

                        if (trades != null && trades.Count > 0)
                        {
                            tradePrice  = trades[0].Value <double?>("price") ?? 0D;
                            tradeVolume = trades[0].Value <double?>("volume") ?? 0D;
                            tradeFunds  = trades[0].Value <double?>("funds") ?? 0D;
                        }

                        if ("done".Equals(orderState))
                        {
                            if ("bid".Equals(side))
                            {
                                // BUY
                                WriteLog("#### {0} BUY : 매수금 {1:N0} : 매수 평단가 {2:N0} : 수량 {3:F6}", coinName, tradeFunds, tradePrice, tradeVolume);
                            }
                            else if ("ask".Equals(side))
                            {
                                // SELL
                                WriteLog("#### {0} SELL : 매도금 {1:N0} : 매도 평단가 {2:N0} : 수량 {3:F6}", coinName, tradeFunds, tradePrice, tradeVolume);
                            }

                            // 수익
                            var balance = GetBalance(coinName);
                            krwBalance   = balance.KRWBalance;
                            coinBalance  = currPrice * balance.CoinVol;
                            totalBalance = krwBalance + coinBalance;
                            var profit     = totalBalance - StartKRW;
                            var profitRate = (StartKRW == 0) ? 0D : profit / StartKRW * 100;

                            if (InvokeRequired)
                            {
                                BeginInvoke(new Action(() =>
                                {
                                    txtKRWBalance.Text  = krwBalance.ToString("N0");
                                    txtCoinBalance.Text = coinBalance.ToString("N0");
                                    txtBalance.Text     = totalBalance.ToString("N0");
                                    txtProfitPrice.Text = profit.ToString("N0");
                                    txtProfitRate.Text  = profitRate.ToString("F4");
                                }));
                            }
                            else
                            {
                                txtKRWBalance.Text  = krwBalance.ToString("N0");
                                txtCoinBalance.Text = coinBalance.ToString("N0");
                                txtBalance.Text     = totalBalance.ToString("N0");
                                txtProfitPrice.Text = profit.ToString("N0");
                                txtProfitRate.Text  = profitRate.ToString("F4");
                            }

                            var args = new object[] { coinName, StartKRW, totalBalance, profit, profitRate, krwBalance, coinBalance };
                            WriteLog("#### {0} 수익 : 거래시작금액 {1:N0}, 현재평가 금액 {2:N0}, 수익금액 {3:N0}, 수익율 {4:F6}, 보유현금 {5:N0}, 코인 평가 {6:N0}", args);
                        }
                    }
                    else
                    {
                        WriteLog("#### 거래 결과를 가져올수 없습니다.");
                    }
                }
            }
            catch (Exception ex)
            {
                WriteLog(ex.StackTrace);
            }
        }
        private void MinuteCandleByTicks()
        {
            // 설정값
            var coinName    = Coin.Ticker;
            var candleType  = CandleType.Name;
            var candleCount = CandleCount;
            var feeRate     = FeeRate;
            var tradeRate   = OrderRate;
            var triggerRate = TriggerRate;

            // 캔들 갯수 많큼 캔들 가져오기
            var candles          = ApiData.getCandle <List <Candle> >(coinName, candleType, candleCount * 2); // 캔들 조회 (2배로 여유롭게)
            var currPrice        = candles.First().Close;                                                     // 현재가
            var prevPrice        = candles[1].Close;                                                          // 직전종가
            var highPrice        = candles.GetRange(1, candleCount - 1).Max(x => x.High);                     // 최고가
            var downPrice        = highPrice * (triggerRate + (feeRate * 2)) / 100;                           // 하락가
            var triggerDownPrice = highPrice - downPrice;                                                     // 매수 하락 촉발가
            var downRate         = Math.Min(0D, (currPrice - highPrice) * 100 / highPrice);                   // 하락율
            var upPrice          = prevPrice * ((triggerRate / candleCount) + (feeRate * 2)) / 100;           // 반등가
            var triggerUpPrice   = prevPrice + upPrice;                                                       // 매수 반등 촉발가
            var upRate           = Math.Max(0D, (currPrice - prevPrice) * 100 / prevPrice);                   // 반등 상승율
            var downUpRate       = upRate == 0 ? 0D : (-downRate / upRate);                                   // 반등율

            // 보유현금 및 주문 정보
            var orderChance    = ApiData.getOrdersChance(coinName);                                 // 주문 가능 정보
            var bid            = orderChance["bid_account"];
            var ask            = orderChance["ask_account"];
            var krwBalance     = bid.Value <double>("balance");                                     // 보유 현금
            var coinVol        = ask.Value <double?>("balance") ?? 0D;                              // 보유 코인 수량
            var avgPrice       = ask.Value <double?>("avg_buy_price") ?? 0D;                        // 매수 평단가
            var buyLock        = bid.Value <double?>("locked") ?? 0D;                               // 매수 대기 금액
            var sellLock       = ask.Value <double?>("locked") ?? 0D;                               // 매도 대기 금액
            var coinBuyBalance = avgPrice * coinVol;                                                // 코인 매수금
            var coinBalance    = currPrice * coinVol;                                               // 코인 평가금
            var totalBalance   = krwBalance + coinBalance;                                          // 현재 자산
            var minTradeKRW    = Settings.Default.minTradeKRW;                                      // 최소 거래 금액

            avgPrice = avgPrice < minTradeKRW ? currPrice : avgPrice;

            // 거래 쿨다운 타임
            var buyTs           = (DateTime.Now - LastSellDate).TotalSeconds;                       // 마지막 매도 이후 경과 시간 (초)
            var buyCoolDownSec  = CandleType.Minute * 60;                                           // 매수 쿨다운 시간 (초)
            var buyCountDownSec = buyCoolDownSec - buyTs;                                           // 매수 쿨다운 시간 (초)

            var sellTs           = (DateTime.Now - LastBuyDate).TotalSeconds;                       // 마지막 매수 이후 경과 시간 (초)
            var sellCoolDownSec  = (CandleType.Minute + (1 / candleCount)) * 60;                    // 매도 쿨다운 시간 (초)
            var sellCountDownSec = sellCoolDownSec - sellTs;                                        // 매도 까지 남은 시간 (초)

            var targetRate      = ((triggerRate / (candleCount - 1)) + (feeRate * 2)) / 100;        // 목표 수익율
            var targetSellPrice = avgPrice + (avgPrice * targetRate);                               // 매도 목표가

            var args = new object[] {
                DateTime.Now,
                coinName,
                currPrice,
                prevPrice,
                highPrice,
                downRate,
                upRate,
                avgPrice,
                StartKRW,
                totalBalance,
                triggerDownPrice,
                triggerUpPrice,
                targetSellPrice,
                sellCountDownSec,
                buyCountDownSec,
            };

            string format = "[{0:T}] {1} : 현재가 {2:N0}, 직전가 {3:N0}, 최고가 {4:N0}, 하락율 {5:F4}, 반등율 {6:F4}, 평단가 {7:N0}";

            format += (sellCountDownSec > 0) ? ", 매수 까지 {13:N0}(초)" : "";
            format += (buyCountDownSec > 0) ? ", 매도 까지 {14:N0}(초)" : "";
            format += "\r\n[{0:T}] {1} : 시작자산 {8:N0}, 현재자산 {9:N0} 매수 하락 촉발가 {10:N0}, 매수 반등 촉발가 {11:N0}, 매도 목표가 {12:N0}, 매수 목표가 {12:N0}";
            WriteCurrent(format, args);
            var now   = DateTime.Now;
            var line1 = $"[{now:T}] {coinName}";

            line1 += $" : 현재가 {currPrice:N0}, 직전가 {prevPrice:N0}, 최고가 {highPrice:N0}";
            line1 += $", 하락율 {downRate:F4}, 반등율 {upRate:F4}, 평단가 {avgPrice:N0}";
            line1 += (sellCountDownSec > 0) ? $", 매수 까지 {sellCountDownSec:N0}(초)" : "";
            line1 += (buyCountDownSec > 0) ? $", 매도 까지 {buyCountDownSec:N0}(초)" : "";
            var line2 = $"[{now:T}] {coinName}";

            line2 += $" : 시작자산 {StartKRW:N0}, 현재자산 {totalBalance:N0}";
            line2 += $", 매수 하락 촉발가 {triggerDownPrice:N0}, 매수 반등 촉발가 {triggerUpPrice:N0}, 매도 목표가 {targetSellPrice:N0}";

            WriteCurrent("{0}\r\n{1}", line1, line2);
            log.Debug(orderChance.ToString(Formatting.Indented));

            if (StartKRW < minTradeKRW && krwBalance > minTradeKRW && coinBalance < minTradeKRW)
            {
                // 거래 시작 금액
                StartKRW = krwBalance;
            }

            txtKRWBalance.Text  = krwBalance.ToString("N0");
            txtCoinBalance.Text = coinBalance.ToString("N0");
            txtBalance.Text     = totalBalance.ToString("N0");
            txtStartKRW.Text    = StartKRW.ToString("N0");

            try
            {
                var orderResult = null as JObject;
                var order       = null as JObject;
                var uuid        = "";
                var side        = "";
                var state       = "";

                if (coinBalance <= minTradeKRW && krwBalance <= minTradeKRW)
                {
                    // 보유현금과 보유 코인이 최소 거래금액 보다 적으면 거래 없음
                    WriteLog("#### 거래 불가(금액 부족) : 보유현금 {0}, 코인보유금 {1}, 매도 대기 금액 {2}, 매수 대기 금액 {3}, 최소 거래 금액 {4}", krwBalance, coinBalance, buyLock, sellLock, minTradeKRW);
                    return;
                }

                if (sellCountDownSec > 0 && buyCountDownSec > 0)
                {
                    // 거래 쿨타임 일때
                    WriteLog("#### 거래 불가(쿨타임) : 매수 까지 남은 시간(초) {0}, 매수 까지 남은 시간(초) {1}, 마지막 매수 {2:T}, 마지막 매도 {3:T}", buyCountDownSec, sellCountDownSec, LastSellDate, LastBuyDate);
                    return;
                }

                if (IsWaiting)
                {
                    // 거래 대기 중일때
                    uuid  = LastOrderResult.Value <string>("uuid");
                    side  = LastOrderResult.Value <string>("side");
                    state = LastOrderResult.Value <string>("state");
                    WriteLog("#### 거래 불가(거래 대기중) : side {0}, state {1}, uuid {2}", side, state, uuid);
                    return;
                }

                if (krwBalance > minTradeKRW &&                                                                     // 보유 현금이 최소거래 금액 보다 클때
                    currPrice <= Math.Truncate(triggerDownPrice) && currPrice >= Math.Truncate(triggerUpPrice) &&   // 현재가가 촉발 금액 사이에서 반등하고
                    downUpRate <= candleCount * 2)                                                                  // 반등율이 캔들갯수의 2배수가 넘지 않아야 함
                {
                    // BUY
                    var total       = ToOrderPrice(krwBalance);
                    var avgBuyPrice = currPrice;
                    orderResult = React.executeDeal(true, false, coinName, 0, 0, total);
                    LastBuyDate = DateTime.Now;
                    WriteLog("#### BUY : {0}", orderResult.ToString(Formatting.Indented));
                }
                else if (coinBalance > minTradeKRW &&                                                               // 코인 보유금이 최소거래 금액 보다 클때
                         currPrice >= Math.Truncate(targetSellPrice))                                               // 현재가가 목표 매수 금액보다 클때
                {
                    // SELL
                    var vol = coinVol;
                    vol          = Math.Truncate(vol * 100000) / 100000;
                    orderResult  = React.executeDeal(false, false, coinName, vol, 0, 0);
                    LastSellDate = DateTime.Now;
                    WriteLog("#### SELL : {0}", orderResult.ToString(Formatting.Indented));
                }

                if (orderResult != null)
                {
                    IsWaiting       = true;
                    LastOrderResult = orderResult;
                    uuid            = orderResult.Value <string>("uuid");
                    side            = orderResult.Value <string>("side");
                    state           = orderResult.Value <string>("state");

                    System.Threading.Thread.Sleep(1500);
                    order = ApiData.checkOrder(uuid);

                    if (order != null)
                    {
                        IsWaiting = false;
                        WriteLog("#### TRADE RESULT : {0}", order.ToString(Formatting.Indented));

                        var orderState  = order.Value <string>("state");
                        var orderPrice  = order.Value <double?>("price") ?? 0D;
                        var orderVolume = order.Value <double?>("volume") ?? 0D;
                        var tradeCount  = order.Value <double>("trade_count");
                        var trades      = order["trades"] as JArray;
                        var tradePrice  = trades[0].Value <double?>("price") ?? 0D;
                        var tradeVolume = trades[0].Value <double?>("volume") ?? 0D;
                        var tradeFunds  = trades[0].Value <double?>("funds") ?? 0D;

                        if ("done".Equals(orderState))
                        {
                            if ("bid".Equals(side))
                            {
                                // BUY
                                WriteLog("#### {0} BUY : 매수금 {1:N0} : 매수 평단가 {2:N0} : 수량 {3:F6}", coinName, tradeFunds, tradePrice, tradeVolume);
                            }
                            else if ("ask".Equals(side))
                            {
                                // SELL
                                WriteLog("#### {0} SELL : 매도금 {1:N0} : 매도 평단가 {2:N0} : 수량 {3:F6}", coinName, tradeFunds, tradePrice, tradeVolume);
                            }

                            // 수익
                            var balance = GetBalance(coinName);
                            krwBalance   = balance.KRWBalance;
                            coinBalance  = currPrice * balance.CoinVol;
                            totalBalance = krwBalance + coinBalance;
                            var profit     = totalBalance - StartKRW;
                            var profitRate = (StartKRW == 0) ? 0D : profit / StartKRW * 100;

                            txtKRWBalance.Text  = krwBalance.ToString("N0");
                            txtCoinBalance.Text = coinBalance.ToString("N0");
                            txtBalance.Text     = totalBalance.ToString("N0");
                            txtProfitPrice.Text = profit.ToString("N0");
                            txtProfitRate.Text  = profitRate.ToString("F4");

                            args = new object[] { coinName, StartKRW, totalBalance, profit, profitRate, krwBalance, coinBalance };
                            WriteLog("#### {0} 수익 : 거래시작금액 {1:N0}, 현재평가 금액 {2:N0}, 수익금액 {3:N0}, 수익율 {4:F6}, 보유현금 {5:N0}, 코인 평가 {6:N0}", args);
                        }
                    }
                    else
                    {
                        WriteLog("#### 거래 결과를 가져올수 없습니다.");
                    }
                }
            }
            catch (Exception ex)
            {
                WriteLog(ex.StackTrace);
            }
        }
Example #3
0
        private void MinuteCandleByTicks()
        {
            // 설정값
            var coinName = Coin.Ticker;
            var candleType = CandleType.Name;
            var candleCount = CandleCount;
            var feeRate = FeeRate;
            var tradeRate = TradeRate;
            var triggerRate = TriggerRate;

            // 캔들 갯수 많큼 캔들 가져오기
            var candles = ApiData.getCandle<List<Candle>>(coinName, candleType, candleCount * 2);   // 캔들 조회 (2배로 여유롭게)
            var currPrice = candles.First().Close;                                                  // 현재가
            var prevPrice = candles[1].Close;                                                       // 직전종가
            var highPrice = candles.GetRange(1, candleCount - 1).Max(x => x.High);                  // 최고가
            var downPrice = highPrice * (triggerRate + (feeRate * 2)) / 100;                        // 하락가
            var triggerDownPrice = highPrice - downPrice;                                           // 매수 하락 촉발가
            var downRate = Math.Min(0D, (currPrice - highPrice) * 100 / highPrice);                 // 하락율
            var upPrice = prevPrice * ((triggerRate / candleCount) + (feeRate * 2)) / 100;          // 반등가
            var triggerUpPrice = prevPrice + upPrice;                                               // 매수 반등 촉발가
            var upRate = Math.Max(0D, (currPrice - prevPrice) * 100 / prevPrice);                   // 반등 상승율
            var downUpRate = upRate == 0 ? 0D : (-downRate / upRate);                               // 반등율

            // 보유현금 및 주문 정보
            var orderChance = ApiData.getOrdersChance(coinName);                                    // 주문 가능 정보
            var bid = orderChance["bid_account"];
            var ask = orderChance["ask_account"];
            var krwBalance = bid.Value<double>("balance");                                          // 보유 현금
            var coinVol = ask.Value<double>("balance");                                             // 보유 코인 수량
            var avgPrice = ask.Value<double>("avg_buy_price");                                      // 매수 평단가
            var buyLock = bid.Value<double?>("locked") ?? 0D;                                       // 매수 대기 금액
            var sellLock = ask.Value<double?>("locked") ?? 0D;                                      // 매도 대기 금액
            var coinBuyBalance = avgPrice * coinVol;                                                // 코인 매수금
            var coinBalance = currPrice * coinVol;                                                  // 코인 평가금
            var totalBalance = krwBalance + coinBalance;                                            // 현재 자산
            var minTradeKRW = Settings.Default.minTradeKRW;                                         // 최소 거래 금액
            avgPrice = avgPrice < minTradeKRW ? currPrice : avgPrice;

            // 거래 쿨다운 타임
            var buyTs = (DateTime.Now - LastSellDate).TotalSeconds;                                 // 마지막 매도 이후 경과 시간 (초)
            var buyCoolDownSec = CandleType.Minute * 60;                                            // 매수 쿨다운 시간 (초)
            var buyCountDownSec = buyCoolDownSec - buyTs;                                           // 매수 쿨다운 시간 (초)

            var sellTs = (DateTime.Now - LastBuyDate).TotalSeconds;                                 // 마지막 매수 이후 경과 시간 (초)
            var sellCoolDownSec = (CandleType.Minute + (1/candleCount)) * 60;                       // 매도 쿨다운 시간 (초)
            var sellCountDownSec = sellCoolDownSec - sellTs;                                        // 매도 까지 남은 시간 (초)

            var targetRate = ((triggerRate / (candleCount - 1)) + (feeRate * 2)) / 100;             // 목표 수익율
            var targetSellPrice = avgPrice + (avgPrice * targetRate);                               // 매도 목표가

            var result = null as JObject;
            var args = new object[] {
                DateTime.Now,
                coinName,
                currPrice,
                prevPrice,
                highPrice,
                downRate,
                upRate,
                avgPrice,
                StartKRW,
                totalBalance,
                triggerDownPrice,
                triggerUpPrice,
                targetSellPrice,
                sellCountDownSec,
                buyCoolDownSec,
            };

            log.Debug(JsonConvert.SerializeObject(candles));
            log.Debug(JsonConvert.SerializeObject(orderChance));

            string format = "[{0:T}] {1} : 현재가 {2:N0}, 직전가 {3:N0}, 최고가 {4:N0}, 하락율 {5:F6}, 반등율 {6:F6}, 평단가 {7:N0}";
            format += (sellCountDownSec > 0) ? ", 매수 까지 남은 시간(초) {13:N0}" : "";
            format += (buyCoolDownSec > 0) ? ", 매도 까지 남은 시간(초) {14:N0}" : "";
            format += "\r\n[{0:T}] {1} : 시작자산 {8:N0}, 현재자산 {9:N0} 매수 하락 촉발가 {10:N0}, 매수 반등 촉발가 {11:N0}, 매도 목표가 {12:N0}";
            WriteCurrent(format, args);

            if (StartKRW < minTradeKRW && krwBalance > minTradeKRW && coinBalance < minTradeKRW)
            {
                // 거래 시작 금액
                StartKRW = krwBalance;
            }

            txtKRWBalance.Text = krwBalance.ToString("N0");
            txtCoinBalance.Text = coinBalance.ToString("N0");
            txtBalance.Text = totalBalance.ToString("N0");
            txtStartKRW.Text = StartKRW.ToString("N0");

            try
            {
                var buySates = null as string;
                var sellSates = null as string;

                if (coinBalance <= minTradeKRW && krwBalance <= minTradeKRW)
                {
                    // 보유현금과 보유 코인이 최소 거래금액 보다 적으면 거래 없음
                    WriteLog("#### 거래 불가(금액 부족) : 보유현금 {0}, 코인보유금 {1}, 매도 대기 금액 {2}, 매수 대기 금액 {3}, 최소 거래 금액 {4}", krwBalance, coinBalance, buyLock, sellLock, minTradeKRW);
                    return;
                }

                if (sellCountDownSec <= 0 && buyCountDownSec <= 0)
                {
                    // 거래 쿨타임 일때
                    WriteLog("#### 거래 불가(쿨타임) : 매수 까지 남은 시간(초) {0}, 매수 까지 남은 시간(초) {1}, 마지막 매수 {2:T}, 마지막 매도 {3:T}", buyCountDownSec, sellCountDownSec, LastSellDate, LastBuyDate);
                    return;
                }

                if (krwBalance > minTradeKRW                                                                        // 보유 현금이 최소거래 금액 보다 크고
                    && currPrice <= Math.Truncate(triggerDownPrice) && currPrice >= Math.Truncate(triggerUpPrice)   // 현재가가 촉발 금액 사이에서 반등하고
                    && downUpRate <= candleCount * 2)                                                               // 반등율이 캔들갯수의 2배수가 넘지 않아야 함
                {
                    // BUY
                    var total = ToOrderPrice(krwBalance);
                    var avgBuyPrice = currPrice;
                    result = React.executeDeal(true, false, coinName, 0, 0, total);
                    buySates = result.Value<string>("state");

                    if ("cancel".Equals(buySates))
                    {
                        System.Threading.Thread.Sleep(1000);
                        result = React.executeDeal(true, false, coinName, 0, 0, total);
                        buySates = result.Value<string>("state");
                    }

                }
                else if (coinBalance > minTradeKRW                                                                  // 코인 보유금이 최소거래 금액 보다 크고
                    && currPrice >= Math.Truncate(targetSellPrice)))                                                // 현재가가 평단가 보다 (수익율/캔들수-1) + 수료율2배 이상일때 전체 매도
                {
                    // SELL
                    // 현재가가 평단가 보다 (수익율/캔들수 + 수료율2배) 이상일때 전체 매도
                    var vol = coinVol;
                    vol = Math.Truncate(vol * 100000) / 100000;
                    result = React.executeDeal(false, false, coinName, vol, 0, 0);
                    sellSates = result.Value<string>("state");
                }

                if(result != null)
                {
                    WriteLog("{0} RESULT : {1}", coinName,  JsonConvert.SerializeObject(result));

                    var uuid = result.Value<string>("uuid");
                    var side = result.Value<string>("side");

                    if (result != null && !"cancel".Equals(buySates))
                    {


                        WriteLog("#### START GetOrderResultAsync : {0}", uuid);
                        //result = GetOrderResultAsync(uuid);
                        result = GetOrderResult(uuid);
                        WriteLog("#### FINISH GetOrderResultAsync : {0}", uuid);

                        if (result == null)
                        {
                            WriteLog("#### 거래 결과를 가져올수 없습니다.");
                            return;
                        }

                        var state = result.Value<string>("state");
                        var price = result.Value<double?>("price") ?? 0D;
                        var volume = result.Value<double?>("volume") ?? 0D;
                        var tradeCount = result.Value<double>("trade_count");
                        var trades = result["trades"] as JArray;
                        var tradePrice = trades[0].Value<double>("price");
                        var tradeVolume = trades[0].Value<double>("volume");
                        var tradeFunds = trades[0].Value<double>("funds");

                        args = new object[] { uuid, side, state, price, volume, tradeCount, tradePrice, tradeVolume, tradeFunds };
                        WriteLog("#### uuid {0}, side {1}, state {2}, price {3:N0}, volume {4:F6}, tradeCount {5:N0}, tradePrice {6:N0}, tradeVolume {7:N0}, tradeFunds {8:N0}", args);

                        if ("bid".Equals(side))
                        {
                            // BUY
                            LastBuyDate = DateTime.Now;
                            WriteLog("#### {0} BUY : 매수금 {1:N0} : 매수 평단가 {1:N0} : 수량 {3:F6}", coinName, price * volume, price, volume);

                        }
                        else if ("ask".Equals(side))
                        {
                            // SELL
                            LastSellDate = DateTime.Now;
                            WriteLog("#### {0} SELL : 매도금 {1:N0} : 매도 평단가 {1:N0} : 수량 {3:F6}", coinName, price * volume, price, volume);
                        }

                        // 수익
                        var balance = GetBalance(coinName);
                        krwBalance = balance.KRWBalance;
                        coinBalance = currPrice * balance.CoinVol;
                        totalBalance = krwBalance + coinBalance;
                        var profit = totalBalance - StartKRW;
                        var profitRate = (StartKRW == 0) ? 0D : profit / StartKRW * 100;

                        txtKRWBalance.Text = krwBalance.ToString("N0");
                        txtCoinBalance.Text = coinBalance.ToString("N0");
                        txtBalance.Text = totalBalance.ToString("N0");
                        txtProfitPrice.Text = profit.ToString("N0");
                        txtProfitRate.Text = profitRate.ToString("F4");

                        args = new object[] { coinName, StartKRW, totalBalance, profit, profitRate, krwBalance, coinBalance };
                        WriteLog("#### {0} 수익 : 거래시작금액 {1:N0}, 현재평가 금액 {2:N0}, 수익금액 {3:N0}, 수익율 {4:F6}, 보유현금 {5:N0}, 코인 평가 {6:N0}", args);
                    }

                }
            }
            catch (Exception ex)
            {
                WriteLog(ex.StackTrace);
            }
        }