From 78a0740821aea9379293d5004fe97f469168f4da Mon Sep 17 00:00:00 2001 From: Travis Desell Date: Fri, 1 Mar 2024 21:24:50 -0500 Subject: [PATCH 1/3] Adding initial implementations for RL/predictions --- CMakeLists.txt | 2 + strategic/CMakeLists.txt | 2 + strategic/evaluate_strategy.cxx | 83 ++++++++++ strategic/forecaster.cxx | 35 ++++ strategic/forecaster.hxx | 67 ++++++++ strategic/oracle.cxx | 0 strategic/oracle.hxx | 0 strategic/simple_stock_strategy.cxx | 111 +++++++++++++ strategic/simple_stock_strategy.hxx | 67 ++++++++ strategic/state.cxx | 0 strategic/state.hxx | 0 strategic/strategy.cxx | 20 +++ strategic/strategy.hxx | 48 ++++++ strategic/trivial_forecaster.cxx | 61 +++++++ strategic/trivial_forecaster.hxx | 46 ++++++ time_series/CMakeLists.txt | 2 +- time_series/time_series_new.cxx | 244 ++++++++++++++++++++++++++++ time_series/time_series_new.hxx | 130 +++++++++++++++ 18 files changed, 917 insertions(+), 1 deletion(-) 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/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 create mode 100644 time_series/time_series_new.cxx create mode 100644 time_series/time_series_new.hxx diff --git a/CMakeLists.txt b/CMakeLists.txt index cc629b75..9c9ac947 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/strategic/CMakeLists.txt b/strategic/CMakeLists.txt new file mode 100644 index 00000000..abe7e1ec --- /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) +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..2f64e192 --- /dev/null +++ b/strategic/evaluate_strategy.cxx @@ -0,0 +1,83 @@ +#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 "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); + + Forecaster* forecaster = Forecaster::initialize_from_arguments(arguments); + Strategy* strategy = Strategy::initialize_from_arguments(arguments); + + string time_series_filename; + get_argument(arguments, "--time_series_filename", true, time_series_filename); + TimeSeriesNew *time_series = new TimeSeriesNew(time_series_filename, forecaster->get_input_parameter_names(), forecaster->get_output_parameter_names()); + + if (argument_exists(arguments, "--normalize")) { + //Normalizer *normalizer = Normalizer::initialize_from_arguments(arguments); + //normalizer->normalize(time_series_new); + } + + double reward = 0.0; + + map inputs; + map outputs; + + for (int32_t i = 0; i < time_series->get_number_rows() - time_offset; i++) { + time_series->get_inputs_at(i, inputs); + time_series->get_outputs_at(i + time_offset, outputs); + + if (Log::at_level(Log::DEBUG)) print_values("inputs", inputs); + + map forecast = forecaster->forecast(inputs); + if (Log::at_level(Log::DEBUG)) print_values("forecast", forecast); + + strategy->make_move(inputs, forecast); + + //current_reward = oracle->calculate_reward(strategy->get_state(), outputs); + + //strategy->report_reward(current_reward); + + //reward += current_reward; + } + + delete time_series; + delete forecaster; + //delete strategy; + + + Log::release_id("main"); + return 0; +} diff --git a/strategic/forecaster.cxx b/strategic/forecaster.cxx new file mode 100644 index 00000000..07c703ee --- /dev/null +++ b/strategic/forecaster.cxx @@ -0,0 +1,35 @@ +#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) { + + string forecaster_type; + get_argument(arguments, "--forecaster_type", true, forecaster_type); + + if (forecaster_type == "trivial") { + 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); + + 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..4ed47a42 --- /dev/null +++ b/strategic/forecaster.hxx @@ -0,0 +1,67 @@ +#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 + * + * \return a pointer to a Forecaster object + */ + static Forecaster* initialize_from_arguments(const vector &arguments); + + /** + * 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..e69de29b diff --git a/strategic/oracle.hxx b/strategic/oracle.hxx new file mode 100644 index 00000000..e69de29b diff --git a/strategic/simple_stock_strategy.cxx b/strategic/simple_stock_strategy.cxx new file mode 100644 index 00000000..222c7770 --- /dev/null +++ b/strategic/simple_stock_strategy.cxx @@ -0,0 +1,111 @@ +#include "simple_stock_strategy.hxx" + +#include "common/log.hxx" +#include "common/arguments.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); + + 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) { + current_money += purchased_shares[stock] * context.at(stock + "_PRC"); + } + 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 + "_PRC")) { + 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 + "_RET")) { + 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 + "_RET"); + + 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 + "_PRC") > 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 + "_PRC"), 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 + "_PRC"); + 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 + "_PRC"); + 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; + } +} + + +/* +Stock* get_state() { +} +*/ diff --git a/strategic/simple_stock_strategy.hxx b/strategic/simple_stock_strategy.hxx new file mode 100644 index 00000000..fa998604 --- /dev/null +++ b/strategic/simple_stock_strategy.hxx @@ -0,0 +1,67 @@ +#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; + + //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); + +}; + + +#endif diff --git a/strategic/state.cxx b/strategic/state.cxx new file mode 100644 index 00000000..e69de29b diff --git a/strategic/state.hxx b/strategic/state.hxx new file mode 100644 index 00000000..e69de29b 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..98065b03 --- /dev/null +++ b/strategic/strategy.hxx @@ -0,0 +1,48 @@ +#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); + + /** + * 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..d5dbc877 --- /dev/null +++ b/strategic/trivial_forecaster.cxx @@ -0,0 +1,61 @@ +#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) { + 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 diff --git a/time_series/CMakeLists.txt b/time_series/CMakeLists.txt index e8f9a686..95b9f10e 100644 --- a/time_series/CMakeLists.txt +++ b/time_series/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(exact_time_series time_series.cxx) +add_library(exact_time_series time_series.cxx time_series_new.cxx) add_executable(normalize_data normalize_data.cxx) target_link_libraries(normalize_data exact_time_series exact_common) diff --git a/time_series/time_series_new.cxx b/time_series/time_series_new.cxx new file mode 100644 index 00000000..bd7c4b4f --- /dev/null +++ b/time_series/time_series_new.cxx @@ -0,0 +1,244 @@ +#include + +#include +using std::find; + + +#include +using std::ifstream; + +#include +using std::stringstream; + +#include +using std::invalid_argument; + +#include +using std::string; +using std::getline; + + +#include +using std::vector; + +#include "common/log.hxx" +#include "time_series_new.hxx" + + +void string_split_new(const string& s, char delim, vector& result) { + stringstream ss; + ss.str(s); + + string item; + while (getline(ss, item, delim)) { + // get rid of carriage returns (sometimes windows messes this up) + item.erase(std::remove(item.begin(), item.end(), '\r'), item.end()); + + result.push_back(item); + } +} + + +/** + * Initialize the input and output parameter names and then call the default constructor + * which only takes a filename + */ +TimeSeriesNew::TimeSeriesNew(string _filename, const vector &_input_parameter_names, const vector &_output_parameter_names) : TimeSeriesNew(_filename) { + + //set the input/output parameter names to those specified by the user + input_parameter_names = _input_parameter_names; + output_parameter_names = _output_parameter_names; + + // check to see that all the specified input and output parameter names are in the file + for (int32_t i = 0; i < (int32_t) input_parameter_names.size(); i++) { + if (find(parameter_names.begin(), parameter_names.end(), input_parameter_names[i]) == parameter_names.end()) { + // one of the given parameter_names didn't exist in the time series file + Log::fatal("ERROR: could not find specified input parameter name '%s' in time series file: '%s'\n", input_parameter_names[i].c_str(), filename.c_str() ); + + Log::fatal("file's parameter_names:\n"); + for (int32_t j = 0; j < (int32_t) parameter_names.size(); j++) { + Log::fatal("\t'%s'\n", parameter_names[j].c_str()); + } + exit(1); + } + } + + for (int32_t i = 0; i < (int32_t) output_parameter_names.size(); i++) { + if (find(parameter_names.begin(), parameter_names.end(), output_parameter_names[i]) == parameter_names.end()) { + // one of the given parameter_names didn't exist in the time series file + Log::fatal("ERROR: could not find specified output parameter name '%s' in time series file: '%s'\n", output_parameter_names[i].c_str(), filename.c_str() ); + + Log::fatal("file's parameter_names:\n"); + for (int32_t j = 0; j < (int32_t) parameter_names.size(); j++) { + Log::fatal("\t'%s'\n", parameter_names[j].c_str()); + } + exit(1); + } + } +} + +TimeSeriesNew::TimeSeriesNew(string _filename) { + filename = _filename; + ifstream ts_file(filename); + + string line; + if (!getline(ts_file, line)) { + Log::error("ERROR! Could not get headers from the CSV file (first line). File potentially empty!\n"); + exit(1); + } + + // if the first line is the comment character '#', remove that character from the header + if (line[0] == '#') { + line = line.substr(1); + } + + string_split_new(line, ',', parameter_names); + Log::info("parameter_names (%d):\n", parameter_names.size()); + for (int32_t i = 0; i < (int32_t)parameter_names.size(); i++) { + Log::info("\t%s\n", parameter_names[i].c_str()); + + time_series[parameter_names[i]] = new vector(); + } + + int32_t row = 1; + while (getline(ts_file, line)) { + if (line.size() == 0 || line[0] == '#' || row < 0) { + row++; + continue; + } + + vector parts; + string_split_new(line, ',', parts); + + if (parts.size() != parameter_names.size()) { + Log::fatal( + "ERROR! number of values in row %d was %d, but there were %d parameter_names in the header.\n", row, + parts.size(), parameter_names.size() + ); + exit(1); + } + + for (int32_t i = 0; i < (int32_t) parts.size(); i++) { + Log::trace("parts[%d]: %s being added to '%s'\n", i, parts[i].c_str(), parameter_names[i].c_str()); + + try { + time_series[parameter_names[i]]->push_back(stod(parts[i])); + } catch (const invalid_argument& ia) { + Log::fatal( + "file: '%s' -- invalid argument: '%s' on row %d and column %d: '%s', value: '%s'\n", + filename.c_str(), ia.what(), row, i, parameter_names[i].c_str(), parts[i].c_str() + ); + exit(1); + } + } + + row++; + } + Log::info("read %d rows.\n", row); + + number_rows = 0; + number_columns = parameter_names.size(); + int32_t prev_number_rows = 0; + int32_t column = 0; + for (auto kv = time_series.begin(); kv != time_series.end(); kv++) { + number_rows = kv->second->size(); + if (number_rows <= 0) { + Log::fatal("ERROR, number rows: %d <= 0\n", number_rows); + exit(1); + } + + //make sure all columns are the same length + if (column != 0 && number_rows != prev_number_rows) { + Log::fatal("ERROR, number rows on column %d (%d) was != number of rows on previous column (%d), all columns must be the same length.", column, number_rows, prev_number_rows); + exit(1); + } + } + + Log::info("read time series file '%s' with %d columns and %d rows.\n", filename.c_str(), number_columns, number_rows); + + + // If the input or output parameters are not specified, all are + // assumed to be used by default. + input_parameter_names = parameter_names; + output_parameter_names = parameter_names; +} + +TimeSeriesNew::~TimeSeriesNew() { + // the time series vectors are stored as the same pointers across the time_series, + // input_time_series, and output_time_series maps + for (auto it = time_series.begin(); it != time_series.end(); it = time_series.begin()) { + vector* series = it->second; + time_series.erase(it); + delete series; + } +} + +void TimeSeriesNew::export_vectors(vector > &inputs, vector > &outputs, int32_t output_time_offset) { + + inputs.resize(number_rows - output_time_offset, vector(get_number_input_columns())); + outputs.resize(number_rows - output_time_offset, vector(get_number_output_columns())); + + for (int32_t column = 0; column < input_parameter_names.size(); column++) { + vector *values = time_series[input_parameter_names[column]]; + + for (int32_t row = 0; row < number_rows - output_time_offset; row++) { + inputs[row][column] = values->at(row); + } + } + + for (int32_t column = 0; column < output_parameter_names.size(); column++) { + vector *values = time_series[output_parameter_names[column]]; + + for (int32_t row = 0; row < number_rows - output_time_offset; row++) { + outputs[row][column] = values->at(row + output_time_offset); + } + } +} + +void TimeSeriesNew::get_inputs_at(int32_t time_step, map &values) { + for (string input_parameter_name : input_parameter_names) { + values[input_parameter_name] = time_series[input_parameter_name]->at(time_step); + } +} + +void TimeSeriesNew::get_outputs_at(int32_t time_step, map &values) { + for (string output_parameter_name : output_parameter_names) { + values[output_parameter_name] = time_series[output_parameter_name]->at(time_step); + } +} + + + +string TimeSeriesNew::get_filename() const { + return filename; +} + +int32_t TimeSeriesNew::get_number_rows() const { + return number_rows; +} + +int32_t TimeSeriesNew::get_number_columns() const { + return number_columns; +} + +int32_t TimeSeriesNew::get_number_input_columns() const { + return input_parameter_names.size(); +} + +int32_t TimeSeriesNew::get_number_output_columns() const { + return output_parameter_names.size(); +} + +vector TimeSeriesNew::get_parameter_names() const { + return parameter_names; +} + +vector TimeSeriesNew::get_input_parameter_names() const { + return input_parameter_names; +} + +vector TimeSeriesNew::get_output_parameter_names() const { + return output_parameter_names; +} + + diff --git a/time_series/time_series_new.hxx b/time_series/time_series_new.hxx new file mode 100644 index 00000000..79a59db1 --- /dev/null +++ b/time_series/time_series_new.hxx @@ -0,0 +1,130 @@ +#ifndef EXAMM_TIME_SERIES_NEW_HXX +#define EXAMM_TIME_SERIES_NEW_HXX + +#include +using std::ostream; + +#include +using std::string; + +#include +using std::map; + +#include +using std::vector; + +class TimeSeriesNew { + private: + int32_t number_rows; + int32_t number_columns; + string filename; + + vector parameter_names; + vector input_parameter_names; + vector output_parameter_names; + + map* > time_series; + + public: + /** + * Initializes a (typically multivariate) time series from a file, with user specified + * input and output parameter names. + * + * \param filename is the filename (CSV) to load the time series from. the first line + * are the column headers (parameter names), followed by rows of data. + * \param input_parameter_names are the columns to be used as input data for whatever + * model or process is being used. + * \param output_parameter_names are the columns to be used as output data for whatever + * model or process is being used. + */ + TimeSeriesNew(string filename, const vector &input_parameter_names, const vector &output_parameter_names); + + /** + * Initializes a (typically multivariate) time series from a file, defaulting to having + * all columns be used as input and output parameters. + * + * \param filename is the filename (CSV) to load the time series from. the first line + * are the column headers (parameter names), followed by rows of data. + */ + TimeSeriesNew(string filename); + + /** + * Destructor for TimeSeriesNew. + */ + ~TimeSeriesNew(); + + /** + * Export the input columns into a 2-d vectors (rows x columns) where each + * row is a step in the time series for the input and output columns. The + * number of rows will be (number_rows - time_step) so the number of input + * rows and output rows is the same. + * + * \param inputs is the vector to fill in + * \param outputs the vector to fill in + * \param output_time_offset is how many time steps in the future the outputs + * will be to their corresponding inputs, e.g., an output_time_offset of + * 1 will set the outputs shifted 1 into the future from the inputs. + */ + void export_vectors(vector > &inputs, vector > &outputs, int32_t output_time_offset); + + /** + * Get the input for each input parameter at a given time step. + * + * \param time_step is the time (row number) for the values + * \param inputs is a map of strings (parameter names) to values at + * the given time step. + */ + void get_inputs_at(int32_t time_step, map &inputs); + + /** + * Get the output for each output parameter at a given time step. + * + * \param time_step is the time (row number) for the values + * \param outputs is a map of strings (parameter names) to values at + * the given time step. + */ + void get_outputs_at(int32_t time_step, map &outputs); + + + /** + * \return the filename used to create the time series. + */ + string get_filename() const; + + /** + * \return the number of rows (not counting the headers) in the time series. + */ + int32_t get_number_rows() const; + + /** + * \return the total number of columns (parameters) in the time series. + */ + int32_t get_number_columns() const; + + /** + * \return the number of columns used as input data. + */ + int32_t get_number_input_columns() const; + + /** + * \return the number of columns used as output data. + */ + int32_t get_number_output_columns() const; + + /** + * \return a vector of all the column headers (parameter names). + */ + vector get_parameter_names() const; + + /** + * \return a vector of all the column headers used as input data. + */ + vector get_input_parameter_names() const; + + /** + * \return a vector of all the column headers used as output data. + */ + vector get_output_parameter_names() const; +}; + +#endif From 8d03134629327702e1bd075d2aea3551a44b8740 Mon Sep 17 00:00:00 2001 From: Travis Desell Date: Sat, 2 Mar 2024 19:52:19 -0500 Subject: [PATCH 2/3] Added in oracle and portfolio code --- strategic/CMakeLists.txt | 2 +- strategic/evaluate_strategy.cxx | 18 ++++++++--- strategic/oracle.cxx | 20 ++++++++++++ strategic/oracle.hxx | 48 +++++++++++++++++++++++++++++ strategic/portfolio.cxx | 25 +++++++++++++++ strategic/portfolio.hxx | 44 ++++++++++++++++++++++++++ strategic/portfolio_oracle.cxx | 35 +++++++++++++++++++++ strategic/portfolio_oracle.hxx | 48 +++++++++++++++++++++++++++++ strategic/simple_stock_strategy.cxx | 35 +++++++++++++-------- strategic/simple_stock_strategy.hxx | 7 +++++ strategic/state.cxx | 2 ++ strategic/state.hxx | 18 +++++++++++ strategic/strategy.hxx | 9 ++++-- 13 files changed, 291 insertions(+), 20 deletions(-) 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 diff --git a/strategic/CMakeLists.txt b/strategic/CMakeLists.txt index abe7e1ec..92b89e74 100644 --- a/strategic/CMakeLists.txt +++ b/strategic/CMakeLists.txt @@ -1,2 +1,2 @@ -add_executable(evaluate_strategy evaluate_strategy.cxx forecaster.cxx trivial_forecaster.cxx strategy.cxx simple_stock_strategy.cxx) +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 index 2f64e192..45de864b 100644 --- a/strategic/evaluate_strategy.cxx +++ b/strategic/evaluate_strategy.cxx @@ -13,8 +13,11 @@ using std::vector; #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) { @@ -40,6 +43,7 @@ int main(int argc, char** argv) { Forecaster* forecaster = Forecaster::initialize_from_arguments(arguments); Strategy* strategy = Strategy::initialize_from_arguments(arguments); + Oracle* oracle = Oracle::initialize_from_arguments(arguments); string time_series_filename; get_argument(arguments, "--time_series_filename", true, time_series_filename); @@ -50,14 +54,15 @@ int main(int argc, char** argv) { //normalizer->normalize(time_series_new); } + double current_reward = 0.0; double reward = 0.0; map inputs; - map outputs; + 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_outputs_at(i + time_offset, outputs); + time_series->get_inputs_at(i + time_offset, next_inputs); if (Log::at_level(Log::DEBUG)) print_values("inputs", inputs); @@ -66,16 +71,21 @@ int main(int argc, char** argv) { strategy->make_move(inputs, forecast); - //current_reward = oracle->calculate_reward(strategy->get_state(), outputs); + 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 strategy; + delete oracle; Log::release_id("main"); diff --git a/strategic/oracle.cxx b/strategic/oracle.cxx index e69de29b..200d87cb 100644 --- a/strategic/oracle.cxx +++ 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 index e69de29b..3cdc3186 100644 --- a/strategic/oracle.hxx +++ 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..5234b437 --- /dev/null +++ b/strategic/portfolio.cxx @@ -0,0 +1,25 @@ +#include "common/log.hxx" + +#include "portfolio.hxx" + + +Portfolio::Portfolio(double _current_money_pool) : current_money_pool(_current_money_pool) { +} + +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 + "_PRC"); + + value += price * purchased_shares; + } + + return value; +} diff --git a/strategic/portfolio.hxx b/strategic/portfolio.hxx new file mode 100644 index 00000000..6da05222 --- /dev/null +++ b/strategic/portfolio.hxx @@ -0,0 +1,44 @@ +#ifndef STOCK_PORTFOLIO_HXX +#define STOCK_PORTFOLIO_HXX + +#include +using std::map; + +#include +using std::string; + +#include "state.hxx" + + +class Portfolio : public State { + private: + double current_money_pool; + map shares; + + public: + /** + * Initialize the portfolio with how much money is available. + */ + Portfolio(double _current_money_pool); + + /** + * 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 index 222c7770..ee1b4069 100644 --- a/strategic/simple_stock_strategy.cxx +++ b/strategic/simple_stock_strategy.cxx @@ -1,8 +1,12 @@ -#include "simple_stock_strategy.hxx" - #include "common/log.hxx" #include "common/arguments.hxx" +#include "simple_stock_strategy.hxx" +#include "portfolio.hxx" + +string price_suffix = "_PRC"; +string return_suffix = "_predicted_RET"; + SimpleStockStrategy::SimpleStockStrategy(const vector &arguments) { buy_threshold = 0; get_argument(arguments, "--buy_threshold", false, buy_threshold); @@ -25,7 +29,7 @@ SimpleStockStrategy::SimpleStockStrategy(const vector &arguments) { void SimpleStockStrategy::make_move(const map &context, const map &forecast) { double current_money = money_pool; for (string stock : stocks) { - current_money += purchased_shares[stock] * context.at(stock + "_PRC"); + current_money += purchased_shares[stock] * context.at(stock + price_suffix); } Log::info("current money: %lf\n", current_money); @@ -35,12 +39,12 @@ void SimpleStockStrategy::make_move(const map &context, const ma //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 + "_PRC")) { + 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 + "_RET")) { + 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; } @@ -49,7 +53,7 @@ void SimpleStockStrategy::make_move(const map &context, const ma //determine which stocks we will sell, and which stocks we will buy for (string stock : stocks) { - double forecasted_return = forecast.at(stock + "_RET"); + 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); @@ -59,13 +63,13 @@ void SimpleStockStrategy::make_move(const map &context, const ma 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 + "_PRC") > bought_price[stock]) { + } 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 + "_PRC"), bought_price[stock]); + 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]); } } @@ -73,7 +77,7 @@ void SimpleStockStrategy::make_move(const map &context, const ma //first sell of stocks to sell for (string stock : stocks_to_sell) { if (purchased_shares[stock] > 0) { - double stock_price = context.at(stock + "_PRC"); + double stock_price = context.at(stock + price_suffix); double gain = purchased_shares[stock] * stock_price; money_pool += gain; @@ -91,7 +95,7 @@ void SimpleStockStrategy::make_move(const map &context, const ma double money_per_stock = money_pool / stocks_to_buy.size(); for (string stock : stocks_to_buy) { - double stock_price = context.at(stock + "_PRC"); + double stock_price = context.at(stock + price_suffix); double shares = money_per_stock / stock_price; purchased_shares[stock] = shares; bought_price[stock] = stock_price; @@ -105,7 +109,12 @@ void SimpleStockStrategy::make_move(const map &context, const ma } -/* -Stock* get_state() { +State* SimpleStockStrategy::get_state() { + Portfolio *portfolio = new Portfolio(money_pool); + + 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 index fa998604..ebe5914a 100644 --- a/strategic/simple_stock_strategy.hxx +++ b/strategic/simple_stock_strategy.hxx @@ -61,6 +61,13 @@ class SimpleStockStrategy : public Strategy { */ 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(); }; diff --git a/strategic/state.cxx b/strategic/state.cxx index e69de29b..d1b01861 100644 --- a/strategic/state.cxx +++ b/strategic/state.cxx @@ -0,0 +1,2 @@ +#include "state.hxx" + diff --git a/strategic/state.hxx b/strategic/state.hxx index e69de29b..d721f15a 100644 --- a/strategic/state.hxx +++ 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.hxx b/strategic/strategy.hxx index 98065b03..4e1b8149 100644 --- a/strategic/strategy.hxx +++ b/strategic/strategy.hxx @@ -10,7 +10,7 @@ using std::string; #include using std::vector; -//#include "state.hxx" +#include "state.hxx" class Strategy { public: @@ -24,6 +24,11 @@ class Strategy { */ 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. @@ -41,7 +46,7 @@ class Strategy { * 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; + virtual State* get_state() = 0; }; From b4da4a82803e9cbb14083b664f9ae1ae7f50e71a Mon Sep 17 00:00:00 2001 From: Travis Desell Date: Thu, 4 Apr 2024 22:58:40 -0400 Subject: [PATCH 3/3] Updated to allow for regex in parameter names --- strategic/evaluate_strategy.cxx | 14 +++++--- strategic/forecaster.cxx | 6 +--- strategic/forecaster.hxx | 6 +++- strategic/portfolio.cxx | 4 +-- strategic/portfolio.hxx | 11 +++++- strategic/simple_stock_strategy.cxx | 12 +++++-- strategic/simple_stock_strategy.hxx | 7 ++++ strategic/trivial_forecaster.cxx | 2 ++ time_series/time_series_new.cxx | 56 +++++++++++++++++++++++++++-- 9 files changed, 99 insertions(+), 19 deletions(-) diff --git a/strategic/evaluate_strategy.cxx b/strategic/evaluate_strategy.cxx index 45de864b..2649e92f 100644 --- a/strategic/evaluate_strategy.cxx +++ b/strategic/evaluate_strategy.cxx @@ -41,13 +41,17 @@ int main(int argc, char** argv) { int32_t time_offset = 1; get_argument(arguments, "--time_offset", false, time_offset); - Forecaster* forecaster = Forecaster::initialize_from_arguments(arguments); - Strategy* strategy = Strategy::initialize_from_arguments(arguments); - Oracle* oracle = Oracle::initialize_from_arguments(arguments); + 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, forecaster->get_input_parameter_names(), forecaster->get_output_parameter_names()); + 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); @@ -66,6 +70,8 @@ int main(int argc, char** argv) { 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); diff --git a/strategic/forecaster.cxx b/strategic/forecaster.cxx index 07c703ee..32a99fb9 100644 --- a/strategic/forecaster.cxx +++ b/strategic/forecaster.cxx @@ -15,16 +15,12 @@ vector Forecaster::get_input_parameter_names() const { return input_parameter_names; } -Forecaster* Forecaster::initialize_from_arguments(const vector &arguments) { +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") { - 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); - return new TrivialForecaster(arguments, input_parameter_names, output_parameter_names); } else { diff --git a/strategic/forecaster.hxx b/strategic/forecaster.hxx index 4ed47a42..f37015f3 100644 --- a/strategic/forecaster.hxx +++ b/strategic/forecaster.hxx @@ -45,10 +45,14 @@ class Forecaster { * 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); + 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 diff --git a/strategic/portfolio.cxx b/strategic/portfolio.cxx index 5234b437..2917d93a 100644 --- a/strategic/portfolio.cxx +++ b/strategic/portfolio.cxx @@ -3,7 +3,7 @@ #include "portfolio.hxx" -Portfolio::Portfolio(double _current_money_pool) : current_money_pool(_current_money_pool) { +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) { @@ -16,7 +16,7 @@ double Portfolio::calculate_value(const map &context) { 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 + "_PRC"); + double price = context.at(stock + price_suffix); value += price * purchased_shares; } diff --git a/strategic/portfolio.hxx b/strategic/portfolio.hxx index 6da05222..045ae075 100644 --- a/strategic/portfolio.hxx +++ b/strategic/portfolio.hxx @@ -12,14 +12,23 @@ using std::string; 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); + Portfolio(double _current_money_pool, string price_suffix); /** * Used to specify how much of each stock has been purchased (if any). diff --git a/strategic/simple_stock_strategy.cxx b/strategic/simple_stock_strategy.cxx index ee1b4069..04b3b3f1 100644 --- a/strategic/simple_stock_strategy.cxx +++ b/strategic/simple_stock_strategy.cxx @@ -4,8 +4,6 @@ #include "simple_stock_strategy.hxx" #include "portfolio.hxx" -string price_suffix = "_PRC"; -string return_suffix = "_predicted_RET"; SimpleStockStrategy::SimpleStockStrategy(const vector &arguments) { buy_threshold = 0; @@ -17,6 +15,12 @@ SimpleStockStrategy::SimpleStockStrategy(const vector &arguments) { 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) { @@ -29,6 +33,8 @@ SimpleStockStrategy::SimpleStockStrategy(const vector &arguments) { 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); @@ -110,7 +116,7 @@ void SimpleStockStrategy::make_move(const map &context, const ma State* SimpleStockStrategy::get_state() { - Portfolio *portfolio = new Portfolio(money_pool); + Portfolio *portfolio = new Portfolio(money_pool, price_suffix); for (string stock : stocks) { portfolio->add_stock(stock, purchased_shares[stock]); diff --git a/strategic/simple_stock_strategy.hxx b/strategic/simple_stock_strategy.hxx index ebe5914a..7f792f56 100644 --- a/strategic/simple_stock_strategy.hxx +++ b/strategic/simple_stock_strategy.hxx @@ -25,6 +25,13 @@ class SimpleStockStrategy : public Strategy { //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 diff --git a/strategic/trivial_forecaster.cxx b/strategic/trivial_forecaster.cxx index d5dbc877..cc240469 100644 --- a/strategic/trivial_forecaster.cxx +++ b/strategic/trivial_forecaster.cxx @@ -30,6 +30,8 @@ TrivialForecaster::TrivialForecaster(const vector &arguments, const vect 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; diff --git a/time_series/time_series_new.cxx b/time_series/time_series_new.cxx index bd7c4b4f..0ff603d8 100644 --- a/time_series/time_series_new.cxx +++ b/time_series/time_series_new.cxx @@ -7,6 +7,10 @@ using std::find; #include using std::ifstream; +#include +using std::regex; +using std::regex_match; + #include using std::stringstream; @@ -49,10 +53,32 @@ TimeSeriesNew::TimeSeriesNew(string _filename, const vector &_input_para input_parameter_names = _input_parameter_names; output_parameter_names = _output_parameter_names; + vector regex_input_parameter_names; + vector regex_output_parameter_names; + // check to see that all the specified input and output parameter names are in the file for (int32_t i = 0; i < (int32_t) input_parameter_names.size(); i++) { - if (find(parameter_names.begin(), parameter_names.end(), input_parameter_names[i]) == parameter_names.end()) { - // one of the given parameter_names didn't exist in the time series file + regex e(input_parameter_names[i]); + + bool found = false; + for (string parameter_name : parameter_names) { + Log::trace("checking if '%s' matches regex '%s'\n", parameter_name.c_str(), input_parameter_names[i].c_str()); + + if (regex_match(parameter_name, e)) { + found = true; + + Log::trace("'%s' matched regex '%s'\n", parameter_name.c_str(), input_parameter_names[i].c_str()); + + //add this parameter name to the input parameter names if we haven't already + if (find(input_parameter_names.begin(), input_parameter_names.end(), parameter_name) == input_parameter_names.end()) { + Log::info("\tadded '%s' to input parameter names\n", parameter_name.c_str()); + regex_input_parameter_names.push_back(parameter_name); + } + } + } + + if (!found) { + // one of the given parameter_names didn't match to any column header in the time series file Log::fatal("ERROR: could not find specified input parameter name '%s' in time series file: '%s'\n", input_parameter_names[i].c_str(), filename.c_str() ); Log::fatal("file's parameter_names:\n"); @@ -64,7 +90,26 @@ TimeSeriesNew::TimeSeriesNew(string _filename, const vector &_input_para } for (int32_t i = 0; i < (int32_t) output_parameter_names.size(); i++) { - if (find(parameter_names.begin(), parameter_names.end(), output_parameter_names[i]) == parameter_names.end()) { + regex e(output_parameter_names[i]); + + bool found = false; + for (string parameter_name : parameter_names) { + Log::trace("checking if '%s' matches regex '%s'\n", parameter_name.c_str(), output_parameter_names[i].c_str()); + + if (regex_match(parameter_name, e)) { + found = true; + + Log::trace("'%s' matched regex '%s'\n", parameter_name.c_str(), output_parameter_names[i].c_str()); + + //add this parameter name to the output parameter names if we haven't already + if (find(output_parameter_names.begin(), output_parameter_names.end(), parameter_name) == output_parameter_names.end()) { + Log::info("\tadded '%s' to output parameter names\n", parameter_name.c_str()); + regex_output_parameter_names.push_back(parameter_name); + } + } + } + + if (!found) { // one of the given parameter_names didn't exist in the time series file Log::fatal("ERROR: could not find specified output parameter name '%s' in time series file: '%s'\n", output_parameter_names[i].c_str(), filename.c_str() ); @@ -75,6 +120,9 @@ TimeSeriesNew::TimeSeriesNew(string _filename, const vector &_input_para exit(1); } } + + input_parameter_names = regex_input_parameter_names; + output_parameter_names = regex_output_parameter_names; } TimeSeriesNew::TimeSeriesNew(string _filename) { @@ -196,12 +244,14 @@ void TimeSeriesNew::export_vectors(vector > &inputs, vector &values) { + values.clear(); for (string input_parameter_name : input_parameter_names) { values[input_parameter_name] = time_series[input_parameter_name]->at(time_step); } } void TimeSeriesNew::get_outputs_at(int32_t time_step, map &values) { + values.clear(); for (string output_parameter_name : output_parameter_names) { values[output_parameter_name] = time_series[output_parameter_name]->at(time_step); }