From 0bad3fe649cb5ff23f582a8a286e525359c2994a Mon Sep 17 00:00:00 2001 From: Devroop Kar Date: Wed, 18 Jun 2025 14:02:50 -0400 Subject: [PATCH 1/2] Added support for stock loss I --- CMakeLists.txt | 2 + rnn/generate_nn.cxx | 3 +- rnn/mse.cxx | 72 ++++++++++++++++ rnn/mse.hxx | 5 ++ rnn/rnn.cxx | 128 +++++++++++++++++++++++++++- rnn/rnn.hxx | 21 ++++- rnn/rnn_genome.cxx | 18 +++- rnn/rnn_genome.hxx | 4 +- strategic/CMakeLists.txt | 2 + strategic/evaluate_strategy.cxx | 99 +++++++++++++++++++++ strategic/forecaster.cxx | 31 +++++++ strategic/forecaster.hxx | 71 +++++++++++++++ strategic/oracle.cxx | 20 +++++ strategic/oracle.hxx | 48 +++++++++++ strategic/portfolio.cxx | 25 ++++++ strategic/portfolio.hxx | 53 ++++++++++++ strategic/portfolio_oracle.cxx | 35 ++++++++ strategic/portfolio_oracle.hxx | 48 +++++++++++ strategic/simple_stock_strategy.cxx | 126 +++++++++++++++++++++++++++ strategic/simple_stock_strategy.hxx | 81 ++++++++++++++++++ strategic/state.cxx | 2 + strategic/state.hxx | 18 ++++ strategic/strategy.cxx | 20 +++++ strategic/strategy.hxx | 53 ++++++++++++ strategic/trivial_forecaster.cxx | 63 ++++++++++++++ strategic/trivial_forecaster.hxx | 46 ++++++++++ 26 files changed, 1083 insertions(+), 11 deletions(-) create mode 100644 strategic/CMakeLists.txt create mode 100644 strategic/evaluate_strategy.cxx create mode 100644 strategic/forecaster.cxx create mode 100644 strategic/forecaster.hxx create mode 100644 strategic/oracle.cxx create mode 100644 strategic/oracle.hxx create mode 100644 strategic/portfolio.cxx create mode 100644 strategic/portfolio.hxx create mode 100644 strategic/portfolio_oracle.cxx create mode 100644 strategic/portfolio_oracle.hxx create mode 100644 strategic/simple_stock_strategy.cxx create mode 100644 strategic/simple_stock_strategy.hxx create mode 100644 strategic/state.cxx create mode 100644 strategic/state.hxx create mode 100644 strategic/strategy.cxx create mode 100644 strategic/strategy.hxx create mode 100644 strategic/trivial_forecaster.cxx create mode 100644 strategic/trivial_forecaster.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt index cc629b75..6cbd3b98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,8 @@ add_subdirectory(rnn) add_subdirectory(rnn_tests) add_subdirectory(rnn_examples) +# add_subdirectory(strategic) + # add_subdirectory(opencl) add_subdirectory(weights) diff --git a/rnn/generate_nn.cxx b/rnn/generate_nn.cxx index 8275bf67..2aace7d6 100644 --- a/rnn/generate_nn.cxx +++ b/rnn/generate_nn.cxx @@ -207,11 +207,10 @@ RNN_Genome* get_seed_genome( get_argument(arguments, "--min_recurrent_depth", false, min_recurrent_depth); int32_t max_recurrent_depth = 10; get_argument(arguments, "--max_recurrent_depth", false, max_recurrent_depth); - Log::info("genome path is %s\n", genome_file_name.c_str()); bool epigenetic_weights = argument_exists(arguments, "--epigenetic_weights"); Log::info("Using epigeneratic weights is set to: %s \n", epigenetic_weights ? "True" : "False"); - seed_genome = new RNN_Genome(genome_file_name); + seed_genome = new RNN_Genome(genome_file_name, arguments); seed_genome->set_normalize_bounds( time_series_sets->get_normalize_type(), time_series_sets->get_normalize_mins(), time_series_sets->get_normalize_maxs(), time_series_sets->get_normalize_avgs(), diff --git a/rnn/mse.cxx b/rnn/mse.cxx index ab7b211c..d6c7d9e1 100644 --- a/rnn/mse.cxx +++ b/rnn/mse.cxx @@ -110,3 +110,75 @@ void get_mae(RNN* genome, const vector >& expected, double& mae_s } } } + +template +double signum(T x) { + if (x > 0) { + return 1.0; + } else if (x < 0) { + return -1.0; + } else { + return 0.0; + } +} + +void get_stock_loss (const vector& output_values, const vector& expected, double& loss_sum, vector& deltas, + const vector return_at_t, + const vector return_at_t_plus_1) +{ + loss_sum = 0.0; + double sum_v_i = 0.0; + + for (int32_t i = 0; i < (int32_t) expected.size(); i++) { + sum_v_i += fabs(output_values[i]); + } + + for (int32_t i = 0; i < (int32_t) expected.size(); i++) { + + double v_i = 1.0 * fabs(output_values[i]) / sum_v_i; + loss_sum += v_i * (return_at_t_plus_1[i] - return_at_t[i]) * signum(output_values[i]); + + } + + double d_loss = loss_sum; + for (int32_t i = 0; i < (int32_t) expected.size(); i++) { + deltas[i] *= d_loss; + } +} + +void get_stock_loss (RNN* genome, const vector >& expected, double& loss_sum, vector >& deltas, + const vector return_at_t, + const vector return_at_t_plus_1) +{ + loss_sum = 0.0; + double sum_v_i = 0.0; + double loss; + + for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { + for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { + sum_v_i += fabs(genome->output_nodes[i]->output_values[j]); + } + } + + for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { + + loss = 0.0; + double v_i = 0.0; + + for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { + sum_v_i += fabs(genome->output_nodes[i]->output_values[j]); + } + + for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { + v_i = 1.0 * fabs(genome->output_nodes[i]->output_values[j]) / sum_v_i; + loss_sum += v_i * (return_at_t_plus_1[i] - return_at_t[i]) * signum(genome->output_nodes[i]->output_values[j]); + } + } + + double d_loss = loss_sum * (1.0 / expected[0].size()); + for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { + for (int32_t j = 0; j < (int32_t) expected[i].size(); j++) { + deltas[i][j] *= d_loss; + } + } +} \ No newline at end of file diff --git a/rnn/mse.hxx b/rnn/mse.hxx index ed246974..7ac9502a 100644 --- a/rnn/mse.hxx +++ b/rnn/mse.hxx @@ -12,4 +12,9 @@ void get_mse(RNN* genome, const vector >& expected, double& mse, void get_mae(const vector& output_values, const vector& expected, double& mae, vector& deltas); void get_mae(RNN* genome, const vector >& expected, double& mae, vector >& deltas); +void get_stock_loss(const vector& output_values, const vector& expected, double& loss, vector& deltas, const vector return_at_t, + const vector return_at_t_plus_1); +void get_stock_loss(RNN* genome, const vector >& expected, double& loss, vector >& deltas, const vector return_at_t, + const vector return_at_t_plus_1); + #endif diff --git a/rnn/rnn.cxx b/rnn/rnn.cxx index e8353e5b..49892311 100644 --- a/rnn/rnn.cxx +++ b/rnn/rnn.cxx @@ -24,6 +24,8 @@ using std::uniform_real_distribution; #include using std::vector; +# include + #include "gru_node.hxx" #include "lstm_node.hxx" #include "rnn/rnn_genome.hxx" @@ -200,12 +202,16 @@ RNN::RNN( RNN::RNN( vector& _nodes, vector& _edges, vector& _recurrent_edges, - const vector& input_parameter_names, const vector& output_parameter_names + const vector& input_parameter_names, const vector& output_parameter_names, const vector& arguments ) { nodes = _nodes; edges = _edges; recurrent_edges = _recurrent_edges; + this->arguments = arguments; + + get_argument(arguments, "--loss", false, this->loss); + // sort nodes by depth // sort edges by depth Log::debug("creating rnn with %d nodes, %d edges\n", nodes.size(), edges.size()); @@ -569,6 +575,14 @@ double RNN::prediction_mae( return calculate_error_mae(expected_outputs); } +double RNN::prediction_stock_loss( + const vector >& series_data, const vector >& expected_outputs, bool using_dropout, + bool training, double dropout_probability +) { + forward_pass(series_data, using_dropout, training, dropout_probability); + return calculate_error_stock_loss(series_data, expected_outputs); +} + vector RNN::get_predictions( const vector >& series_data, const vector >& expected_outputs, bool using_dropout, double dropout_probability @@ -654,13 +668,24 @@ void RNN::get_analytic_gradient( const vector >& outputs, double& mse, vector& analytic_gradient, bool using_dropout, bool training, double dropout_probability ) { + analytic_gradient.assign(test_parameters.size(), 0.0); + // get_argument(arguments, "--loss", false, this->loss); + set_weights(test_parameters); forward_pass(inputs, using_dropout, training, dropout_probability); - mse = calculate_error_mse(outputs); - backward_pass(mse * (1.0 / outputs[0].size()) * 2.0, using_dropout, training, dropout_probability); + if (this->loss == "mse"){ + mse = calculate_error_mse(outputs); + backward_pass(mse * (1.0 / outputs[0].size()) * 2.0, using_dropout, training, dropout_probability); + } else if (this->loss == "mae") { + mse = calculate_error_mae(outputs); + backward_pass(mse * (1.0 / outputs[0].size()) * 2.0, using_dropout, training, dropout_probability); + } else { + mse = calculate_error_stock_loss(inputs, outputs); + backward_pass(mse, using_dropout, training, dropout_probability); + } vector current_gradients; @@ -761,3 +786,100 @@ RNN* RNN::copy() { return new RNN(node_copies, edge_copies, recurrent_edge_copies); } */ + + +template +double signum(T x) { + if (x > 0) { + return 1.0; + } else if (x < 0) { + return -1.0; + } else { + return 0.0; + } +} + +// double RNN::calculate_error_stock_loss (const vector >& expected_outputs, +// const vector return_at_t, +// const vector return_at_t_plus_1) +// { +// double loss_sum = 0.0; +// double sum_v_i = 0.0; + +// for (int32_t i = 0; i < (int32_t) output_nodes.size(); i++) { +// for (int32_t j = 0; j < (int32_t) output_nodes[i]->output_values.size(); j++) { +// sum_v_i += fabs(output_nodes[i]->output_values[j]); +// } +// } + +// for (int32_t i = 0; i < (int32_t) output_nodes.size(); i++) { + +// double v_i = 0.0; + +// for (int32_t j = 0; j < (int32_t) output_nodes[i]->output_values.size(); j++) { +// v_i = 1.0 * fabs(output_nodes[i]->output_values[j]) / sum_v_i; +// loss_sum += v_i * (return_at_t_plus_1[i] - return_at_t[i]) * signum(output_nodes[i]->output_values[j]); +// } +// } + +// return loss_sum; +// } + +double RNN::calculate_error_stock_loss(const vector >& return_at_t, const vector >& return_at_t_plus_1){ + + double loss_sum = 0.0; + double sum_v_i = 0.0; + double absolute_sum = 0.0; + + for (int32_t i = 0; i < (int32_t) output_nodes.size(); i++) { + // output_nodes[i]->error_values.resize(expected_outputs[i].size()); + + // tanh convert input to -1 and 1 + + for (int32_t j = 0; j < (int32_t) return_at_t_plus_1[i].size(); j++) { + sum_v_i += fabs(output_nodes[i]->output_values[j]); + // absolute_sum += fabs(tanh(output_nodes[i]->output_values[j])); + } + } + + + for (int32_t i = 0; i < (int32_t) output_nodes.size(); i++) { + // output_nodes[i]->error_values.resize(expected_outputs[i].size()); + + double v_i = 0.0; + double gradient_at_k = 0.0; + double gradient_at_not_k = 0.0; + + for (int32_t j = 0; j < (int32_t) return_at_t_plus_1[i].size(); j++) { + + v_i = 1.0 * fabs(output_nodes[i]->output_values[j]) / sum_v_i; + loss_sum += v_i * (return_at_t_plus_1[i][j] - return_at_t[i][j]) * signum(output_nodes[i]->output_values[j]); + + gradient_at_k += -1.0 * (return_at_t_plus_1[i][j] - return_at_t[i][j]) * + ((sum_v_i - output_nodes[i]->output_values[j]) * + signum(output_nodes[i]->output_values[j])) / pow(sum_v_i, 2); + + output_nodes[i]->error_values[j] += gradient_at_k; + } + + for (int32_t j = 0; j < (int32_t) output_nodes.size(); j++) { + for (int32_t k = 0; j < (int32_t) return_at_t_plus_1[j].size(); k++) { + if (i == j){ + continue; + } + + gradient_at_not_k += -1.0 * (return_at_t_plus_1[i][j] - return_at_t[i][j]) * + signum(output_nodes[i]->output_values[k]) / pow(absolute_sum, 2); + + } + output_nodes[i]->error_values[j] += gradient_at_not_k; + } + + } + + return loss_sum; +} + +string RNN::get_loss() { + return this->loss; +} \ No newline at end of file diff --git a/rnn/rnn.hxx b/rnn/rnn.hxx index 95ac821d..cfaaaa8d 100644 --- a/rnn/rnn.hxx +++ b/rnn/rnn.hxx @@ -24,11 +24,15 @@ class RNN { vector edges; vector recurrent_edges; + vector arguments; + + string loss; + public: RNN(vector& _nodes, vector& _edges, const vector& input_parameter_names, const vector& output_parameter_names); RNN(vector& _nodes, vector& _edges, vector& _recurrent_edges, - const vector& input_parameter_names, const vector& output_parameter_names); + const vector& input_parameter_names, const vector& output_parameter_names, const vector& arguments); ~RNN(); void fix_parameter_orders( @@ -51,6 +55,10 @@ class RNN { double calculate_error_mse(const vector >& expected_outputs); double calculate_error_mae(const vector >& expected_outputs); + // Stock Loss + double calculate_error_stock_loss(const vector >& return_at_t, + const vector >& return_at_t_plus_1); + double prediction_softmax( const vector >& series_data, const vector >& expected_outputs, bool using_dropout, bool training, double dropout_probability @@ -63,6 +71,10 @@ class RNN { const vector >& series_data, const vector >& expected_outputs, bool using_dropout, bool training, double dropout_probability ); + double prediction_stock_loss( + const vector >& series_data, const vector >& expected_outputs, bool using_dropout, + bool training, double dropout_probability + ); vector get_predictions( const vector >& series_data, const vector >& expected_outputs, bool usng_dropout, @@ -82,7 +94,7 @@ class RNN { int32_t get_number_weights(); - void get_analytic_gradient( + void get_analytic_gradient( /// gradients const vector& test_parameters, const vector >& inputs, const vector >& outputs, double& mse, vector& analytic_gradient, bool using_dropout, bool training, double dropout_probability @@ -93,6 +105,8 @@ class RNN { bool training, double dropout_probability ); + string get_loss(); + // RNN* copy(); friend void get_mse( @@ -101,6 +115,9 @@ class RNN { friend void get_mae( RNN* genome, const vector >& expected, double& mae, vector >& deltas ); + // Stock Loss + friend void get_stock_loss(RNN* genome, const vector >& expected, double& loss, vector >& deltas, const vector return_at_t, + const vector return_at_t_plus_1); }; #endif diff --git a/rnn/rnn_genome.cxx b/rnn/rnn_genome.cxx index 80005b27..4a7172fd 100644 --- a/rnn/rnn_genome.cxx +++ b/rnn/rnn_genome.cxx @@ -874,7 +874,7 @@ RNN* RNN_Genome::get_rnn() { // recurrent_edges[i]->copy(node_copies) ); } - return new RNN(node_copies, edge_copies, recurrent_edge_copies, input_parameter_names, output_parameter_names); + return new RNN(node_copies, edge_copies, recurrent_edge_copies, input_parameter_names, output_parameter_names, this->arguments); } vector RNN_Genome::get_best_parameters() const { @@ -933,9 +933,19 @@ void forward_pass_thread_regression( const vector >& outputs, int32_t i, double* mses, bool use_dropout, bool training, double dropout_probability ) { + string loss = rnn->get_loss(); + rnn->set_weights(parameters); rnn->forward_pass(inputs, use_dropout, training, dropout_probability); - mses[i] = rnn->calculate_error_mse(outputs); + + if (loss == "mse") { + mses[i] = rnn->calculate_error_mse(outputs); + } else if (loss == "mae") { + mses[i] = rnn->calculate_error_mae(outputs); + } else if (loss == "stock") { + mses[i] = rnn->calculate_error_stock_loss(inputs, outputs); + } + // mses[i] = rnn->calculate_error_mae(outputs); Log::trace("mse[%d]: %lf\n", i, mses[i]); @@ -3219,9 +3229,11 @@ void read_binary_string(istream& in, string& s, string name) { Log::debug("read %d %s characters '%s'\n", n, name.c_str(), s.c_str()); } -RNN_Genome::RNN_Genome(string binary_filename) { +RNN_Genome::RNN_Genome(string binary_filename, const vector& arguments) { ifstream bin_infile(binary_filename, ios::in | ios::binary); + this->arguments = arguments; + if (!bin_infile.good()) { Log::fatal("ERROR: could not open RNN genome file '%s' for reading.\n", binary_filename.c_str()); exit(1); diff --git a/rnn/rnn_genome.hxx b/rnn/rnn_genome.hxx index 81fad3eb..7bbe060c 100644 --- a/rnn/rnn_genome.hxx +++ b/rnn/rnn_genome.hxx @@ -75,6 +75,8 @@ class RNN_Genome { vector input_parameter_names; vector output_parameter_names; + vector arguments; + string normalize_type; map normalize_mins; map normalize_maxs; @@ -303,7 +305,7 @@ class RNN_Genome { void print_equations(); void write_equations(ostream& outstream); - RNN_Genome(string binary_filename); + RNN_Genome(string binary_filename, const vector& arguments); RNN_Genome(char* array, int32_t length); RNN_Genome(istream& bin_infile); diff --git a/strategic/CMakeLists.txt b/strategic/CMakeLists.txt new file mode 100644 index 00000000..92b89e74 --- /dev/null +++ b/strategic/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(evaluate_strategy evaluate_strategy.cxx forecaster.cxx trivial_forecaster.cxx strategy.cxx simple_stock_strategy.cxx state.cxx portfolio.cxx oracle.cxx portfolio_oracle.cxx) +target_link_libraries(evaluate_strategy examm_strategy exact_common exact_time_series exact_weights examm_nn pthread) diff --git a/strategic/evaluate_strategy.cxx b/strategic/evaluate_strategy.cxx new file mode 100644 index 00000000..2649e92f --- /dev/null +++ b/strategic/evaluate_strategy.cxx @@ -0,0 +1,99 @@ +#include +using std::map; + +#include +using std::string; + +#include +using std::vector; + +#include "common/arguments.hxx" +#include "common/log.hxx" +#include "rnn/rnn_genome.hxx" +#include "time_series/time_series_new.hxx" + +#include "forecaster.hxx" +#include "oracle.hxx" +#include "state.hxx" +#include "strategy.hxx" + + +vector arguments; + +void print_values(string name, const map &values) { + Log::debug("%s:\n", name.c_str()); + for (auto const& [key, value] : values) { + Log::debug("\t%s = %lf\n", key.c_str(), value); + } +} + + + +int main(int argc, char** argv) { + arguments = vector(argv, argv + argc); + + Log::initialize(arguments); + Log::set_id("main"); + + string output_directory; + get_argument(arguments, "--output_directory", true, output_directory); + + int32_t time_offset = 1; + get_argument(arguments, "--time_offset", false, time_offset); + + vector input_parameter_names, output_parameter_names; + get_argument_vector(arguments, "--input_parameter_names", true, input_parameter_names); + get_argument_vector(arguments, "--output_parameter_names", true, output_parameter_names); + + string time_series_filename; + get_argument(arguments, "--time_series_filename", true, time_series_filename); + TimeSeriesNew *time_series = new TimeSeriesNew(time_series_filename, input_parameter_names, output_parameter_names); + + Forecaster* forecaster = Forecaster::initialize_from_arguments(arguments, time_series->get_input_parameter_names(), time_series->get_output_parameter_names()); + Strategy* strategy = Strategy::initialize_from_arguments(arguments); + Oracle* oracle = Oracle::initialize_from_arguments(arguments); + + if (argument_exists(arguments, "--normalize")) { + //Normalizer *normalizer = Normalizer::initialize_from_arguments(arguments); + //normalizer->normalize(time_series_new); + } + + double current_reward = 0.0; + double reward = 0.0; + + map inputs; + map next_inputs; + + for (int32_t i = 0; i < time_series->get_number_rows() - time_offset; i++) { + time_series->get_inputs_at(i, inputs); + time_series->get_inputs_at(i + time_offset, next_inputs); + + if (Log::at_level(Log::DEBUG)) print_values("inputs", inputs); + + Log::info("getting forecast!\n"); + + map forecast = forecaster->forecast(inputs); + if (Log::at_level(Log::DEBUG)) print_values("forecast", forecast); + + strategy->make_move(inputs, forecast); + + State *state = strategy->get_state(); + current_reward = oracle->calculate_reward(state, next_inputs); + Log::info("reward for move is: %lf\n", current_reward); + + //strategy->report_reward(current_reward); + + //reward += current_reward; + + delete state; + } + + delete time_series; + delete forecaster; + delete strategy; + delete oracle; + + + Log::release_id("main"); + return 0; +} diff --git a/strategic/forecaster.cxx b/strategic/forecaster.cxx new file mode 100644 index 00000000..32a99fb9 --- /dev/null +++ b/strategic/forecaster.cxx @@ -0,0 +1,31 @@ +#include "common/arguments.hxx" +#include "common/log.hxx" + +#include "forecaster.hxx" +#include "trivial_forecaster.hxx" + +Forecaster::Forecaster(const vector &_input_parameter_names, const vector &_output_parameter_names) : input_parameter_names(_input_parameter_names), output_parameter_names(_output_parameter_names) { +} + +vector Forecaster::get_output_parameter_names() const { + return output_parameter_names; +} + +vector Forecaster::get_input_parameter_names() const { + return input_parameter_names; +} + +Forecaster* Forecaster::initialize_from_arguments(const vector &arguments, const vector &input_parameter_names, const vector &output_parameter_names) { + + string forecaster_type; + get_argument(arguments, "--forecaster_type", true, forecaster_type); + + if (forecaster_type == "trivial") { + return new TrivialForecaster(arguments, input_parameter_names, output_parameter_names); + + } else { + Log::fatal("unknown forecaster type '%s', cannot evaluate strategy.\n\n", forecaster_type.c_str()); + exit(1); + } + +} diff --git a/strategic/forecaster.hxx b/strategic/forecaster.hxx new file mode 100644 index 00000000..f37015f3 --- /dev/null +++ b/strategic/forecaster.hxx @@ -0,0 +1,71 @@ +#ifndef FORECASTER_HXX +#define FORECASTER_HXX + +#include +using std::map; + +#include +using std::string; + +#include +using std::vector; + + + +class Forecaster { + protected: + vector input_parameter_names; + vector output_parameter_names; + + public: + /** + * Initializes the abstract class of forecaster by setting its input and output parameter names + * \param input_parameter_names are the input columns to use by the forecaster + * \param output_parameter names are the columns forecasted by the forecaster + */ + Forecaster(const vector &_input_parameter_names, const vector &_output_parameter_names); + + /** + * Provide a virtual destructor for this virtual class + */ + virtual ~Forecaster() = default; + + /** + * \return the input parameter names for the forecaster + */ + vector get_input_parameter_names() const; + + /** + * \return the output parameter names for the forecaster + */ + vector get_output_parameter_names() const; + + /** + * Creates one of any of the potential sublcasses of the Forecaster class given + * command line arguments. + * + * \param arguments is the vector of command line arguments + * \param input_parameter_names are the input parameter names to be used (the ones in the arguments + * may be regular expressions so we use the ones from the time series object). + * \param output_parameter_names are the output parameter names to be used (the ones in the arguments + * may be regular expressions so we use the ones from the time series object). + * + * \return a pointer to a Forecaster object + */ + static Forecaster* initialize_from_arguments(const vector &arguments, const vector &input_parameter_names, const vector &output_parameter_names); + + /** + * Given the current context of the system, provide a forecast of what the next + * context is expected to be. + * + * \param context is a representation of the current context of the problem being worked + * on, e.g., the current time series values (used to predict the next or other future + * time series values). + * + * \return the forecasted vector of values + */ + virtual map forecast(const map &context) = 0; + +}; + +#endif diff --git a/strategic/oracle.cxx b/strategic/oracle.cxx new file mode 100644 index 00000000..200d87cb --- /dev/null +++ b/strategic/oracle.cxx @@ -0,0 +1,20 @@ +#include "common/arguments.hxx" +#include "common/log.hxx" + +#include "oracle.hxx" +#include "portfolio_oracle.hxx" + +Oracle* Oracle::initialize_from_arguments(const vector &arguments) { + + string oracle_type; + get_argument(arguments, "--oracle_type", true, oracle_type); + + if (oracle_type == "portfolio") { + return new PortfolioOracle(arguments); + + } else { + Log::fatal("unknown oracle type '%s', cannot evaluate oracle.\n\n", oracle_type.c_str()); + exit(1); + } + +} diff --git a/strategic/oracle.hxx b/strategic/oracle.hxx new file mode 100644 index 00000000..3cdc3186 --- /dev/null +++ b/strategic/oracle.hxx @@ -0,0 +1,48 @@ +#ifndef STRATEGY_ORACLE_HXX +#define STRATEGY_ORACLE_HXX + +#include +using std::map; + +#include +using std::string; + +#include +using std::vector; + +#include "state.hxx" + +class Oracle { + public: + /** + * Creates one of any of the potential sublcasses of the Oracle class given + * command line arguments. + * + * \param arguments is the vector of command line arguments + * + * \return a pointer to an Oracle object + */ + static Oracle* initialize_from_arguments(const vector &arguments); + + + /** + * A default destructor for this virtual class. + */ + virtual ~Oracle() = default; + + /** + * Calculates the reward given the state of a strategy and the current state (context) of + * the system. + * + * \param state is some subclass of the State class, representing the state of the strategy + * needed to calculate a reward. + * \param context is the context of the system for the next time step after it made its + * last move. + * + * \return a reward value representing how good the strategy's current state is compared + * to its previous state. + */ + virtual double calculate_reward(State *state, const map &context) = 0; +}; + +#endif diff --git a/strategic/portfolio.cxx b/strategic/portfolio.cxx new file mode 100644 index 00000000..2917d93a --- /dev/null +++ b/strategic/portfolio.cxx @@ -0,0 +1,25 @@ +#include "common/log.hxx" + +#include "portfolio.hxx" + + +Portfolio::Portfolio(double _current_money_pool, string _price_suffix) : current_money_pool(_current_money_pool), price_suffix(_price_suffix) { +} + +void Portfolio::add_stock(string stock, double purchased_shares) { + shares[stock] = purchased_shares; +} + +double Portfolio::calculate_value(const map &context) { + double value = current_money_pool; + + for (auto const& [stock, purchased_shares] : shares) { + Log::info("getting price for %s with %lf purchased shares.\n", stock.c_str(), purchased_shares); + + double price = context.at(stock + price_suffix); + + value += price * purchased_shares; + } + + return value; +} diff --git a/strategic/portfolio.hxx b/strategic/portfolio.hxx new file mode 100644 index 00000000..045ae075 --- /dev/null +++ b/strategic/portfolio.hxx @@ -0,0 +1,53 @@ +#ifndef STOCK_PORTFOLIO_HXX +#define STOCK_PORTFOLIO_HXX + +#include +using std::map; + +#include +using std::string; + +#include "state.hxx" + + +class Portfolio : public State { + private: + //The strategy's current money pool + double current_money_pool; + + //The suffix for price column names determined by the stock strategy. + string price_suffix; + + //A map of stock names to how many shares are owned. + map shares; + + public: + /** + * Initialize the portfolio with how much money is available. + * + * \param _current_money_pool is how much unspent money the portfolio has. + * \param price_suffix is the suffix for column names for stock prices. + */ + Portfolio(double _current_money_pool, string price_suffix); + + /** + * Used to specify how much of each stock has been purchased (if any). + * + * \param stock is the stock ticker name to add + * \param purchased shares is how many shares are owned of the stock + */ + void add_stock(string stock, double purchased_shares); + + /** + * Calculate how much money we have available in our money pool plus the + * values of all our stocks. + * + * \param context is the current state which has all the current stock ticker + * prices. + * + * \return the total worth of our stocks and current money pool + */ + double calculate_value(const map &context); +}; + +#endif diff --git a/strategic/portfolio_oracle.cxx b/strategic/portfolio_oracle.cxx new file mode 100644 index 00000000..2add2042 --- /dev/null +++ b/strategic/portfolio_oracle.cxx @@ -0,0 +1,35 @@ +#include "common/log.hxx" +#include "common/arguments.hxx" + +#include "portfolio.hxx" +#include "portfolio_oracle.hxx" +#include "state.hxx" + +PortfolioOracle::PortfolioOracle(const vector &arguments) { + //use the money pool command line argument to determine how much + //money was started with + initial_money_pool = 100.0; + get_argument(arguments, "--money_pool", false, initial_money_pool); + + //use the initial money pool to calculate the first previous money + //pool + previous_total_money = initial_money_pool; + +} + + +double PortfolioOracle::calculate_reward(State *state, const map &context) { + Portfolio* portfolio = dynamic_cast(state); + if (portfolio == NULL) { + Log::fatal("ERROR: could not cast the state object to a Portfolio. An incompatible strategy was used.\n"); + exit(1); + } + + double current_total_money = portfolio->calculate_value(context); + + double reward = current_total_money - previous_total_money; + + previous_total_money = current_total_money; + + return reward; +} diff --git a/strategic/portfolio_oracle.hxx b/strategic/portfolio_oracle.hxx new file mode 100644 index 00000000..3ffa5cc2 --- /dev/null +++ b/strategic/portfolio_oracle.hxx @@ -0,0 +1,48 @@ +#ifndef STRATEGY_PORTFOLIO_ORACLE_HXX +#define STRATEGY_PORTFOLIO_ORACLE_HXX + +#include +using std::map; + +#include +using std::string; + +#include +using std::vector; + +#include "oracle.hxx" +#include "state.hxx" + + +class PortfolioOracle : public Oracle { + private: + double initial_money_pool; + double previous_total_money; + + vector stocks; + + public: + /** + * Initialize a PortfolioOracle from user provided arguments. + * + * \param arguments are the command line arguments. + */ + PortfolioOracle(const vector &arguments); + + + /** + * Calculates the reward given the state of a strategy and the current state (context) of + * the system. + * + * \param state is some subclass of the State class, representing the state of the strategy + * needed to calculate a reward. + * \param context is the context of the system for the next time step after it made its + * last move. + * + * \return a reward value representing how good the strategy's current state is compared + * to its previous state. + */ + double calculate_reward(State *state, const map &context); +}; + +#endif diff --git a/strategic/simple_stock_strategy.cxx b/strategic/simple_stock_strategy.cxx new file mode 100644 index 00000000..04b3b3f1 --- /dev/null +++ b/strategic/simple_stock_strategy.cxx @@ -0,0 +1,126 @@ +#include "common/log.hxx" +#include "common/arguments.hxx" + +#include "simple_stock_strategy.hxx" +#include "portfolio.hxx" + + +SimpleStockStrategy::SimpleStockStrategy(const vector &arguments) { + buy_threshold = 0; + get_argument(arguments, "--buy_threshold", false, buy_threshold); + + sell_threshold = 0; + get_argument(arguments, "--sell_threshold", false, sell_threshold); + + money_pool = 100.0; + get_argument(arguments, "--money_pool", false, money_pool); + + price_suffix = "_price"; + get_argument(arguments, "--price_suffix", false, price_suffix); + + return_suffix = "_predicted_RET"; + get_argument(arguments, "--return_suffix", false, return_suffix); + + get_argument_vector(arguments, "--stocks", true, stocks); + + for (string stock : stocks) { + purchased_shares[stock] = 0; + bought_price[stock] = 0; + } +} + + +void SimpleStockStrategy::make_move(const map &context, const map &forecast) { + double current_money = money_pool; + for (string stock : stocks) { + Log::info("adding money for stock '%s'\n", stock.c_str()); + + current_money += purchased_shares[stock] * context.at(stock + price_suffix); + } + Log::info("current money: %lf\n", current_money); + + vector stocks_to_buy; + vector stocks_to_sell; + + //ensure that _PRC is in the context and _RET is in the forecast + bool missing_stock = false; + for (string stock : stocks) { + if (!context.contains(stock + price_suffix)) { + Log::fatal("ERROR, user specified stock '%s' was being traded but '%s_PRC' was not found in context parameters.\n", stock.c_str(), stock.c_str()); + missing_stock = true; + } + + if (!forecast.contains(stock + return_suffix)) { + Log::fatal("ERROR, user specified stock '%s' was being traded but '%s_RET' was not found in forecast parameters.\n", stock.c_str(), stock.c_str()); + missing_stock = true; + } + } + if (missing_stock) exit(1); + + //determine which stocks we will sell, and which stocks we will buy + for (string stock : stocks) { + double forecasted_return = forecast.at(stock + return_suffix); + + Log::info("forecasted return: %lf, buy_threshold: %lf, sell_threshold: %lf\n", forecasted_return, buy_threshold, sell_threshold); + + if (money_pool > 0 && forecasted_return > buy_threshold) { + //buy stocks if we have money available and the forecasted return is greater than our + //buy threshold. + stocks_to_buy.push_back(stock); + Log::info("\tbuying %s because money_pool (%lf) > 0 and forecasted_return (%lf) > buy_threshold (%lf)\n", stock.c_str(), money_pool, forecasted_return, buy_threshold); + + } else if (purchased_shares[stock] > 0 && forecasted_return < sell_threshold && context.at(stock + price_suffix) > bought_price[stock]) { + //sell stock if we have shares, the forecasted return is less then our sell threshold + //and the sell price is greater than our buy price. + stocks_to_sell.push_back(stock); + Log::info("\tselling %s\n", stock.c_str()); + } else { + Log::info("\tholding %s because purchased shares (%lf) == 0 or forecasted_return (%lf) >= sell_threshold (%lf) or price (%lf) <= bought price (%lf)\n", stock.c_str(), purchased_shares[stock], forecasted_return, sell_threshold, context.at(stock + price_suffix), bought_price[stock]); + } + } + + Log::info("selling %d stocks.\n", stocks_to_sell.size()); + //first sell of stocks to sell + for (string stock : stocks_to_sell) { + if (purchased_shares[stock] > 0) { + double stock_price = context.at(stock + price_suffix); + double gain = purchased_shares[stock] * stock_price; + money_pool += gain; + + Log::info("\tsold %lf shares of %s for %lf$\n", purchased_shares[stock], stock.c_str(), gain); + + purchased_shares[stock] = 0; + bought_price[stock] = 0; + } + } + + Log::info("buying %d stocks.\n", stocks_to_buy.size()); + //if we have any money, use the money pool to buy stocks giving each an + //equal amount of money + if (money_pool > 0 && stocks_to_buy.size() > 0) { + double money_per_stock = money_pool / stocks_to_buy.size(); + + for (string stock : stocks_to_buy) { + double stock_price = context.at(stock + price_suffix); + double shares = money_per_stock / stock_price; + purchased_shares[stock] = shares; + bought_price[stock] = stock_price; + + Log::info("\tbought %lf shares of %s for %lf\n", shares, stock.c_str(), money_per_stock); + } + + //we've spent all our money + money_pool = 0; + } +} + + +State* SimpleStockStrategy::get_state() { + Portfolio *portfolio = new Portfolio(money_pool, price_suffix); + + for (string stock : stocks) { + portfolio->add_stock(stock, purchased_shares[stock]); + } + + return portfolio; +} diff --git a/strategic/simple_stock_strategy.hxx b/strategic/simple_stock_strategy.hxx new file mode 100644 index 00000000..7f792f56 --- /dev/null +++ b/strategic/simple_stock_strategy.hxx @@ -0,0 +1,81 @@ +#ifndef SIMPLE_STOCK_STRATEGY_HXX +#define SIMPLE_STOCK_STRATEGY_HXX + +#include +using std::map; + +#include +using std::string; + +#include +using std::vector; + +#include "strategy.hxx" + +class SimpleStockStrategy : public Strategy { + private: + //buy the stock if money is available and the predicted return + //is above this threshold. + double buy_threshold; + + //sell the stock of the predicted return is less than this threshold + //but above the buying price. + double sell_threshold; + + //how much money we have to buy stocks with initially. + double money_pool; + + //the suffix for the column name of a stock price, e.g. _price for AAPL_price + string price_suffix; + + //the suffix for the column name of a stock return, e.g. _return for AAPL_predicted_RET + //or AAPL_expected_RET + string return_suffix; + + //This is used to determine which parameters need to be pulled from the + //context and forecast. For example, if a stock name is AAPL then the + //strategy will look for APPL_PRC (for price) from the context and + //APPL_RET (for returns) in the forecast. + vector stocks; + + //for each stock, track what price it was bought at the last + //time it was bought. + map bought_price; + + //fore each stock, track how many shares were purchased. + map purchased_shares; + + public: + /** + * Initialize a SimpleStockStrategy from user provided arguments. + * + * \param arguments are the command line arguments. + */ + SimpleStockStrategy(const vector &arguments); + + /** + * This is the simplest strategy for buying and selling stocks. In particular, a stock + * will be purchased if the predicted return is > threshold or sold if the predicted return is + * < threshold and the selling price is > the buying price. The default value for the threshold + * is 0. + * + * \param context is a representation of the current context of the problem being worked + * on, e.g., the current time series values (used to predict the next or other future + * time series values). + * + * \param forecast is a forecast of the next context of the problem being worked on (e.g., + * the time series values at the next time step). + */ + void make_move(const map &context, const map &forecast); + + /** + * Returns the state of the strategy, how much moeny is in the money pool and + * how many of each stock have been purchased. + * + * \return a Portfolio object representing the amount of stock and money available. + */ + State* get_state(); +}; + + +#endif diff --git a/strategic/state.cxx b/strategic/state.cxx new file mode 100644 index 00000000..d1b01861 --- /dev/null +++ b/strategic/state.cxx @@ -0,0 +1,2 @@ +#include "state.hxx" + diff --git a/strategic/state.hxx b/strategic/state.hxx new file mode 100644 index 00000000..d721f15a --- /dev/null +++ b/strategic/state.hxx @@ -0,0 +1,18 @@ +#ifndef STRATEGY_STATE_HXX +#define STRATEGY_STATE_HXX + +class State { + /** + * State is an empty class because depending on the task the state of the + * strategy can be completely different. + */ + + public: + /** + * A default destructor for this abstract class. + */ + virtual ~State() = default; +}; + + +#endif diff --git a/strategic/strategy.cxx b/strategic/strategy.cxx new file mode 100644 index 00000000..ac9dde3e --- /dev/null +++ b/strategic/strategy.cxx @@ -0,0 +1,20 @@ +#include "common/arguments.hxx" +#include "common/log.hxx" + +#include "strategy.hxx" +#include "simple_stock_strategy.hxx" + +Strategy* Strategy::initialize_from_arguments(const vector &arguments) { + + string strategy_type; + get_argument(arguments, "--strategy_type", true, strategy_type); + + if (strategy_type == "simple_stock") { + return new SimpleStockStrategy(arguments); + + } else { + Log::fatal("unknown strategy type '%s', cannot evaluate strategy.\n\n", strategy_type.c_str()); + exit(1); + } + +} diff --git a/strategic/strategy.hxx b/strategic/strategy.hxx new file mode 100644 index 00000000..4e1b8149 --- /dev/null +++ b/strategic/strategy.hxx @@ -0,0 +1,53 @@ +#ifndef STRATEGY_HXX +#define STRATEGY_HXX + +#include +using std::map; + +#include +using std::string; + +#include +using std::vector; + +#include "state.hxx" + +class Strategy { + public: + /** + * Creates one of any of the potential sublcasses of the Strategy class given + * command line arguments. + * + * \param arguments is the vector of command line arguments + * + * \return a pointer to a Strategy object + */ + static Strategy* initialize_from_arguments(const vector &arguments); + + /** + * A default destructor for this abstract class. + */ + virtual ~Strategy() = default; + + /** + * Take an action or actions using this strategy given the current context and a forecast + * of the next context. + * + * \param context is a representation of the current context of the problem being worked + * on, e.g., the current time series values (used to predict the next or other future + * time series values). + * + * \param forecast is a forecast of the next context of the problem being worked on (e.g., + * the time series values at the next time step). + */ + virtual void make_move(const map &context, const map &forecast) = 0; + + /** + * Returns the current state of the strategy so an Oracle object can calculate + * a reward given the current state and the current context of the system. + */ + virtual State* get_state() = 0; +}; + + +#endif diff --git a/strategic/trivial_forecaster.cxx b/strategic/trivial_forecaster.cxx new file mode 100644 index 00000000..cc240469 --- /dev/null +++ b/strategic/trivial_forecaster.cxx @@ -0,0 +1,63 @@ +#include +using std::find; + +#include "trivial_forecaster.hxx" + +#include "common/log.hxx" +#include "common/arguments.hxx" + +TrivialForecaster::TrivialForecaster(const vector &arguments, const vector &_input_parameter_names, const vector &_output_parameter_names) : Forecaster(_input_parameter_names, _output_parameter_names) { + forecaster_lag = 0; + get_argument(arguments, "--forecaster_lag", false, forecaster_lag); + + if (forecaster_lag < 0) { + Log::fatal("ERROR: forecaster_lag must be a value >= 0, value was %d\n", forecaster_lag); + exit(1); + } + + //check to make sure all the output parameter names are available in the input parameter names + for (string output_parameter_name : output_parameter_names) { + if (find(input_parameter_names.begin(), input_parameter_names.end(), output_parameter_name) == input_parameter_names.end()) { + Log::fatal("ERROR: could not find output parameter name '%s' in the input parameter names.\n", output_parameter_name.c_str()); + Log::fatal("The trivial forecaster requries all output parameter names to be in the input parameter names:\n"); + for (string input_parameter_name : input_parameter_names) { + Log::fatal("\t%s\n", input_parameter_name.c_str()); + } + exit(1); + } + } +} + + +map TrivialForecaster::forecast(const map &context) { + Log::info("forecasting!\n"); + + if (forecaster_lag == 0) { + // predictions for the next value(s) are just the current values + map result; + + //only return the output parameter names + for (string output_parameter_name : output_parameter_names) { + result[output_parameter_name] = context.at(output_parameter_name); + } + + return result; + } else { + history.push_back(context); + + map lagged_context = history[0]; + + //only return the output parameter names + map result; + for (string output_parameter_name : output_parameter_names) { + result[output_parameter_name] = lagged_context.at(output_parameter_name); + } + + //return the Nth previous context where N is the forecaster_lag + if (history.size() > forecaster_lag) { + history.erase(history.begin()); + } + + return result; + } +} diff --git a/strategic/trivial_forecaster.hxx b/strategic/trivial_forecaster.hxx new file mode 100644 index 00000000..589c9919 --- /dev/null +++ b/strategic/trivial_forecaster.hxx @@ -0,0 +1,46 @@ +#ifndef TRIVIAL_FORECASTER_HXX +#define TRIVIAL_FORECASTER_HXX + +#include +using std::map; + +#include +using std::string; + +#include +using std::vector; + +#include "forecaster.hxx" + +class TrivialForecaster : public Forecaster { + private: + int32_t forecaster_lag; + + vector > history; + + public: + /** + * Initialize a TrivialForecaster from user provided arguments. + * + * \param arguments are the command line arguments. + */ + TrivialForecaster(const vector &arguments, const vector &_input_parameter_names, const vector &_output_parameter_names); + + /** + * Will return a forecast, which will be the context N contexts ago, + * where N is the forecaster lag. If forecaster lag is 0, this will + * return the given context. If forecaster lag is 1, it will return the + * previous, if forecaster lag is 2, it will return the 2nd previous + * context, etc. + * + * \param the context (the current values of the system for the model to + * provide a forecast from). + * + * \return a forecast given the context. + */ + map forecast(const map &context); + +}; + + +#endif From 3c1b4d6b8f9d73a1ba3004cf7ece77f1892fca51 Mon Sep 17 00:00:00 2001 From: Devroop Kar Date: Wed, 18 Jun 2025 21:25:10 -0400 Subject: [PATCH 2/2] Added stock_loss file and loss checks --- rnn/CMakeLists.txt | 2 +- rnn/mse.cxx | 72 ---------------------------------------- rnn/mse.hxx | 5 --- rnn/rnn.cxx | 64 +++++++++++++++++++++++++++++++----- rnn/rnn_genome.cxx | 3 ++ rnn/stock_loss.cxx | 82 ++++++++++++++++++++++++++++++++++++++++++++++ rnn/stock_loss.hxx | 15 +++++++++ 7 files changed, 156 insertions(+), 87 deletions(-) create mode 100644 rnn/stock_loss.cxx create mode 100644 rnn/stock_loss.hxx diff --git a/rnn/CMakeLists.txt b/rnn/CMakeLists.txt index 2de0d537..46cd6a04 100644 --- a/rnn/CMakeLists.txt +++ b/rnn/CMakeLists.txt @@ -1,2 +1,2 @@ -add_library(examm_nn generate_nn.cxx rnn_genome.cxx rnn.cxx lstm_node.cxx ugrnn_node.cxx delta_node.cxx gru_node.cxx enarc_node.cxx enas_dag_node.cxx random_dag_node.cxx mgu_node.cxx dnas_node.cxx mse.cxx rnn_node.cxx rnn_edge.cxx rnn_recurrent_edge.cxx rnn_node_interface.cxx genome_property.cxx sin_node.cxx sum_node.cxx cos_node.cxx tanh_node.cxx sigmoid_node.cxx inverse_node.cxx multiply_node.cxx sin_node_gp.cxx cos_node_gp.cxx tanh_node_gp.cxx sigmoid_node_gp.cxx inverse_node_gp.cxx multiply_node_gp.cxx sum_node_gp.cxx) +add_library(examm_nn generate_nn.cxx rnn_genome.cxx rnn.cxx lstm_node.cxx ugrnn_node.cxx delta_node.cxx gru_node.cxx enarc_node.cxx enas_dag_node.cxx random_dag_node.cxx mgu_node.cxx dnas_node.cxx mse.cxx rnn_node.cxx rnn_edge.cxx rnn_recurrent_edge.cxx rnn_node_interface.cxx genome_property.cxx sin_node.cxx sum_node.cxx cos_node.cxx tanh_node.cxx sigmoid_node.cxx inverse_node.cxx multiply_node.cxx sin_node_gp.cxx cos_node_gp.cxx tanh_node_gp.cxx sigmoid_node_gp.cxx inverse_node_gp.cxx multiply_node_gp.cxx sum_node_gp.cxx stock_loss.cxx) target_link_libraries(examm_nn exact_time_series exact_weights exact_common) diff --git a/rnn/mse.cxx b/rnn/mse.cxx index d6c7d9e1..6fca83e1 100644 --- a/rnn/mse.cxx +++ b/rnn/mse.cxx @@ -109,76 +109,4 @@ void get_mae(RNN* genome, const vector >& expected, double& mae_s deltas[i][j] *= d_mae; } } -} - -template -double signum(T x) { - if (x > 0) { - return 1.0; - } else if (x < 0) { - return -1.0; - } else { - return 0.0; - } -} - -void get_stock_loss (const vector& output_values, const vector& expected, double& loss_sum, vector& deltas, - const vector return_at_t, - const vector return_at_t_plus_1) -{ - loss_sum = 0.0; - double sum_v_i = 0.0; - - for (int32_t i = 0; i < (int32_t) expected.size(); i++) { - sum_v_i += fabs(output_values[i]); - } - - for (int32_t i = 0; i < (int32_t) expected.size(); i++) { - - double v_i = 1.0 * fabs(output_values[i]) / sum_v_i; - loss_sum += v_i * (return_at_t_plus_1[i] - return_at_t[i]) * signum(output_values[i]); - - } - - double d_loss = loss_sum; - for (int32_t i = 0; i < (int32_t) expected.size(); i++) { - deltas[i] *= d_loss; - } -} - -void get_stock_loss (RNN* genome, const vector >& expected, double& loss_sum, vector >& deltas, - const vector return_at_t, - const vector return_at_t_plus_1) -{ - loss_sum = 0.0; - double sum_v_i = 0.0; - double loss; - - for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { - for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { - sum_v_i += fabs(genome->output_nodes[i]->output_values[j]); - } - } - - for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { - - loss = 0.0; - double v_i = 0.0; - - for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { - sum_v_i += fabs(genome->output_nodes[i]->output_values[j]); - } - - for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { - v_i = 1.0 * fabs(genome->output_nodes[i]->output_values[j]) / sum_v_i; - loss_sum += v_i * (return_at_t_plus_1[i] - return_at_t[i]) * signum(genome->output_nodes[i]->output_values[j]); - } - } - - double d_loss = loss_sum * (1.0 / expected[0].size()); - for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { - for (int32_t j = 0; j < (int32_t) expected[i].size(); j++) { - deltas[i][j] *= d_loss; - } - } } \ No newline at end of file diff --git a/rnn/mse.hxx b/rnn/mse.hxx index 7ac9502a..ed246974 100644 --- a/rnn/mse.hxx +++ b/rnn/mse.hxx @@ -12,9 +12,4 @@ void get_mse(RNN* genome, const vector >& expected, double& mse, void get_mae(const vector& output_values, const vector& expected, double& mae, vector& deltas); void get_mae(RNN* genome, const vector >& expected, double& mae, vector >& deltas); -void get_stock_loss(const vector& output_values, const vector& expected, double& loss, vector& deltas, const vector return_at_t, - const vector return_at_t_plus_1); -void get_stock_loss(RNN* genome, const vector >& expected, double& loss, vector >& deltas, const vector return_at_t, - const vector return_at_t_plus_1); - #endif diff --git a/rnn/rnn.cxx b/rnn/rnn.cxx index 49892311..ae3dcf15 100644 --- a/rnn/rnn.cxx +++ b/rnn/rnn.cxx @@ -41,6 +41,7 @@ using std::vector; #include "random_dag_node.hxx" #include "time_series/time_series.hxx" // #include "word_series/word_series.hxx" +#include "stock_loss.hxx" void RNN::validate_parameters( const vector& input_parameter_names, const vector& output_parameter_names @@ -682,9 +683,12 @@ void RNN::get_analytic_gradient( } else if (this->loss == "mae") { mse = calculate_error_mae(outputs); backward_pass(mse * (1.0 / outputs[0].size()) * 2.0, using_dropout, training, dropout_probability); - } else { + } else if (this->loss == "stock") { mse = calculate_error_stock_loss(inputs, outputs); backward_pass(mse, using_dropout, training, dropout_probability); + } else { + Log::fatal("ERROR: incorrect loss function provided\n"); + exit(1); } vector current_gradients; @@ -718,7 +722,7 @@ void RNN::get_analytic_gradient( void RNN::get_empirical_gradient( const vector& test_parameters, const vector >& inputs, - const vector >& outputs, double& mse, vector& empirical_gradient, bool using_dropout, + const vector >& outputs, double& loss, vector& empirical_gradient, bool using_dropout, bool training, double dropout_probability ) { empirical_gradient.assign(test_parameters.size(), 0.0); @@ -727,11 +731,25 @@ void RNN::get_empirical_gradient( set_weights(test_parameters); forward_pass(inputs, using_dropout, training, dropout_probability); - double original_mse = calculate_error_mse(outputs); + double original_loss = 0.0; + + if (this->loss == "mse"){ + original_loss = calculate_error_mse(outputs); + + } else if (this->loss == "mae") { + original_loss = calculate_error_mae(outputs); + + } else if (this->loss == "stock") { + original_loss = calculate_error_stock_loss(inputs, outputs); + + } else { + Log::fatal("ERROR: incorrect loss function provided\n"); + exit(1); + } double save; double diff = 0.00001; - double mse1, mse2; + double loss1, loss2; vector parameters = test_parameters; for (int32_t i = 0; i < (int32_t) parameters.size(); i++) { @@ -740,20 +758,48 @@ void RNN::get_empirical_gradient( parameters[i] = save - diff; set_weights(parameters); forward_pass(inputs, using_dropout, training, dropout_probability); - get_mse(this, outputs, mse1, deltas); + // get_mse(this, outputs, loss1, deltas); + + if (this->loss == "mse"){ + get_mse(this, outputs, loss1, deltas); + + } else if (this->loss == "mae") { + get_mae(this, outputs, loss1, deltas); + + } else if (this->loss == "stock") { + get_stock_loss(this, outputs, loss1, deltas, inputs, outputs); + + } else { + Log::fatal("ERROR: incorrect loss function provided\n"); + exit(1); + } parameters[i] = save + diff; set_weights(parameters); forward_pass(inputs, using_dropout, training, dropout_probability); - get_mse(this, outputs, mse2, deltas); + // get_mse(this, outputs, loss2, deltas); + + if (this->loss == "mse"){ + get_mse(this, outputs, loss2, deltas); + + } else if (this->loss == "mae") { + get_mae(this, outputs, loss2, deltas); + + } else if (this->loss == "stock") { + get_stock_loss(this, outputs, loss2, deltas, inputs, outputs); + + } else { + Log::fatal("ERROR: incorrect loss function provided\n"); + exit(1); + } - empirical_gradient[i] = (mse2 - mse1) / (2.0 * diff); - empirical_gradient[i] *= original_mse; + empirical_gradient[i] = (loss2 - loss1) / (2.0 * diff); + empirical_gradient[i] *= original_loss; parameters[i] = save; } - mse = original_mse; + loss = original_loss; } void RNN::initialize_randomly() { diff --git a/rnn/rnn_genome.cxx b/rnn/rnn_genome.cxx index 4a7172fd..b0485aba 100644 --- a/rnn/rnn_genome.cxx +++ b/rnn/rnn_genome.cxx @@ -944,6 +944,9 @@ void forward_pass_thread_regression( mses[i] = rnn->calculate_error_mae(outputs); } else if (loss == "stock") { mses[i] = rnn->calculate_error_stock_loss(inputs, outputs); + } else{ + Log::fatal("ERROR: incorrect loss function provided\n"); + exit(1); } // mses[i] = rnn->calculate_error_mae(outputs); diff --git a/rnn/stock_loss.cxx b/rnn/stock_loss.cxx new file mode 100644 index 00000000..49af98e5 --- /dev/null +++ b/rnn/stock_loss.cxx @@ -0,0 +1,82 @@ +#include +#include +using std::vector; + +#include "common/log.hxx" +#include "rnn.hxx" + +template +double signum(T x) { + if (x > 0) { + return 1.0; + } else if (x < 0) { + return -1.0; + } else { + return 0.0; + } +} + +void get_stock_loss (const vector& output_values, const vector& expected, double& loss_sum, vector& deltas, + const vector return_at_t, + const vector return_at_t_plus_1) +{ + deltas.assign(expected.size(), 0.0); + + loss_sum = 0.0; + double sum_v_i = 0.0; + + for (int32_t i = 0; i < (int32_t) expected.size(); i++) { + sum_v_i += fabs(output_values[i]); + } + + for (int32_t i = 0; i < (int32_t) expected.size(); i++) { + + double v_i = 1.0 * fabs(output_values[i]) / sum_v_i; + loss_sum += v_i * (return_at_t_plus_1[i] - return_at_t[i]) * signum(output_values[i]); + + } + + double d_loss = loss_sum; + for (int32_t i = 0; i < (int32_t) expected.size(); i++) { + deltas[i] *= d_loss; + } +} + +void get_stock_loss (RNN* genome, const vector >& expected, double& loss_sum, vector >& deltas, + const vector> return_at_t, + const vector> return_at_t_plus_1) +{ + deltas.assign(genome->output_nodes.size(), vector(expected[0].size(), 0.0)); + + loss_sum = 0.0; + double sum_v_i = 0.0; + double loss; + + for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { + for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { + sum_v_i += fabs(genome->output_nodes[i]->output_values[j]); + } + } + + for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { + + loss = 0.0; + double v_i = 0.0; + + for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { + sum_v_i += fabs(genome->output_nodes[i]->output_values[j]); + } + + for (int32_t j = 0; j < (int32_t) genome->output_nodes[i]->output_values.size(); j++) { + v_i = 1.0 * fabs(genome->output_nodes[i]->output_values[j]) / sum_v_i; + loss_sum += v_i * (return_at_t_plus_1[i][j] - return_at_t[i][j]) * signum(genome->output_nodes[i]->output_values[j]); + } + } + + double d_loss = loss_sum * (1.0 / expected[0].size()); + for (int32_t i = 0; i < (int32_t) genome->output_nodes.size(); i++) { + for (int32_t j = 0; j < (int32_t) expected[i].size(); j++) { + deltas[i][j] *= d_loss; + } + } +} \ No newline at end of file diff --git a/rnn/stock_loss.hxx b/rnn/stock_loss.hxx new file mode 100644 index 00000000..78107393 --- /dev/null +++ b/rnn/stock_loss.hxx @@ -0,0 +1,15 @@ +#ifndef EXAMM_STOCK_LOSSES_HXX +#define EXAMM_STOCK_LOSSES_HXX + +#include +using std::vector; + +#include "rnn.hxx" + + +void get_stock_loss(const vector& output_values, const vector& expected, double& loss, vector& deltas, const vector return_at_t, + const vector return_at_t_plus_1); +void get_stock_loss(RNN* genome, const vector >& expected, double& loss, vector >& deltas, const vector> return_at_t, + const vector> return_at_t_plus_1); + +#endif \ No newline at end of file