#region License Information
/* HeuristicLab
* Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
*
* This file is part of HeuristicLab.
*
* HeuristicLab 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.
*
* HeuristicLab 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 HeuristicLab. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using HeuristicLab.Common;
namespace HeuristicLab.Problems.DataAnalysis.Trading {
public class OnlineProfitCalculator : IOnlineCalculator {
private int position; // currently held position: -1: short, 0: out of market, 1: long
private readonly double transactionCost;
private int count; // only necessary to reset position and total profit on the first data point
private double totalProfit;
public double Profit {
get { return totalProfit; }
}
public OnlineProfitCalculator(double transactionCost) {
this.transactionCost = transactionCost;
Reset();
}
#region IOnlineCalculator Members
public OnlineCalculatorError ErrorState {
get { return OnlineCalculatorError.None; }
}
public double Value {
get { return Profit; }
}
public void Reset() {
position = 0;
count = 0;
totalProfit = 0.0;
}
public void Add(double actualReturn, double signal) {
double profit = 0.0;
if (count == 0) {
position = (int)signal;
profit = 0;
count++;
} else {
if (position == 0 && signal.IsAlmost(0)) {
} else if (position == 0 && signal.IsAlmost(1)) {
position = 1;
profit = -transactionCost;
} else if (position == 0 && signal.IsAlmost(-1)) {
position = -1;
profit = -transactionCost;
} else if (position == 1 && signal.IsAlmost(1)) {
profit = actualReturn;
} else if (position == 1 && signal.IsAlmost(0)) {
profit = actualReturn - transactionCost;
position = 0;
} else if (position == 1 && signal.IsAlmost(-1)) {
profit = actualReturn - transactionCost;
position = -1;
} else if (position == -1 && signal.IsAlmost(-1)) {
profit = -actualReturn;
} else if (position == -1 && signal.IsAlmost(0)) {
profit = -actualReturn - transactionCost;
position = 0;
} else if (position == -1 && signal.IsAlmost(1)) {
profit = -actualReturn - transactionCost;
position = 1;
}
count++;
}
totalProfit += profit;
}
#endregion
public static double Calculate(IEnumerable returns, IEnumerable signals, double transactionCost, out OnlineCalculatorError errorState) {
errorState = OnlineCalculatorError.None;
return GetProfits(returns, signals, transactionCost).Sum();
}
public static IEnumerable GetProfits(IEnumerable returns, IEnumerable signals, double transactionCost) {
var calc = new OnlineProfitCalculator(transactionCost);
IEnumerator returnsEnumerator = returns.GetEnumerator();
IEnumerator signalsEnumerator = signals.GetEnumerator();
// always move forward both enumerators (do not use short-circuit evaluation!)
while (returnsEnumerator.MoveNext() & signalsEnumerator.MoveNext()) {
double signal = signalsEnumerator.Current;
double @return = returnsEnumerator.Current;
double prevTotalProfit = calc.Profit;
calc.Add(@return, signal);
double curTotalProfit = calc.Profit;
yield return curTotalProfit - prevTotalProfit;
}
// check if both enumerators are at the end to make sure both enumerations have the same length
if (returnsEnumerator.MoveNext() || signalsEnumerator.MoveNext()) {
throw new ArgumentException("Number of elements in first and second enumeration doesn't match.");
}
}
}
}