/*
 * Copyright (c) 2015, StrategyQuant - All rights reserved.
 *
 * Code in this file was made in a good faith that it is correct and does what it should. 
 * If you found a bug in this code OR you have an improvement suggestion OR you want to include 
 * your own code snippet into our standard library please contact us at:
 * http://tasks.strategyquant.com/projects/snippets/
 *
 * This code can be used only within StrategyQuant products. 
 * Every owner of valid (free, trial or commercial) license of any StrategyQuant product 
 * is allowed to freely use, copy, modify or make derivative work of this code without limitations,
 * to be used in all StrategyQuant products and share his/her modifications or derivative work 
 * with the StrategyQuant community.
 *  
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES 
 * OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *  
 */
package com.strategyquant.extend.StatValues;

import java.util.ArrayList;

import com.strategyquant.extend.Functions.StatFunctions;
import com.strategyquant.lib.results.SQOrder;
import com.strategyquant.lib.results.SQOrderList;
import com.strategyquant.lib.results.SQStats;
import com.strategyquant.lib.results.StatValue;
import com.strategyquant.lib.results.StatsConst;
import com.strategyquant.lib.results.StatsTypeCombination;
import com.strategyquant.lib.settings.SQSettings;
import com.strategyquant.lib.utils.SQUtils;

/**
 * @author Mark Fric
 *
 */
public class SQNValues extends StatValue {

	private ArrayList<Double> rResult = new ArrayList<Double>();
	private double avgLossForExpectancy;

	@Override
	public String dependsOn() {
		return StatsConst.join(StatsConst.AVG_LOSS, StatsConst.NUMBER_OF_TRADES, StatsConst.AVG_TRADES_PER_MONTH);
	}
	
	@Override
	public void initCompute(SQStats stats, StatsTypeCombination combination, SQOrderList ordersList, SQSettings settings, SQStats statsLong, SQStats statsShort) throws Exception {
		rResult.clear();
		
		avgLossForExpectancy = stats.getDouble(StatsConst.AVG_LOSS);
        if(avgLossForExpectancy == 0) {
        	avgLossForExpectancy = 1;
        }
        
        avgLossForExpectancy = Math.abs(avgLossForExpectancy);
	}

	@Override
	public void computeForOrder(SQStats stats, StatsTypeCombination combination, SQOrder order, SQSettings settings, SQStats statsLong, SQStats statsShort) throws Exception {
   		rResult.add(getPLByStatsType(order, combination) / avgLossForExpectancy);
	}

	@Override
	public void endCompute(SQStats stats, StatsTypeCombination combination, SQOrderList ordersList, SQSettings settings, SQStats statsLong, SQStats statsShort) throws Exception {
		int numberOfTrades = stats.getInt(StatsConst.NUMBER_OF_TRADES);
		double avgTradesPerMonth = stats.getDouble(StatsConst.AVG_TRADES_PER_MONTH);
		
		double sqn = 0;
		double sqn_score = 0;

		if(numberOfTrades > 0) {
        	sqn = computeOriginalSQN(rResult);
        	sqn_score = computeSQNScore(rResult) * (avgTradesPerMonth * 12f) / 100f;
        }		
		
	    stats.set(StatsConst.SQN, SQUtils.round2(sqn));
	    stats.set(StatsConst.SQN_SCORE, SQUtils.round2(sqn_score));
		
	    rResult.clear();
	}
	
	private StatFunctions statFunctions = new StatFunctions();
	
	private double computeOriginalSQN(ArrayList<Double> rResults) {
		if(rResults.size() < 3) return 0;
		
		double meanPct = statFunctions.computeAverage(rResults);
        double stddevPct = statFunctions.computeStdev(meanPct, rResults);		
		
        if(stddevPct == 0) return 0;
        
		return (double) ((meanPct / stddevPct) * Math.sqrt(rResults.size()));
	}
	
	private double computeSQNScore(ArrayList<Double> orders) {
		double sqn_avg = 0;
		int indexFrom = 0;
		int chunks = 100;
		int iterations = 0;
		
		while(true) {
			int size = indexFrom + chunks;
			if(size > orders.size()) {
				size = orders.size();
			}
			
			double sqn = computeSQNOn100Orders(orders, indexFrom, size);
			double coef = 1f;
			
			if((size - indexFrom) < chunks) {
				coef = ((double) (size - indexFrom)) / (double) chunks;
			}
			
			sqn_avg += sqn * coef;
			iterations++;
			
			indexFrom += chunks;
			if(indexFrom > orders.size() || size == orders.size()) {
				break;
			}
		}
		
		if(iterations == 0) return 0;
		
		sqn_avg /= (double) iterations;
		
		return sqn_avg;
	}
		
	private double computeSQNOn100Orders(ArrayList<Double> orders, int indexFrom, int size) {
        double meanPct = statFunctions.computeAverage(orders, indexFrom, size);
        double stddevPct = statFunctions.computeStdev(meanPct, orders, indexFrom, size);		
		
        if(meanPct == 0 || stddevPct == 0) return 0;
        
		return (double) ((meanPct / stddevPct) * Math.sqrt((double) (size - indexFrom)));
	}	
}