(ns propeller.problems.stocks.stock-regression
  (:require
   [clojure.core :refer [read-string]]
   [propeller.tools.math :as math]
   [propeller.genome :as genome]
   [propeller.gp :as gp]
   [propeller.push.interpreter :as interpreter]
   [propeller.push.state :as state]
   [propeller.tools.loading :as loading]
   [propeller.push.instructions :as instructions]
   [propeller.utils :as utils]))

(def train-and-test-data
  "Train and test data split for GLD. Might change this in a bit."
  (let [data (loading/basic-load-data-csv "src/propeller/problems/stocks/data/dailyret_GLD.csv")
        train-len (math/round (* (count data) 0.8))
        test-len (- (count data) train-len)
        ]
    {:train (map (fn [x] {:input1 (vector (first (map read-string x))) :output1 (vector (fnext (map read-string x)))}) (doall (take train-len data)))
     :test (map (fn [x] {:input1 (vector (first (map read-string x))) :output1 (vector (fnext (map read-string x)))}) (doall (take-last test-len data)))}))

;; Random float between -100.0 and 100.0
(defn random-float "Random float between -100.0 and 100.0" [] (- (* (rand) 200) 100.0))

; Random integer between -100 and 100
(defn random-int "Random integer between -100 and 100" [] (- (rand-int 201) 100))

(def instructions
  "stack-specific instructions, input instructions, close, and constants"
  (utils/not-lazy
   (concat
    (instructions/get-stack-instructions #{:float :integer :boolean :signal :exec :code})
    (list :in1)
    (list random-float random-int))))
 
(defn error-function
  "Finds the sharpe ratio for a given individual. Heavy penalty if the stack is empty."
  ([argmap data individual]
   (let [program (genome/plushy->push (:plushy individual) argmap)
         all-inputs (map (fn [x] (first (:input1 x))) data)
         daily-returns (map (fn [x] (first (:output1 x))) data)
         ;outputs (map (fn [input]
         ;               (state/peek-stack
         ;                 (interpreter/interpret-program
         ;                   program
         ;                   (assoc state/empty-state :input {:in1 input})
         ;                   (:step-limit argmap))
         ;                 :integer))
         ;             inputs)
         ;errors (map (fn [correct-output output]
         ;              (if (= output :no-stack-item)
         ;                1000000
         ;                (math/abs (- correct-output output))))
         ;            correct-outputs
         ;            outputs)]
         ]
     (loop [sharpes []
            returns []
            inputs all-inputs
            holding-position? false ; true when :buy passes, false when :sell passes
            trade-signal (if (empty? inputs)
                           :end-loop
                           (state/peek-stack
                            (interpreter/interpret-program
                             program
                             (assoc state/empty-state :input {:in1 (first inputs)})
                             (:step-limit argmap))
                            :signal))]
       ; TODO: I'm not confident on calculating the sharpe ratio yet.
       ; I need time to learn. I'll come back to this when I'm ready.
       ;(assoc individual
       ;       :behaviors outputs
       ;       :errors errors
       ;       :total-error #?(:clj  (apply +' errors)
       ;                       :cljs (apply + errors)))
       ))))

(def stock-argmap
  {:instructions             instructions
   :error-function           error-function
   :training-data            (:train train-and-test-data)
   :testing-data             (:test train-and-test-data)
   :max-generations          300
   :population-size          1000
   :max-initial-plushy-size  5
   :step-limit               1000
   :parent-selection         :lexicase
   :tournament-size          5
   :umad-rate                0.1
   :variation                {:umad 1.0 :crossover 0.0}
   :elitism                  false
   :closes                   :balanced
   :transaction-cost         0.0005})

(defn -main
  "Runs the top-level genetic programming function, giving it a map of 
  arguments with defaults that can be overridden from the command line
  or through a passed map."
  [& args]
  (gp/gp
    (merge
     stock-argmap
     (apply hash-map (map #(if (string? %) (read-string %) %) args)))))
