<commit_before>/**
 * Copyright (c) 2015-2017, <NAME> All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * * Neither the name of [project] nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.systematic.trading.signals;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.systematic.trading.data.TradingDayPrices;
import com.systematic.trading.signal.IndicatorSignalId;
import com.systematic.trading.signal.event.SignalAnalysisEvent;
import com.systematic.trading.signal.event.SignalAnalysisListener;
import com.systematic.trading.signals.indicator.IndicatorSignals;
import com.systematic.trading.signals.model.BuySignal;
import com.systematic.trading.signals.model.TradingDayPricesDateOrder;
import com.systematic.trading.signals.model.event.IndicatorSignalEvent;
import com.systematic.trading.signals.model.filter.SignalFilter;
import com.systematic.trading.signals.model.indicator.IndicatorSignal;

public class AnalysisLongBuySignals implements AnalysisBuySignals {

	/** When there are no signals generators, no input is needed.*/
	private static final int NO_DAYS_REQUIRED = 0;

	/** Used for converting from base zero to base one counting systems. */
	private static final int CONVERT_BASE_ZERO_TO_BASE_ONE = 1;

	/** Default ordering of prices. */
	private static final TradingDayPricesDateOrder TRADING_DAY_ORDER_BY_DATE = new TradingDayPricesDateOrder();

	/** Most number of trading day data used by the signal generators. */
	private final int requiredNumberOfTradingDays;

	/** Listeners interested in signal analysis events. */
	private final List<SignalAnalysisListener> listeners = new ArrayList<>();

	private final SignalFilter[] filters;
	private final IndicatorSignals[] generators;

	public AnalysisLongBuySignals( final IndicatorSignals[] generators, final SignalFilter[] filters ) {
		validateInput(generators, filters);
		this.generators = generators;
		this.filters = filters;
		this.requiredNumberOfTradingDays = getRequiredNumberOfTradingDays(generators);
	}

	//TODO validator
	private void validateInput( final IndicatorSignals[] generators, final SignalFilter[] filters ) {
		if (generators == null) {
			throw new IllegalArgumentException("Expecting a non-null list of generators");
		}
		if (generators.length == 0) {
			throw new IllegalArgumentException("Expecting a non-empty list of generators");
		}
		if (filters == null) {
			throw new IllegalArgumentException("Expecting a non-null list of filters");
		}
		if (filters.length == 0) {
			throw new IllegalArgumentException("Expecting a non-empty list of filters");
		}
	}

	private int getRequiredNumberOfTradingDays( final IndicatorSignals[] requiredGenerators ) {

		final List<Integer> requiredTradingDays = new ArrayList<>();
		for (final IndicatorSignals generator : requiredGenerators) {
			requiredTradingDays.add(generator.getRequiredNumberOfTradingDays());
		}

		return requiredTradingDays.isEmpty() ? NO_DAYS_REQUIRED
		        : Collections.max(requiredTradingDays) + CONVERT_BASE_ZERO_TO_BASE_ONE;
	}

	//TODO the given data should already be sorted, change to a sorted map, keyed by LocalDate
	@Override
	public List<BuySignal> analyse( final TradingDayPrices[] data ) {

		// Correct the ordering from earliest to latest
		Arrays.sort(data, TRADING_DAY_ORDER_BY_DATE);

		// Generate the indicator signals
		final Map<IndicatorSignalId, List<IndicatorSignal>> indicatorSignals = getSignals(data);

		final LocalDate latestTradingDate = getLatestTradingDate(data);
		final List<BuySignal> signals = new ArrayList<>();

		// Apply the rule filters
		for (final SignalFilter filter : filters) {
			signals.addAll(filter.apply(indicatorSignals, latestTradingDate));
		}

		return signals;
	}

	private LocalDate getLatestTradingDate( final TradingDayPrices[] data ) {
		return data.length > 1 ? data[data.length - 1].getDate() : LocalDate.now();
	}

	private Map<IndicatorSignalId, List<IndicatorSignal>> getSignals( final TradingDayPrices[] data ) {
		final Map<IndicatorSignalId, List<IndicatorSignal>> indicatorSignals = new HashMap<>();<commit_msg>refactoring<commit_after>/**
 * Copyright (c) 2015-2017, <NAME> All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this list of conditions
 * and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * * Neither the name of [project] nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.systematic.trading.signals;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.systematic.trading.data.TradingDayPrices;
import com.systematic.trading.signal.IndicatorSignalId;
import com.systematic.trading.signal.event.SignalAnalysisEvent;
import com.systematic.trading.signal.event.SignalAnalysisListener;
import com.systematic.trading.signals.indicator.IndicatorSignals;
import com.systematic.trading.signals.model.BuySignal;
import com.systematic.trading.signals.model.TradingDayPricesDateOrder;
import com.systematic.trading.signals.model.event.IndicatorSignalEvent;
import com.systematic.trading.signals.model.filter.SignalFilter;
import com.systematic.trading.signals.model.indicator.IndicatorSignal;

public class AnalysisLongBuySignals implements AnalysisBuySignals {

	/** Used for converting from base zero to base one counting systems. */
	private static final int CONVERT_BASE_ZERO_TO_BASE_ONE = 1;

	/** Default ordering of prices. */
	private static final TradingDayPricesDateOrder TRADING_DAY_ORDER_BY_DATE = new TradingDayPricesDateOrder();

	/** Most number of trading day data used by the signal generators. */
	private final int requiredNumberOfTradingDays;

	/** Listeners interested in signal analysis events. */
	private final List<SignalAnalysisListener> listeners = new ArrayList<>();

	private final SignalFilter[] filters;
	private final IndicatorSignals[] generators;

	public AnalysisLongBuySignals( final IndicatorSignals[] generators, final SignalFilter[] filters ) {
		validateInput(generators, filters);
		this.generators = generators;
		this.filters = filters;
		this.requiredNumberOfTradingDays = getRequiredNumberOfTradingDays(generators);

		if (requiredNumberOfTradingDays < 1) {
			throw new IllegalArgumentException(String
			        .format("Expecting at least one trading day required from generators %s", generators.toString()));
		}
	}

	//TODO validator
	private void validateInput( final IndicatorSignals[] generators, final SignalFilter[] filters ) {
		if (generators == null) {
			throw new IllegalArgumentException("Expecting a non-null list of generators");
		}
		if (generators.length == 0) {
			throw new IllegalArgumentException("Expecting a non-empty list of generators");
		}
		if (filters == null) {
			throw new IllegalArgumentException("Expecting a non-null list of filters");
		}
		if (filters.length == 0) {
			throw new IllegalArgumentException("Expecting a non-empty list of filters");
		}
	}

	private int getRequiredNumberOfTradingDays( final IndicatorSignals[] requiredGenerators ) {

		final List<Integer> requiredTradingDays = new ArrayList<>();
		for (final IndicatorSignals generator : requiredGenerators) {
			requiredTradingDays.add(generator.getRequiredNumberOfTradingDays());
		}

		return Collections.max(requiredTradingDays) + CONVERT_BASE_ZERO_TO_BASE_ONE;
	}

	//TODO the given data should already be sorted, change to a sorted map, keyed by LocalDate
	@Override
	public List<BuySignal> analyse( final TradingDayPrices[] data ) {

		//TODO avoid this sort by using a SortedMap, data should already be sorted so it's only linear time complexity
		// Correct the ordering from earliest to latest
		Arrays.sort(data, TRADING_DAY_ORDER_BY_DATE);

		// Generate the indicator signals
		final Map<IndicatorSignalId, List<IndicatorSignal>> indicatorSignals = calculateSignals(data);
		final LocalDate latestTradingDate = getLatestTradingDate(data);

		return filterSignals(indicatorSignals, latestTradingDate);
	}

	private List<BuySignal> filterSignals( final Map<IndicatorSignalId, List<IndicatorSignal>> indicatorSignals,
	        final LocalDate latestTradingDate ) {
		final List<BuySignal> signals = new ArrayList<>();

		// Apply the rule filters
		for (final SignalFilter filter : filters) {
			signals.addAll(filter.apply(indicatorSignals, latestTradingDate));
		}

		return signals;
	}

	private LocalDate getLatestTradingDate( final TradingDayPrices[] data ) {
		return data[data.length - 1].getDate();
	}

	private Map<IndicatorSignalId, List<IndicatorSignal>> calculateSignals( final TradingDayPrices[] data ) {
		final Map<IndicatorSignalId, List<IndicatorSignal>> indicatorSignals = new HashMap<>();
{'fraction_non_alphanumeric': 0.0783442265795207, 'fraction_numerical': 0.002352941176470588, 'mean_word_length': 5.257360959651036, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5112230', 'n_tokens_mistral': 3291, 'n_tokens_neox': 2999, 'n_words': 1067}