// Copyright (C) 2003, 2004 Daisuke Arai <darai@users.sourceforge.jp>
// Copyright (C) 2005, 2007, 2008, 2010, 2013 panacoran <panacoran@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
// 
// $Id$

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Protra.Lib.Config;
using Protra.Lib.Data;
using Protra.Lib.Lang.Builtins;

namespace PtSim
{
    /// <summary>
    /// システムの成績を計算するクラス
    /// </summary>
    public class Performance
    {
        private readonly string _name;
        private readonly BrandList _brandList;
        private readonly TimeFrame _timeFrame;

        private int _allTrades; // 全トレード数
        private int _winTrades; // 勝ちトレード数
        private float _allProfitRatio; // 全トレード平均損益率
        private float _winProfitRatio; // 勝ちトレード平均利率
        private float _winMaxProfitRatio; // 勝ちトレード最大利率
        private float _loseMaxLossRatio; // 負けトレード最大損率
        private float _allTerm; // 全トレード平均期間
        private float _winTerm; // 勝ちトレード平均期間
        private float _totalProfit; // 総利益
        private float _winTotalProfit; // 勝ちトレード総利益
        private float _winMaxProfit; // 勝ちトレード最大利益
        private float _loseMaxLoss; // 負けトレード最大損失
        private int _runningTrades; // 進行中のトレード数
        private float _marketMaxDrowDown; // 時価の最大ドローダウン
        private float _bookMaxDrowDown; // 簿価の最大ドローダウン
        private DateTime _firstTrade; // 最初のトレードの日付
        private DateTime _lastTrade; // 最後のトレードの日付

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="name">システムの名前</param>
        /// <param name="timeFrame">タイムフレーム</param>
        /// <param name="brandList">使用する銘柄リスト</param>
        public Performance(string name, BrandList brandList, TimeFrame timeFrame)
        {
            _name = name;
            _brandList = brandList;
            _timeFrame = timeFrame;
        }

        /// <summary>
        /// システムの成績を分析する。
        /// </summary>
        /// <param name="worker">Executeを実行するBackgroundWorker</param>
        /// <param name="appendText">TextBoxのAppendText</param>
        /// <returns>利益の集計値のリスト</returns>
        public CumultiveProfitList Calculate(BackgroundWorker worker, AppendTextDelegate appendText)
        {
            var profits = CalcProfit(worker);
            if (worker.CancellationPending)
                return null;
            CalcDrowdown(profits);
            _firstTrade = profits.Dates[0];
            _lastTrade = profits.Dates[profits.Count - 1];
            PrintResult(appendText);
            return profits;
        }

        private CumultiveProfitList CalcProfit(BackgroundWorker worker)
        {
            var profits = new CumultiveProfitList();
            var count = 0;
            using (var logData = new LogData(_name, _timeFrame))
            {
                foreach (var code in _brandList.List)
                {
                    if (worker.CancellationPending)
                        return null;
                    var logs = logData.GetLog(code);
                    var logIndex = 0;
                    var prevClose = 0;
                    var position = 0;
                    var totalBuy = 0f;
                    var totalSell = 0f;
                    var startDate = DateTime.MinValue;
                    var prices = PriceData.GetPrices(code, _timeFrame);
                    if (prices == null)
                        throw new Exception("株価データがありません。");
                    foreach (var price in prices)
                    {
                        if (worker.CancellationPending)
                            return null;
                        var dailyProfit = profits[price.Date];
                        var close = price.Close;
                        if (position != 0 && close > 0 && prevClose > 0)
                            dailyProfit.AddMarketProfit(position * (close - prevClose)); // 時価の前日比を積算する。
                        if (close > 0)
                            prevClose = close;
                        if (logIndex == logs.Count || logs[logIndex].Date != price.Date) // 売買が発生しない。
                            continue;
                        var log = logs[logIndex++];
                        if (log.Quantity == 0) // 0株の売買は無視する。
                            continue;
                        var prevPosition = position;
                        if (log.Order == Order.Buy)
                        {
                            position += log.Quantity;
                            totalBuy += log.Quantity * log.Price;
                            if (close > 0)
                                dailyProfit.AddMarketProfit(log.Quantity * (close - log.Price));
                        }
                        else
                        {
                            position -= log.Quantity;
                            totalSell += log.Quantity * log.Price;
                            if (close > 0)
                                dailyProfit.AddMarketProfit(log.Quantity * (log.Price - close));
                        }
                        if (position * prevPosition > 0) // トレード継続
                            continue;
                        if (prevPosition == 0) // トレード開始
                        {
                            startDate = log.Date;
                            continue;
                        }
                        // ドテンの補正
                        var realTotalBuy = totalBuy;
                        var realTotalSell = totalSell;
                        totalBuy = totalSell = 0;
                        if (position > 0)
                            realTotalBuy -= (totalBuy = position * log.Price);
                        else if (position < 0)
                            realTotalSell -= (totalSell = -position * log.Price);
                        dailyProfit.AddBookProfit(EvaluateTrade(log.Order == Order.Sell, (log.Date - startDate).Days,
                                                                realTotalBuy, realTotalSell));
                        if (position != 0) // ドテン
                            startDate = log.Date;
                    }
                    if (position != 0)
                        _runningTrades++;
                    worker.ReportProgress(100 * ++count / _brandList.List.Count);
                }
            }
            return profits.AccumulatedList;
        }

