using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Security; using System.Text; using System.Threading.Tasks; using cAlgo; using cAlgo.API; using cAlgo.Modular; using Icarus.Indicators.Standard; using Icarus.Optimiser.Core.Genetic; using Icarus.Optimiser.Interfaces; namespace Icarus.Optimiser.Core.Modular { public class ModularStudy : IGeneticStudy { private readonly IcarusOptimiser m_Optimiser; private readonly int m_NumGenerations; private readonly List m_Symbols; private readonly RunSettings m_RunSettings; private readonly List m_DataBrokers; public ModularStudy(IcarusOptimiser optimiser, int numGenerations, List symbols, RunSettings runSettings, List dataBrokers) { m_Optimiser = optimiser; m_NumGenerations = numGenerations; m_Symbols = symbols; m_RunSettings = runSettings; m_DataBrokers = dataBrokers; } public IChromosomeOperator ChromosomeOperator { get; set; } public int NumberOfMutations { get; set; } public int NumberOfCrossovers { get; set; } public int NumberOfNewChromosomes { get; set; } public bool HasStudyFinished(int generationsStudied, ModularChromosome fittestChromosome) { return m_NumGenerations > 0 && generationsStudied >= m_NumGenerations; } public void RunGeneration(List generation) { if (generation.Count == 0) { return; } Func robotFactory = singleSymbolRequest => { return new ModularBot(singleSymbolRequest.SingleConfigRequest.ModularChromosome.EntryModules, singleSymbolRequest.SingleConfigRequest.ModularChromosome.ExitModules); }; m_RunSettings.RobotFactory = robotFactory; m_RunSettings.RobotType = null; m_RunSettings.RobotTypeString = null; foreach (var dataBroker in m_DataBrokers) { RunGenerationWithData(generation, dataBroker); } } private List RunGenerationWithData(List generation, IDataBroker dataBroker) { Console.WriteLine("Running data from {0}", dataBroker.Name); var singleConfigRequests = new List(); m_RunSettings.DataBroker = dataBroker; foreach (var chromosome in generation) { var singleConfigRequest = new SingleConfigRequest { Config = new RunConfig(), // Config is passed through in the form of module instances ModularChromosome = chromosome, Symbols = m_Symbols, RunSettings = m_RunSettings }; chromosome.SingleConfigRequest = singleConfigRequest; singleConfigRequests.Add(singleConfigRequest); } var results = m_Optimiser.RunMultipleSingleConfigs(singleConfigRequests); var tradeCalculator = new TradeCalculator { AccountCurrency = m_RunSettings.AccountCurrency, DataBroker = m_RunSettings.DataBroker }; foreach (var singleConfigResult in results) { // Go through all positions taken on each symbol and treat them as if they were all taken on the same account. Then resize the volume of each position such that // any one position does not risk more than a certain % of the current account. Then simulate all positions and determine the final combined profit. var allPositions = singleConfigResult.AllPositions.OrderBy(x => x.EntryTime); var account = m_RunSettings.StartingAccount; const double maxPercentOfAccount = 2.0; foreach (var position in allPositions) { var volume = GetPositionSize(position.EntryTime, position.SymbolCode, maxPercentOfAccount, position.StartingStopLossPips.Value, account, m_RunSettings.AccountCurrency); var profit = tradeCalculator.CalculateProfit(position.SymbolCode, position.Entry, position.Exit, volume, position.EntryTime, position.TradeType, m_RunSettings.TimeFrame); position.GrossProfit = profit.GrossProfit; position.NetProfit = profit.NetProfit; position.Commission = profit.Commission; position.Spread = m_RunSettings.SpreadEnabled ? SpreadList.Spreads[position.SymbolCode] : 0; if (profit.NetProfit / account < -0.3) { throw new Exception("Trade risked more than 3% of the account"); } account += profit.NetProfit; } var totalProfit = account - m_RunSettings.StartingAccount; var previousFitness = singleConfigResult.Request.ModularChromosome.Fitness; Console.WriteLine("{0} gave {1} profit", dataBroker.Name, totalProfit); if (!previousFitness.HasValue || totalProfit < previousFitness.Value) { if (previousFitness.HasValue) { Console.WriteLine("{0} chosen as new fitness because it is lower", totalProfit); } singleConfigResult.Request.ModularChromosome.Fitness = totalProfit; } } return results; } public void GenerationFinished(ModularChromosome fittestChromosome, long elapsedMilliseconds, List fittestChromosomes, int generationNumber) { ((ModularChromosomeOperator)ChromosomeOperator).BestPerformingModuleConfigs = GetBestPerformingModuleConfigs(fittestChromosomes); Console.WriteLine("---"); Console.WriteLine("Generation {3} finished in {1}ms with an average {2} trades per symbol, fittest chromosome gave {0} profit", fittestChromosome.Fitness, elapsedMilliseconds, fittestChromosome.SingleConfigRequest.Result.AllPositions.Count / fittestChromosome.SingleConfigRequest.Result.SingleSymbolResults.Count, generationNumber); Console.WriteLine("Using {0} for entry and {1} for exit", string.Join(", ", fittestChromosome.EntryModules.Select(x => string.Format("{0} ({1})", x.ModuleDefinition.ModuleType.Name, string.Join(",", x.RunConfig.Parameters.Select(y => y.Value))))), string.Join(", ", fittestChromosome.ExitModules.Select(x => string.Format("{0} ({1})", x.ModuleDefinition.ModuleType.Name, string.Join(",", x.RunConfig.Parameters.Select(y => y.Value)))))); var descending = fittestChromosome.SingleConfigRequest.Result.SingleSymbolResults.OrderByDescending(x => x.Profit); var ascending = fittestChromosome.SingleConfigRequest.Result.SingleSymbolResults.OrderBy(x => x.Profit); var nextThree = descending.Skip(1).Take(3).Select(x => string.Format("{0} ({1})", x.Request.Symbol, x.Profit)); var worstThree = ascending.Take(3).Select(x => string.Format("{0} ({1})", x.Request.Symbol, x.Profit)); Console.WriteLine("Best symbol was {0} with profit {1}, followed by {2}", fittestChromosome.SingleConfigRequest.Result.BestSymbol.Request.Symbol, fittestChromosome.SingleConfigRequest.Result.BestSymbol.Profit, Environment.NewLine + string.Join(", ", nextThree)); Console.WriteLine("Worst symbols were {0}", string.Join(", ", worstThree)); var allModules = fittestChromosomes.SelectMany(x => x.EntryModules).Concat(fittestChromosomes.SelectMany(x => x.ExitModules)).ToList(); var groupedBestModules = allModules.GroupBy(x => x.ModuleDefinition.ModuleType).ToList(); Console.WriteLine("Module performance in descending order is {0}", string.Join(", ", groupedBestModules.OrderByDescending(x => x.Count()).Select(x => string.Format("{0} ({1})", x.Key.Name, x.Count())))); } private Dictionary> GetBestPerformingModuleConfigs(List fittestChromosomes) { return fittestChromosomes.SelectMany(x => x.EntryModules).Union(fittestChromosomes.SelectMany(x => x.ExitModules)).GroupBy(x => x.ModuleDefinition.ModuleType).ToDictionary(x => x.Key, x => x.ToList()); } private long GetPositionSize( DateTime time, string symbolCode, double accountPctRisk, double stopLossPips, double accountBalance, string accountCurrency) { double targetRiskDollars = accountBalance * (accountPctRisk / 100d); double baseCurrencyPerPip = PipSizeList.PipSizes[symbolCode] / m_RunSettings.DataBroker.GetCloseForBar(symbolCode, time, m_RunSettings.TimeFrame); // Get this back into the currency for the account string pairBaseCurrency = symbolCode.Substring(0, 3); string conversionPairCode = SymbolList.Symbols.First(x => x.Contains(pairBaseCurrency) && x.Contains(accountCurrency)); int conversionRefIndex = m_RunSettings.DataBroker.GetIndexForOpenTime(conversionPairCode, m_RunSettings.TimeFrame, time); double conversionCloseAtTime = m_RunSettings.DataBroker.GetCloseForBar(conversionPairCode, time, m_RunSettings.TimeFrame); double conversionPerPip; if (conversionPairCode.StartsWith(pairBaseCurrency)) { conversionPerPip = baseCurrencyPerPip * conversionCloseAtTime; } else { conversionPerPip = baseCurrencyPerPip * (1 / conversionCloseAtTime); } double posSize = targetRiskDollars / (conversionPerPip * stopLossPips); double roundedToThousand = Math.Round(posSize / 1000d) * 1000; return (long)roundedToThousand; } } }