[
  {
    "path": ".gitignore",
    "content": "node_modules\npackage-lock.json\nresults/config\nconfig\n!config/sample-config.js\n"
  },
  {
    "path": "README.md",
    "content": "# gekkoga\nGenetic Algorithm for solving optimization of trading strategies using Gekko\n## Installation\n    node >= 7.x.x required!\n1) git clone https://github.com/gekkowarez/gekkoga.git\n2) cd gekkoga\n3) npm install\n4) cp config/sample-config.js config/your-config.js\n5) modify your-config.js make sure you have data for currency/asset pair and the daterange\n6) node run.js -c config/your-config.js\n#### tmux usage:\n1) sudo apt-get install tmux\n2) tmux new -s gekkoga\n3) go to the web folder in your gekko installation (gekko/web)\n4) node –max-old-space-size=8192 server.js (for macos: --max_old_space_size=8192)\n5) hold CTRL and press b, then press % (to split the screen)\n6) goto your gekkoga clone directory\n7) node run.js -c config/your-config.js \n8) hold CTRL and press b, then hit d (to detach and run in the background)\n9) tmux attach -t gekkoga (to reattach and bring gekkoga to foreground)\n"
  },
  {
    "path": "index.js",
    "content": "const async = require('async');\nconst nodemailer = require('nodemailer');\nconst randomExt = require('random-ext');\nconst rp = require('request-promise');\nconst { some } = require('bluebird');\nconst fs = require('fs-extra');\nconst flat = require('flat');\nconst util = require('util');\n\nclass Ga {\n\n  constructor({ gekkoConfig, stratName, mainObjective, populationAmt, parallelqueries, minSharpe, variation, mutateElements, notifications, getProperties, apiUrl }, configName ) {\n    this.configName = configName.replace(/\\.js|config\\//gi, \"\");\n    this.stratName = stratName;\n    this.mainObjective = mainObjective;\n    this.getProperties = getProperties;\n    this.apiUrl = apiUrl;\n    this.sendemail = notifications.email.enabled;\n    this.senderservice = notifications.email.senderservice;\n    this.sender = notifications.email.sender;\n    this.senderpass = notifications.email.senderpass;\n    this.receiver = notifications.email.receiver;\n    this.currency = gekkoConfig.watch.currency;\n    this.asset = gekkoConfig.watch.asset;\n    this.previousBestParams = null;\n    this.populationAmt = populationAmt;\n    this.parallelqueries = parallelqueries;\n    this.minSharpe = minSharpe;\n    this.variation = variation;\n    this.mutateElements = mutateElements;\n    this.baseConfig = {\n      watch: gekkoConfig.watch,\n      paperTrader: {\n        slippage: gekkoConfig.slippage,\n        feeTaker: gekkoConfig.feeTaker,\n        feeMaker: gekkoConfig.feeMaker,\n        feeUsing: gekkoConfig.feeUsing,\n        simulationBalance: gekkoConfig.simulationBalance,\n        reportRoundtrips: true,\n        enabled: true\n      },\n      writer: {\n        enabled: false,\n        logpath: ''\n      },\n      tradingAdvisor: {\n        enabled: true,\n        method: this.stratName,\n      },\n      trader: {\n        enabled: false,\n      },\n      backtest: {\n        daterange: gekkoConfig.daterange\n      },\n      backtestResultExporter: {\n        enabled: true,\n        writeToDisk: false,\n        data: {\n          stratUpdates: false,\n          roundtrips: false,\n          stratCandles: true,\n          stratCandleProps: [\n              'close',\n              'start'\n          ],\n          trades: false\n        }\n      },\n      performanceAnalyzer: {\n        riskFreeReturn: 5,\n        enabled: true\n      },\n      valid: true\n    };\n\n\n  }\n\n  // Checks for, and if present loads old .json parameters\n  async loadBreakPoint() {\n\n    const fileName = `./results/${this.configName}-${this.currency}_${this.asset}.json`;\n    const exists = fs.existsSync(fileName);\n\n    if(exists){\n\n      console.log('Previous config found, loading...');\n      return fs.readFile(fileName, 'utf8').then(JSON.parse);\n\n    }\n\n    return false;\n\n  }\n\n  // Allows queued execution via Promise\n  queue(items, parallel, ftc) {\n\n    const queued = [];\n\n    return Promise.all(items.map((item) => {\n\n      const mustComplete = Math.max(0, queued.length - parallel + 1);\n      const exec = some(queued, mustComplete).then(() => ftc(item));\n      queued.push(exec);\n\n      return exec;\n\n    }));\n\n  }\n\n  // Creates a random gene if prop='all', creates one random property otherwise\n  createGene(prop) {\n    // Is first generation, and previous props available, load them as a start-point\n    if (this.previousBestParams === null || this.runstarted) {\n      let properties = flat.flatten(this.getProperties());\n      return prop === 'all' ? flat.unflatten(properties) : properties[prop];\n    } else if ( this.previousBestParams.parameters && !this.runstarted) {\n      this.runstarted = 1;\n      let properties = flat.flatten(this.previousBestParams.parameters);\n      return prop === 'all' ? flat.unflatten(properties) : properties[prop];\n    } else {\n      throw Error('Could not resolve a suitable state for previousBestParams');\n    }\n  }\n\n  // Creates random population from genes\n  createPopulation() {\n    let population = [];\n\n    for (let i = 0; i < this.populationAmt; i++) {\n\n      population.push(this.createGene('all'));\n\n    }\n\n    return population;\n  }\n\n  // Pairs two parents returning two new childs\n  crossover(a, b) {\n\n    let len = Object.keys(a).length;\n    let crossPoint = randomExt.integer(len - 1, 1);\n    let tmpA = {};\n    let tmpB = {};\n    let currPoint = 0;\n\n    for (let i in a) {\n\n      if (a.hasOwnProperty(i) && b.hasOwnProperty(i)) {\n\n        if (currPoint < crossPoint) {\n\n          tmpA[i] = a[i];\n          tmpB[i] = b[i];\n\n        } else {\n\n          tmpA[i] = b[i];\n          tmpB[i] = a[i];\n\n        }\n\n      }\n\n      currPoint++;\n\n    }\n\n    return [tmpA, tmpB];\n  }\n\n  // Mutates object a at most maxAmount times\n  mutate(a, maxAmount) {\n\n    let amt = randomExt.integer(maxAmount, 0);\n    // flatten, mutate, return unflattened object\n    let flattened = flat.flatten(a);\n    let allProps = Object.keys(flattened);\n\n    for (let i = 0; i < amt; i++) {\n      let position = randomExt.integer(Object.keys(allProps).length - 1, 0);\n      let prop = allProps[position];\n      flattened[prop] = this.createGene(prop);\n    }\n\n    return flat.unflatten(flattened);\n  }\n\n  // For the given population and fitness, returns new population and max score\n  runEpoch(population, populationProfits, populationSharpes, populationScores) {\n    let selectionProb = [];\n    let fitnessSum = 0;\n    let maxFitness = [0, 0, 0, 0];\n\n    for (let i = 0; i < this.populationAmt; i++) {\n\n     if (this.mainObjective == 'score') {\n\n       if (populationProfits[i] < 0 && populationSharpes[i] < 0) {\n\n         populationScores[i] = (populationProfits[i] * populationSharpes[i]) * -1;\n\n       } else {\n\n         populationScores[i] = Math.tanh(populationProfits[i] / 3) * Math.tanh(populationSharpes[i] / 0.25);\n\n       }\n\n       if (populationScores[i] > maxFitness[2]) {\n\n         maxFitness = [populationProfits[i], populationSharpes[i], populationScores[i], i];\n\n       }\n\n     } else if (this.mainObjective == 'profit') {\n\n        if (populationProfits[i] > maxFitness[0]) {\n\n          maxFitness = [populationProfits[i], populationSharpes[i], populationScores[i], i];\n\n        }\n\n      } else if (this.mainObjective == 'profitForMinSharpe') {\n\n        if (populationProfits[i] > maxFitness[0] && populationSharpes[i] >= this.minSharpe) {\n\n          maxFitness = [populationProfits[i], populationSharpes[i], populationScores[i], i];\n\n        }\n\n      }\n\n      fitnessSum += populationProfits[i];\n\n    }\n\n    if (fitnessSum === 0) {\n\n      for (let j = 0; j < this.populationAmt; j++) {\n\n        selectionProb[j] = 1 / this.populationAmt;\n\n      }\n\n    } else {\n      for (let j = 0; j < this.populationAmt; j++) {\n        selectionProb[j] = populationProfits[j] / fitnessSum;\n      }\n\n    }\n\n    let newPopulation = [];\n\n    while (newPopulation.length < this.populationAmt * (1 - this.variation)) {\n\n      let a, b;\n      let selectedProb = randomExt.float(1, 0);\n\n      for (let k = 0; k < this.populationAmt; k++) {\n\n        selectedProb -= selectionProb[k];\n\n        if (selectedProb <= 0) {\n\n          a = population[k];\n          break;\n\n        }\n\n      }\n      selectedProb = randomExt.float(1, 0);\n\n      for (let k = 0; k < this.populationAmt; k++) {\n\n        selectedProb -= selectionProb[k];\n\n        if (selectedProb <= 0) {\n\n          b = population[k];\n          break;\n\n        }\n\n      }\n\n      let res = this.crossover(this.mutate(a, this.mutateElements), this.mutate(b, this.mutateElements));\n      newPopulation.push(res[0]);\n      newPopulation.push(res[1]);\n\n    }\n\n    for (let l = 0; l < this.populationAmt * this.variation; l++) {\n\n      newPopulation.push(this.createGene('all'));\n\n    }\n\n    return [newPopulation, maxFitness];\n  }\n\n  getConfig(data) {\n\n    const conf = Object.assign({}, this.baseConfig);\n\n    conf[this.stratName] = Object.keys(data).reduce((acc, key) => {\n      acc[key] = data[key];\n      return acc;\n    }, {});\n\n    Object.assign(conf.tradingAdvisor, {\n      candleSize: data.candleSize,\n      historySize: data.historySize\n    });\n\n    return conf;\n\n  }\n\n  // Calls api for every element in testSeries and returns gain for each\n  async fitnessApi(testsSeries) {\n\n    const numberOfParallelQueries = this.parallelqueries;\n\n    const results = await this.queue(testsSeries, numberOfParallelQueries, async (data) => {\n\n      const outconfig = this.getConfig(data);\n      const body = await rp.post({\n        url: `${this.apiUrl}/api/backtest`,\n        json: true,\n        body: outconfig,\n        headers: { 'Content-Type': 'application/json' },\n        timeout: 3600000\n      });\n\n      // These properties will be outputted every epoch, remove property if not needed\n      const properties = ['balance', 'profit', 'sharpe', 'market', 'relativeProfit', 'yearlyProfit', 'relativeYearlyProfit', 'startPrice', 'endPrice', 'trades'];\n      const report = body.performanceReport;\n      let result = { profit: 0, metrics: false };\n\n      if (report) {\n\n        let picked = properties.reduce((o, k) => {\n\n          o[k] = report[k];\n\n          return o;\n\n        }, {});\n\n        result = { profit: body.performanceReport.profit, sharpe: body.performanceReport.sharpe, metrics: picked };\n\n      }\n\n      return result;\n\n    });\n\n    let scores = [];\n    let profits = [];\n    let sharpes = [];\n    let otherMetrics = [];\n\n    for (let i in results) {\n\n      if (results.hasOwnProperty(i)) {\n\n        scores.push(results[i]['profit'] * results[i]['sharpe']);\n        profits.push(results[i]['profit']);\n        sharpes.push(results[i]['sharpe']);\n        otherMetrics.push(results[i]['metrics']);\n\n      }\n\n    }\n\n    return { scores, profits, sharpes, otherMetrics };\n\n  }\n\n  async run() {\n    // Check for old break point\n    const loaded_config = await this.loadBreakPoint();\n    let population = this.createPopulation();\n    let epochNumber = 0;\n    let populationScores;\n    let populationProfits;\n    let populationSharpes;\n    let otherPopulationMetrics;\n    let allTimeMaximum = {\n      parameters: {},\n      score: -5,\n      profit: -5,\n      sharpe: -5,\n      epochNumber: 0,\n      otherMetrics: {}\n    };\n\n    if (loaded_config) {\n\n      console.log(`Loaded previous config from ${this.configName}-${this.currency}_${this.asset}.json`);\n      this.previousBestParams = loaded_config;\n\n      epochNumber = this.previousBestParams.epochNumber;\n      populationScores = this.previousBestParams.score;\n      populationProfits = this.previousBestParams.profit;\n      populationSharpes = this.previousBestParams.sharpe;\n      otherPopulationMetrics = this.previousBestParams.otherMetrics;\n      allTimeMaximum = {\n        parameters: this.previousBestParams.parameters,\n        score: this.previousBestParams.score,\n        profit: this.previousBestParams.profit,\n        sharpe: this.previousBestParams.sharpe,\n        epochNumber: this.previousBestParams.epochNumber,\n        otherMetrics: this.previousBestParams.otherMetrics\n      };\n\n      console.log('Resuming previous run...');\n\n    } else {\n\n      console.log('No previous run data, starting from scratch!');\n\n    }\n\n    console.log(`Starting GA with epoch populations of ${this.populationAmt}, running ${this.parallelqueries} units at a time!`);\n\n    while (1) {\n\n      const startTime = new Date().getTime();\n      const res = await this.fitnessApi(population);\n\n      populationScores = res.scores;\n      populationProfits = res.profits;\n      populationSharpes = res.sharpes;\n      otherPopulationMetrics = res.otherMetrics;\n\n      let endTime = new Date().getTime();\n      epochNumber++;\n      let results = this.runEpoch(population, populationProfits, populationSharpes, populationScores);\n      let newPopulation = results[0];\n      let maxResult = results[1];\n      let score = maxResult[2];\n      let profit = maxResult[0];\n      let sharpe = maxResult[1];\n      let position = maxResult[3];\n\n      this.notifynewhigh = false;\n      if (this.mainObjective == 'score') {\n        if (score >= allTimeMaximum.score) {\n            this.notifynewhigh = true;\n            allTimeMaximum.parameters = population[position];\n            allTimeMaximum.otherMetrics = otherPopulationMetrics[position];\n            allTimeMaximum.score = score;\n            allTimeMaximum.profit = profit;\n            allTimeMaximum.sharpe = sharpe;\n            allTimeMaximum.epochNumber = epochNumber;\n\n        }\n      } else if (this.mainObjective == 'profit') {\n        if (profit >= allTimeMaximum.profit) {\n            this.notifynewhigh = true;\n            allTimeMaximum.parameters = population[position];\n            allTimeMaximum.otherMetrics = otherPopulationMetrics[position];\n            allTimeMaximum.score = score;\n            allTimeMaximum.profit = profit;\n            allTimeMaximum.sharpe = sharpe;\n            allTimeMaximum.epochNumber = epochNumber;\n\n        }\n      } else if (this.mainObjective == 'profitForMinSharpe') {\n        if (profit >= allTimeMaximum.profit && sharpe >= this.minSharpe) {\n            this.notifynewhigh = true;\n            allTimeMaximum.parameters = population[position];\n            allTimeMaximum.otherMetrics = otherPopulationMetrics[position];\n            allTimeMaximum.score = score;\n            allTimeMaximum.profit = profit;\n            allTimeMaximum.sharpe = sharpe;\n            allTimeMaximum.epochNumber = epochNumber;\n\n        }\n      }\n\n      console.log(`\n    --------------------------------------------------------------\n    Epoch number: ${epochNumber}\n    Time it took (seconds): ${(endTime - startTime) / 1000}\n    Max score: ${score}\n    Max profit: ${profit} ${this.currency}\n    Max sharpe: ${sharpe}\n    Max profit position: ${position}\n    Max parameters:\n    `,\n        util.inspect(population[position], false, null),\n        `\n    Other metrics:\n    `,\n        otherPopulationMetrics[position]);\n\n      // Prints out the whole population with its fitness,\n      // useful for finding properties that make no sense and debugging\n      // for(let element in population){\n      //\n      //     console.log('Fitness: '+populationProfits[element]+' Properties:');\n      //     console.log(population[element]);\n      //\n      // }\n\n      console.log(`\n    --------------------------------------------------------------\n    Global Maximums:\n    Score: ${allTimeMaximum.score}\n    Profit: ${allTimeMaximum.profit} ${this.currency}\n    Sharpe: ${allTimeMaximum.sharpe}\n    parameters: \\n\\r`,\n    util.inspect(allTimeMaximum.parameters, false, null),\n    `\n    Global maximum so far:\n    `,\n        allTimeMaximum.otherMetrics,\n        `\n    --------------------------------------------------------------\n    `);\n\n      // store in json\n      const json = JSON.stringify(allTimeMaximum);\n      await fs.writeFile(`./results/${this.configName}-${this.currency}_${this.asset}.json`, json, 'utf8').catch(err => console.log(err) );\n\n      if (this.sendemail && this.notifynewhigh) {\n        var transporter = nodemailer.createTransport({\n          service: this.senderservice,\n          auth: {\n            user: this.sender,\n            pass: this.senderpass\n          }\n        });\n        var mailOptions = {\n          from: this.sender,\n          to: this.receiver,\n          subject: `Profit: ${allTimeMaximum.profit} ${this.currency}`,\n          text: json\n        };\n        transporter.sendMail(mailOptions, function(error, info){\n          if (error) {\n            console.log(error);\n          } else {\n            console.log('Email sent: ' + info.response);\n          }\n        });\n      }\n\n\n      population = newPopulation;\n\n    }\n\n    console.log(`Finished!\n  All time maximum:\n  ${allTimeMaximum}`);\n\n  }\n\n}\n\n\nmodule.exports = Ga;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"gekkoga\",\n  \"version\": \"2.0.0\",\n  \"description\": \"GA for Gekko\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/gekkowarez/gekkoga.git\"\n  },\n  \"keywords\": [\n    \"gekkoga\"\n  ],\n  \"author\": \"gekkowarez\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/gekkowarez/gekkoga/issues\"\n  },\n  \"homepage\": \"https://github.com/gekkowarez/gekkoga#readme\",\n  \"dependencies\": {\n    \"async\": \"^2.6.0\",\n    \"commander\": \"^2.11.0\",\n    \"flat\": \"^4.0.0\",\n    \"fs-extra\": \"^4.0.2\",\n    \"nodemailer\": \"^4.4.1\",\n    \"random-ext\": \"^2.6.1\",\n    \"request\": \"^2.83.0\",\n    \"request-promise\": \"^4.2.2\"\n  }\n}\n"
  },
  {
    "path": "results/.gitkeep",
    "content": ""
  },
  {
    "path": "run.js",
    "content": "const Ga = require('./');\nconst fs = require('fs');\n\nvar runGA = require('commander');\n\nrunGA\n  .version('0.0.3?')\n  .option('-c --config <config file>', 'config')\n  .parse(process.argv);\n\nif(!runGA.config||!fs.existsSync(runGA.config)){\n  console.error(\"\\n\",\" error: option `-c --config <config file>' argument or file missing\",\"\\n\");\n  process.exit(1);\n}\n\nconst config = require('./'+runGA.config);\nconst ga = new Ga(config,runGA.config);\n\nga.run().catch(err => console.error(err) );\n"
  }
]