        private float EvaluateTrade(bool isLong, int term, float totalBuy, float totalSell)
        {
            _allTrades++;
            var ratio = isLong ? totalSell / totalBuy - 1 : 1 - totalBuy / totalSell; // 空売りは売りポジションが分母
            _allProfitRatio += ratio;
            _allTerm += term;
            var profit = totalSell - totalBuy;
            _totalProfit += profit;
            if (totalSell > totalBuy) // 勝ち
            {
                _winTrades++;
                _winProfitRatio += ratio;
                _winMaxProfitRatio = Math.Max(_winMaxProfitRatio, ratio);
                _winMaxProfit = Math.Max(_winMaxProfit, profit);
                _winTotalProfit += profit;
                _winTerm += term;
            }
            else // 負け
            {
                _loseMaxLossRatio = Math.Min(_loseMaxLossRatio, ratio);
                _loseMaxLoss = Math.Min(_loseMaxLoss, profit);
            }
            return profit;
        }

        private void CalcDrowdown(IEnumerable<CumultiveProfit> profits)
        {
            var maxProfit = new CumultiveProfit();
            var maxDrowdown = new CumultiveProfit();
            foreach (var profit in profits)
            {
                maxProfit = CumultiveProfit.Max(maxProfit, profit);
                maxDrowdown = CumultiveProfit.Min(maxDrowdown, profit - maxProfit);
            }
            _marketMaxDrowDown = maxDrowdown.MarketProfit;
            _bookMaxDrowDown = maxDrowdown.BookProfit;
        }

        private void PrintResult(AppendTextDelegate appendText)
        {
            var loseTrades = _allTrades - _winTrades;
            var loseProfitRatio = _allProfitRatio - _winProfitRatio;
            var loseTerm = _allTerm - _winTerm;
            var loseTotalLoss = _totalProfit - _winTotalProfit;
            appendText(string.Format(
                "ファイル: {0}\n" +
                "株価データ: {1}\n" +
                "銘柄リスト: {2}\n" +
                "{3:d}～{4:d}における成績です。\n" +
                "----------------------------------------\n" +
                "全トレード数\t\t{5:d}\n" +
                "勝ちトレード数(勝率)\t{6:d}({7:p})\n" +
                "負けトレード数(負率)\t{8:d}({9:p})\n" +
                "\n" +
                "全トレード平均利率\t{10:p}\n" +
                "勝ちトレード平均利率\t{11:p}\n" +
                "負けトレード平均損率\t{12:p}\n" +
                "\n" +
                "勝ちトレード最大利率\t{13:p}\n" +
                "負けトレード最大損率\t{14:p}\n" +
                "\n" +
                "全トレード平均期間\t{15:n}\n" +
                "勝ちトレード平均期間\t{16:n}\n" +
                "負けトレード平均期間\t{17:n}\n" +
                "----------------------------------------\n" +
                "純利益\t\t\t{18:c}\n" +
                "勝ちトレード総利益\t\t{19:c}\n" +
                "負けトレード総損失\t\t{20:c}\n" +
                "\n" +
                "全トレード平均利益\t{21:c}\n" +
                "勝ちトレード平均利益\t{22:c}\n" +
                "負けトレード平均損失\t{23:c}\n" +
                "\n" +
                "勝ちトレード最大利益\t{24:c}\n" +
                "負けトレード最大損失\t{25:c}\n" +
                "\n" +
                "プロフィットファクター\t\t{26:n}\n" +
                "最大ドローダウン(簿価)\t{27:c}\n" +
                "最大ドローダウン(時価)\t{28:c}\n" +
                "----------------------------------------\n" +
                "現在進行中のトレード数\t{29:d}\n",
                _name,
                _timeFrame == TimeFrame.Daily ? "日足" : "週足",
                _brandList.Name,
                _firstTrade, _lastTrade,
                _allTrades, _winTrades, (float)_winTrades / _allTrades, loseTrades, (float)loseTrades / _allTrades,
                _allProfitRatio / _allTrades, _winProfitRatio / _winTrades, loseProfitRatio / loseTrades,
                _winMaxProfitRatio, _loseMaxLossRatio,
                _allTerm / _allTrades, _winTerm / _winTrades, loseTerm / loseTrades,
                _totalProfit, _winTotalProfit, loseTotalLoss,
                _totalProfit / _allTrades, _winTotalProfit / _winTrades, loseTotalLoss / loseTrades,
                _winMaxProfit, _loseMaxLoss,
                _winTotalProfit / -loseTotalLoss,
                _bookMaxDrowDown, _marketMaxDrowDown,
                _runningTrades));
        }
    }
}