From 849afee493a5868f7e71998ac2c2a7e3c416b469 Mon Sep 17 00:00:00 2001 From: Alexandre Catarino Date: Tue, 9 Jul 2024 23:39:04 +0100 Subject: [PATCH] Adds CAI Methods - CreateModel - Train - Get Results - Predict --- CAI_Demo_20231010.ipynb | 531 +++++++++++++++++++++++++ Models/ModelParameters.cs | 247 ++++++++++++ Models/ModelResponse.cs | 48 +++ Models/PredictResult.cs | 87 ++++ Models/TrainModelResponse.cs | 30 ++ Models/TrainingResult.cs | 85 ++++ PredictNowClient.cs | 114 +++++- PredictNowNET-CAI-Example-Python.ipynb | 282 +++++++++++++ PredictNowNET.csproj | 2 +- Tests/PredictNowClientTests.cs | 31 +- 10 files changed, 1453 insertions(+), 4 deletions(-) create mode 100644 CAI_Demo_20231010.ipynb create mode 100644 Models/ModelParameters.cs create mode 100644 Models/ModelResponse.cs create mode 100644 Models/PredictResult.cs create mode 100644 Models/TrainModelResponse.cs create mode 100644 Models/TrainingResult.cs create mode 100644 PredictNowNET-CAI-Example-Python.ipynb diff --git a/CAI_Demo_20231010.ipynb b/CAI_Demo_20231010.ipynb new file mode 100644 index 0000000..8ab1b57 --- /dev/null +++ b/CAI_Demo_20231010.ipynb @@ -0,0 +1,531 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CAI demo notebook\n", + "For demo purposes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# basic import statements\n", + "from predictnow.pdapi import PredictNowClient\n", + "import os\n", + "import pandas as pd\n", + "import requests\n", + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Basic configuration\n", + "# User ID\n", + "username = \"variable29\" # \"variable29\", only letters, numbers, or underscores\n", + "email = \"variable29@gmail.com\" # \"variable29@gmail.com\"\n", + "\n", + "# connect to API\n", + "makeshiftapi_host = \"http://127.0.0.1:5000/\" # local host for debugging purposes only\n", + "\n", + "api_key = \"--------\"\n", + "client = PredictNowClient(makeshiftapi_host,api_key)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " date SPY\n", + "2013 2018-01-02 0.007157\n", + "2014 2018-01-03 0.006325\n", + "2015 2018-01-04 0.004215\n", + "2016 2018-01-05 0.006664\n", + "2017 2018-01-08 0.001829\n", + "... ... ...\n", + "3309 2023-02-27 0.003406\n", + "3310 2023-02-28 -0.003696\n", + "3311 2023-03-01 -0.003836\n", + "3312 2023-03-02 0.007777\n", + "3313 2023-03-03 0.016038\n", + "\n", + "[1301 rows x 2 columns]\n", + "42\n" + ] + } + ], + "source": [ + "import pandas as pd\n", + "import os\n", + "# load and process data\n", + "input_filename = \"ETF_return.csv\" # pre-processed to shift the date by one role\n", + "df_return = pd.read_csv(os.path.join(\".\", \"Data\", input_filename), parse_dates=[\"date\"])\n", + "\n", + "# CAI predicts the sign of a single return stream, and any additional columns will be taken as features\n", + "# so we will only keep SPY in this demo\n", + "target_label = \"SPY\"\n", + "df_return = df_return[[\"date\", target_label]].iloc[1:].copy()\n", + "\n", + "# train test split. Here we will use all data between 2018 and 2023 as training data set to predict returns of 2023\n", + "# you can change the training time window and testing period accordingly\n", + "# they are actually sent together as on file to the API, but we do need to determine their sizes.\n", + "df_train = df_return.loc[(df_return[\"date\"] >= pd.to_datetime(\"2018-01-01\")) & (df_return[\"date\"] <= pd.to_datetime(\"2022-12-31\"))]\n", + "df_test = df_return.loc[df_return[\"date\"] >= pd.to_datetime(\"2023-01-01\")]\n", + "df_input = pd.concat([df_train, df_test])\n", + "\n", + "# finally a quick snap on the data\n", + "print(df_input)\n", + "print(str(len(df_test)))" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "# model configuration\n", + "model_name = \"DemoModel\"\n", + "params= {\n", + " \"timeseries\": \"no\", # (yes, no)\n", + " \"type\": \"classification\", # (classification, regression)\n", + " \"feature_selection\": \"shap\", # (shap, cmda, none)\n", + " \"analysis\": \"small\", # (small, none)\n", + " \"boost\": \"gbdt\", # (dart, gbdt)\n", + " \"mode\": \"train\", # (train, live)\n", + " \"testsize\": str(len(df_test)), # testsize < 1 --> ratio, > 1 --> exact # of rows, we are using the size of df_test here\n", + " \"weights\": \"no\", # (yes, no, custom)\n", + " \"prob_calib\": \"no\", # (yes, no) -> refine your probability\n", + " \"eda\": \"no\", # (yes, no) -> exploratory data analysis\n", + " \"random_seed\":\"1\", # random seed for initialization, default=1\n", + " \"custom_weights\":\"\",\n", + " \"pre_engg_features_list\": [\"all\"] #['TR', 'CANARY', 'NOPE', 'OF'], Comment this param out in case it is not required to be used.\n", + " #cmda added features\n", + " # \"cmda_corr_method\":\"PEARSON\", # pearson,kendall,spearman\n", + " # \"cmda_n_clusters\":\"3\",\n", + " # \"cmda_select_top_n_clusters\":\"4\",\n", + " #\"mandatory_features\":['sma_5'],\n", + "}\n", + "df_input.name = model_name" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Response - create_model\n", + "{'message': 'Successfully stored the model', 'success': True, 'model_name': 'DemoModel'}\n" + ] + } + ], + "source": [ + "# create model and send training request\n", + "response = client.create_model(\n", + " username=username, \n", + " model_name=model_name,\n", + " params=params,\n", + ")\n", + "print(\"Response - create_model\")\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n", + "in false\n", + "return_op True\n", + "Response - train\n", + "{'message': 'Training the model is successfully requested.', 'model_name': 'saved_model_DemoModel.pkl', 'success': True, 'train_id': '982d529c-d654-4cfc-a8de-e52e0fb16df0'}\n" + ] + } + ], + "source": [ + "# start training, there will be an error message!\n", + "response = client.train(\n", + " model_name=model_name,\n", + " input_df=df_input,\n", + " label=target_label,\n", + " username=username,\n", + " email=email,\n", + " #external_feature=True\n", + ")\n", + "print('Response - train')\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current status:\n", + "{'status': 'Prediction completed! Experiment complete.', 'current': 6, 'datetime': '2023-12-19 16:45:07.795215', 'state': 'COMPLETED', 'result': 'Experiment complete', 'total': 6}\n" + ] + } + ], + "source": [ + "# check status\n", + "status = client.getstatus(\n", + " username=username,\n", + " train_id=response[\"train_id\"]\n", + ")\n", + "print(\"Current status:\")\n", + "print(status)\n", + "# cannot move on unter experiment is finished" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "predicted_prob_cv\n", + " Unnamed: 0 date 0.0 1.0\n", + "0 0 2018-01-02 0.006849 0.993151\n", + "1 1 2018-01-03 0.220018 0.779982\n", + "2 2 2018-01-04 0.156282 0.843718\n", + "3 3 2018-01-05 0.090680 0.909320\n", + "4 4 2018-01-08 0.430789 0.569211\n", + "... ... ... ... ...\n", + "1254 1254 2022-12-23 0.440742 0.559258\n", + "1255 1255 2022-12-27 0.029810 0.970190\n", + "1256 1256 2022-12-28 0.572378 0.427622\n", + "1257 1257 2022-12-29 0.077087 0.922913\n", + "1258 1258 2022-12-30 0.958594 0.041406\n", + "\n", + "[1259 rows x 4 columns]\n", + "predicted_prob_test\n", + " date 0.0 1.0\n", + "0 2023-01-03 0.907594 0.092406\n", + "1 2023-01-04 0.913679 0.086321\n", + "2 2023-01-05 0.357275 0.642725\n", + "3 2023-01-06 0.949131 0.050869\n", + "4 2023-01-09 0.708843 0.291157\n", + "5 2023-01-10 0.927519 0.072481\n", + "6 2023-01-11 0.583928 0.416072\n", + "7 2023-01-12 0.946364 0.053636\n", + "8 2023-01-13 0.335753 0.664247\n", + "9 2023-01-17 0.708922 0.291078\n", + "10 2023-01-18 0.841643 0.158357\n", + "11 2023-01-19 0.802962 0.197038\n", + "12 2023-01-20 0.102086 0.897914\n", + "13 2023-01-23 0.955160 0.044840\n", + "14 2023-01-24 0.262326 0.737674\n", + "15 2023-01-25 0.564705 0.435295\n", + "16 2023-01-26 0.184954 0.815046\n", + "17 2023-01-27 0.659500 0.340500\n", + "18 2023-01-30 0.042429 0.957571\n", + "19 2023-01-31 0.974373 0.025627\n", + "20 2023-02-01 0.969620 0.030380\n", + "21 2023-02-02 0.168541 0.831459\n", + "22 2023-02-03 0.718518 0.281482\n", + "23 2023-02-06 0.035984 0.964016\n", + "24 2023-02-07 0.810985 0.189015\n", + "25 2023-02-08 0.890561 0.109439\n", + "26 2023-02-09 0.743028 0.256972\n", + "27 2023-02-10 0.893943 0.106057\n", + "28 2023-02-13 0.979053 0.020947\n", + "29 2023-02-14 0.718608 0.281392\n", + "30 2023-02-15 0.402740 0.597260\n", + "31 2023-02-16 0.953071 0.046929\n", + "32 2023-02-17 0.665290 0.334710\n", + "33 2023-02-21 0.767746 0.232254\n", + "34 2023-02-22 0.729408 0.270592\n", + "35 2023-02-23 0.314779 0.685221\n", + "36 2023-02-24 0.969402 0.030598\n", + "37 2023-02-27 0.609278 0.390722\n", + "38 2023-02-28 0.481490 0.518510\n", + "39 2023-03-01 0.560528 0.439472\n", + "40 2023-03-02 0.407046 0.592954\n", + "41 2023-03-03 0.650097 0.349903\n", + "predicted_targets_cv\n", + " Unnamed: 0 true_target pred_target\n", + "0 0 1 1\n", + "1 1 1 1\n", + "2 2 1 1\n", + "3 3 1 1\n", + "4 4 1 1\n", + "... ... ... ...\n", + "1254 1254 1 1\n", + "1255 1255 0 1\n", + "1256 1256 0 0\n", + "1257 1257 1 1\n", + "1258 1258 0 0\n", + "\n", + "[1259 rows x 3 columns]\n", + "predicted_targets_test\n", + " date true_target pred_target\n", + "0 2023-01-03 0 0\n", + "1 2023-01-04 1 0\n", + "2 2023-01-05 0 1\n", + "3 2023-01-06 1 0\n", + "4 2023-01-09 0 0\n", + "5 2023-01-10 1 0\n", + "6 2023-01-11 1 0\n", + "7 2023-01-12 1 0\n", + "8 2023-01-13 1 1\n", + "9 2023-01-17 0 0\n", + "10 2023-01-18 0 0\n", + "11 2023-01-19 0 0\n", + "12 2023-01-20 1 1\n", + "13 2023-01-23 1 0\n", + "14 2023-01-24 0 1\n", + "15 2023-01-25 1 0\n", + "16 2023-01-26 1 1\n", + "17 2023-01-27 1 0\n", + "18 2023-01-30 0 1\n", + "19 2023-01-31 1 0\n", + "20 2023-02-01 1 0\n", + "21 2023-02-02 1 1\n", + "22 2023-02-03 0 0\n", + "23 2023-02-06 0 1\n", + "24 2023-02-07 1 0\n", + "25 2023-02-08 0 0\n", + "26 2023-02-09 0 0\n", + "27 2023-02-10 1 0\n", + "28 2023-02-13 1 0\n", + "29 2023-02-14 0 0\n", + "30 2023-02-15 1 1\n", + "31 2023-02-16 0 0\n", + "32 2023-02-17 0 0\n", + "33 2023-02-21 0 0\n", + "34 2023-02-22 0 0\n", + "35 2023-02-23 1 1\n", + "36 2023-02-24 0 0\n", + "37 2023-02-27 1 0\n", + "38 2023-02-28 0 1\n", + "39 2023-03-01 0 0\n", + "40 2023-03-02 1 1\n", + "41 2023-03-03 1 0\n", + "feature_importance\n", + " Unnamed: 0 0\n", + "0 NOPE_1 0.028866\n", + "1 CAN_1 0.024660\n", + "2 CAN_2 0.021757\n", + "3 FSTS_37 0.021105\n", + "4 FSTS_39 0.020998\n", + ".. ... ...\n", + "188 F21 0.002521\n", + "189 F27 0.002514\n", + "190 F13 0.002392\n", + "191 F29 0.002374\n", + "192 CAN_3 0.002338\n", + "\n", + "[193 rows x 2 columns]\n", + "performance_metrics\n", + " The range of the CV set is: 02-01-2018 00:00:00 to 30-12-2022 00:00:00\n", + "0 THE ACCURACY SCORE FOR CV = 0.5782366957903098 \n", + "1 THE F1 SCORE FOR CV = 0.5751686949988205 \n", + "2 THE AUC SCORE FOR CV= 0.5800305110602594 \n", + "3 The range of the test set is: 03-01-2023 00:00... \n", + "4 THE ACCURACY SCORE FOR TEST = 0.5238095238095238 \n", + "5 THE F1 SCORE FOR TEST = 0.5014005602240896 \n", + "6 THE AUC SCORE FOR TEST= 0.47045454545454546 \n" + ] + } + ], + "source": [ + "# check training is finished\n", + "assert status['state'] == \"COMPLETED\", \"Please wait for the training to finish.\"\n", + "\n", + "# load predictions\n", + "response = client.getresult(\n", + " model_name=model_name,\n", + " username=username,\n", + " )\n", + "# the response contains several groups of results, as follow:\n", + "\n", + "# predicted probability (float between 0 and 1) for validation/training data set, i.e. 2018 - 2022 in the demo experiment\n", + "# the last column notes the probability that it's a \"1\", i.e. positive return\n", + "predicted_prob_cv = pd.read_json(response.predicted_prob_cv)\n", + "print(\"predicted_prob_cv\")\n", + "print(predicted_prob_cv)\n", + "\n", + "# predicted probability (float between 0 and 1) for the testing data set, i.e. every row after 2023 in the demo experiment\n", + "predicted_prob_test = pd.read_json(response.predicted_prob_test)\n", + "print(\"predicted_prob_test\")\n", + "print(predicted_prob_test)\n", + "\n", + "# predicted label, 0 or 1, for validation/training data set. Classified as class 1 if probability > 0.5\n", + "predicted_targets_cv = pd.read_json(response.predicted_targets_cv)\n", + "print(\"predicted_targets_cv\")\n", + "print(predicted_targets_cv)\n", + "\n", + "# predicted label, 0 or 1, for testing data set. Classified as class 1 if probability > 0.5\n", + "predicted_targets_test = pd.read_json(response.predicted_targets_test)\n", + "print(\"predicted_targets_test\")\n", + "print(predicted_targets_test)\n", + "\n", + "# feature importance score, shows what features are being used in the prediction\n", + "# more helpful when you include your features\n", + "# and only works when you set param['feature_selection'] to shap or cmda\n", + "if response.feature_importance:\n", + " feature_importance = pd.read_json(response.feature_importance)\n", + " print(\"feature_importance\")\n", + " print(feature_importance)\n", + "\n", + "# performance metrics in terms of accuracies and so on\n", + "performance_metrics = pd.read_json(response.performance_metrics)\n", + "print(\"performance_metrics\")\n", + "print(performance_metrics)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [], + "source": [ + "# you can also save the results shown above to your disk, e.g.\n", + "predicted_prob_test.to_csv(os.path.join(\".\", \"predicted_prob_test.csv\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['__class__',\n", + " '__delattr__',\n", + " '__dict__',\n", + " '__dir__',\n", + " '__doc__',\n", + " '__eq__',\n", + " '__format__',\n", + " '__ge__',\n", + " '__getattribute__',\n", + " '__gt__',\n", + " '__hash__',\n", + " '__init__',\n", + " '__init_subclass__',\n", + " '__le__',\n", + " '__lt__',\n", + " '__module__',\n", + " '__ne__',\n", + " '__new__',\n", + " '__reduce__',\n", + " '__reduce_ex__',\n", + " '__repr__',\n", + " '__setattr__',\n", + " '__sizeof__',\n", + " '__str__',\n", + " '__subclasshook__',\n", + " '__weakref__',\n", + " 'eda_describe',\n", + " 'feature_importance',\n", + " 'lab_test',\n", + " 'performance_metrics',\n", + " 'predicted_prob_cv',\n", + " 'predicted_prob_test',\n", + " 'predicted_targets_cv',\n", + " 'predicted_targets_test',\n", + " 'success']" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dir(response)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "fdc63ddbea640049c0ebb823d00c0f37262ff671ac735114f7c5ea4f37555b87" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + }, + "varInspector": { + "cols": { + "lenName": 16, + "lenType": 16, + "lenVar": 40 + }, + "kernels_config": { + "python": { + "delete_cmd_postfix": "", + "delete_cmd_prefix": "del ", + "library": "var_list.py", + "varRefreshCmd": "print(var_dic_list())" + }, + "r": { + "delete_cmd_postfix": ") ", + "delete_cmd_prefix": "rm(", + "library": "var_list.r", + "varRefreshCmd": "cat(var_dic_list()) " + } + }, + "position": { + "height": "311.806px", + "left": "878.178px", + "right": "20px", + "top": "13.9653px", + "width": "540.984px" + }, + "types_to_exclude": [ + "module", + "function", + "builtin_function_or_method", + "instance", + "_Feature" + ], + "window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/Models/ModelParameters.cs b/Models/ModelParameters.cs new file mode 100644 index 0000000..21cfa1b --- /dev/null +++ b/Models/ModelParameters.cs @@ -0,0 +1,247 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using QuantConnect.Util; + +namespace QuantConnect.PredictNowNET.Models; + +/// +/// Type of Analysis +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum Analysis +{ + /// + /// No analysis + /// + None, + /// + /// + /// + Small +} + +/// +/// Type of Boost algorithms +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum Boost +{ + /// + /// Dropouts meet Multiple Additive Regression Trees. + /// + Dart, + /// + /// Gradient-boosted decision trees + /// + Gbdt +} + +/// +/// Feature selection +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum FeatureSelection +{ + /// + /// No selection + /// + None, + /// + /// SHapley Additive exPlanations + /// + Shap, + /// + /// Computer-Mediated Discourse Analysis + /// + CMDA +} + +/// +/// Model modes +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum Mode +{ + /// + /// Train + /// + Train, + /// + /// Live + /// + Live +} + +/// +/// Model type +/// +[JsonConverter(typeof(StringEnumConverter))] +public enum ModelType +{ + /// + /// Classification + /// + Classification, + /// + /// Regression + /// + Regression +} + +/// +/// Represents parameters for both backtests and live prediction +/// +public class ModelParameters +{ + /// + /// True if use timeseries + /// + [JsonProperty(PropertyName = "timeseries")] + [JsonConverter(typeof(YesNoJsonConverter))] + public bool Timeseries { get; private set; } + + /// + /// The type of the mode. For example regression or classification + /// + [JsonProperty(PropertyName = "type")] + public ModelType Type { get; private set; } + + /// + /// The feature selection + /// + [JsonProperty(PropertyName = "feature_selection")] + public FeatureSelection FeatureSelection { get; private set; } + + /// + /// The analysys output + /// + [JsonProperty(PropertyName = "analysis")] + public Analysis Analysis { get; private set; } + + /// + /// Select the Boost algorithm + /// + [JsonProperty(PropertyName = "boost")] + public Boost Boost { get; private set; } + + /// + /// The model mode + /// + [JsonProperty(PropertyName = "mode")] + public Mode Mode { get; } + + /// + /// The size of the test sample. If less than 1 --> ratio, otherwise --> exact # of rows + /// + [JsonProperty(PropertyName = "testsize")] + public string Testsize { get; private set; } + + /// + /// Define if should use weights: yes, no, or custom + /// + [JsonProperty(PropertyName = "weights")] + public string Weights { get; private set; } + + /// + /// True if should refine the probability + /// + [JsonProperty(PropertyName = "prob_calib")] + [JsonConverter(typeof(YesNoJsonConverter))] + public bool ProbabilityCalibration { get; private set; } + + /// + /// True if should use exploratory data analysis + /// + [JsonProperty(PropertyName = "eda")] + [JsonConverter(typeof(YesNoJsonConverter))] + public bool ExploratoryDataAnalysis { get; private set; } + + /// + /// Random seed for initialization + /// + [JsonProperty(PropertyName = "random_seed")] + public string RandomSeed { get; private set; } = "1"; + + /// + /// Define custom weights + /// + [JsonProperty(PropertyName = "custom_weights")] + public string CustomWeights { get; private set; } + + /// + /// Create a new instance of ModelParameters + /// + /// The model mode + /// The type of the mode. For example regression or classification + /// The feature selection + /// The analysys output + /// Select the Boost algorithm + /// The size of the test sample. If less than 1 --> ratio, otherwise --> exact # of rows + /// True if use timeseries + /// True if should refine the probability + /// True if should use exploratory data analysis + /// Define if should use weights: yes, no, or custom + /// Define custom weights + /// Random seed for initialization + public ModelParameters(Mode mode, ModelType type, FeatureSelection featureSelection, Analysis analysis, Boost boost, double testsize, bool timeseries, bool probabilityCalibration, bool exploratoryDataAnalysis, string weights, string customWeights= "", double randomSeed = 1) + { + Mode = mode; + Type = type; + FeatureSelection = featureSelection; + Analysis = analysis; + Boost = boost; + Testsize = testsize.ToString(); + Timeseries = timeseries; + ProbabilityCalibration = probabilityCalibration; + ExploratoryDataAnalysis = exploratoryDataAnalysis; + Weights = weights; + CustomWeights = customWeights; + RandomSeed = randomSeed.ToString(); + } + + /// + /// Returns a string that represents the current object + /// + /// A string that represents the current object + public override string ToString() => JsonConvert.SerializeObject(this); +} + +/// +/// Defines how bool should be serialized to json +/// +public class YesNoJsonConverter : TypeChangeJsonConverter +{ + /// + /// Convert the input value to a value to be serialized + /// + /// The input value to be converted before serialization + /// A new instance of TResult that is to be serialized + protected override string Convert(bool value) => value ? "yes" : "no"; + + /// + /// Converts the input value to be deserialized + /// + /// The deserialized value that needs to be converted to T + /// The converted value + protected override bool Convert(string value) => value.ToLower() switch + { + "yes" => true, + "no" => false, + _ => throw new ArgumentOutOfRangeException(nameof(value), $"Not expected value: {value}") + }; +} \ No newline at end of file diff --git a/Models/ModelResponse.cs b/Models/ModelResponse.cs new file mode 100644 index 0000000..4922fe6 --- /dev/null +++ b/Models/ModelResponse.cs @@ -0,0 +1,48 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using Newtonsoft.Json; + +namespace QuantConnect.PredictNowNET.Models; + +/// +/// Base model response +/// +public class ModelResponse +{ + /// + /// Model Name + /// + [JsonProperty(PropertyName = "model_name")] + public string ModelName { get; internal set; } = string.Empty; + + /// + /// Result Message + /// + [JsonProperty(PropertyName = "message")] + public string Message { get; internal set; } = string.Empty; + + /// + /// True if sucessfull + /// + [JsonProperty(PropertyName = "success")] + public bool Success { get; internal set; } + + /// + /// Returns a string that represents the current object + /// + /// A string that represents the current object + public override string ToString() => JsonConvert.SerializeObject(this); +} \ No newline at end of file diff --git a/Models/PredictResult.cs b/Models/PredictResult.cs new file mode 100644 index 0000000..9e9c69a --- /dev/null +++ b/Models/PredictResult.cs @@ -0,0 +1,87 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using Newtonsoft.Json; + +namespace QuantConnect.PredictNowNET.Models; + +/// +/// Model Prediction Result +/// +public class PredictResult +{ + /// + /// True if should use exploratory data analysis + /// + [JsonProperty(PropertyName = "eda")] + public string ExploratoryDataAnalysis { get; internal set; } = string.Empty; + + /// + /// Name of the file with the prediction + /// + [JsonProperty(PropertyName = "filename")] + public string Filename { get; internal set; } = string.Empty; + + /// + /// Labels used in the prediction + /// + [JsonProperty(PropertyName = "labels")] + public string Labels { get; internal set; } = string.Empty; + + /// + /// Objective function + /// + [JsonProperty(PropertyName = "objective")] + public string Objective { get; internal set; } = string.Empty; + + /// + /// True if should refine the probability + /// + [JsonProperty(PropertyName = "prob_calib")] + public string ProbabilityCalibration { get; internal set; } = string.Empty; + + /// + /// Probabilities + /// + [JsonProperty(PropertyName = "probabilities")] + public string Probabilities { get; internal set; } = string.Empty; + + /// + /// Title + /// + [JsonProperty(PropertyName = "title")] + public string Title { get; internal set; } = string.Empty; + + /// + /// + /// + [JsonProperty(PropertyName = "too_many_nulls_list")] + public string TooManyNullsList { get; internal set; } = string.Empty; + + /// + /// Result of the Prediction + /// + /// + public PredictResult(string title) + { + Title = title; + } + + /// + /// Returns a string that represents the current object + /// + /// A string that represents the current object + public override string ToString() => JsonConvert.SerializeObject(this); +} \ No newline at end of file diff --git a/Models/TrainModelResponse.cs b/Models/TrainModelResponse.cs new file mode 100644 index 0000000..fa37433 --- /dev/null +++ b/Models/TrainModelResponse.cs @@ -0,0 +1,30 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using Newtonsoft.Json; + +namespace QuantConnect.PredictNowNET.Models; + +/// +/// Model Training Result +/// +public class TrainModelResponse : ModelResponse +{ + /// + /// Train Id + /// + [JsonProperty(PropertyName = "train_id")] + public string TrainId { get; internal set; } = string.Empty; +} \ No newline at end of file diff --git a/Models/TrainingResult.cs b/Models/TrainingResult.cs new file mode 100644 index 0000000..e7006df --- /dev/null +++ b/Models/TrainingResult.cs @@ -0,0 +1,85 @@ +/* + * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. + * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +using Newtonsoft.Json; + +namespace QuantConnect.PredictNowNET.Models; + +/// +/// Model Training Result +/// +public class TrainingResult +{ + private static readonly string _emptyJson = "{}"; + + /// + /// Lab Test + /// + [JsonProperty(PropertyName = "lab_test_")] + public string LabTest { get; internal set; } = string.Empty; + + /// + /// Feature importance + /// + [JsonProperty(PropertyName = "feature_importance")] + public string FeatureImportance { get; internal set; } = _emptyJson; + + /// + /// Performance Metrics + /// + [JsonProperty(PropertyName = "performance_metrics")] + public string PerformanceMetrics { get; internal set; } = _emptyJson; + + /// + /// Result of the probability CV + /// + [JsonProperty(PropertyName = "predicted_prob_cv_")] + public string PredictedProbCV { get; internal set; } = _emptyJson; + + /// + /// Result of the probability test + /// + [JsonProperty(PropertyName = "predicted_prob_test_")] + public string PredictedProbTest { get; internal set; } = _emptyJson; + + /// + /// Result of the target CV + /// + [JsonProperty(PropertyName = "predicted_targets_cv_")] + public string PredictedTargetsCV { get; internal set; } = _emptyJson; + + /// + /// Result of the target test + /// + [JsonProperty(PropertyName = "predicted_targets_test_")] + public string PredictedTargetsTest { get; internal set; } = _emptyJson; + + /// + /// Description of the exploratory data analysis + /// + [JsonProperty(PropertyName = "eda_describe")] + public string ExploratoryDataAnalysisDescribe { get; internal set; } = string.Empty; + + /// + /// Represents an empty TaskResult (not associated with a valid task) + /// + public static TrainingResult Null => new(); + + /// + /// Returns a string that represents the current object + /// + /// A string that represents the current object + public override string ToString() => this == Null ? "" : JsonConvert.SerializeObject(this); +} \ No newline at end of file diff --git a/PredictNowClient.cs b/PredictNowClient.cs index 475c391..cec4b2b 100644 --- a/PredictNowClient.cs +++ b/PredictNowClient.cs @@ -31,13 +31,15 @@ public class PredictNowClient : IDisposable { private readonly HttpClient _client; private readonly string _userId; + private readonly string? _userName; /// /// Creates a new instance of the REST Client for PredictNow CPO /// /// The base URL to PredictNow REST endpoints /// User identification - protected PredictNowClient(string baseUrl, string userId) + /// User Name + protected PredictNowClient(string baseUrl, string userId, string? userName) { if (string.IsNullOrWhiteSpace(baseUrl)) { @@ -50,6 +52,7 @@ protected PredictNowClient(string baseUrl, string userId) } _userId = userId; + _userName = userName; _client = new HttpClient { BaseAddress = new Uri(baseUrl) }; _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); @@ -59,8 +62,9 @@ protected PredictNowClient(string baseUrl, string userId) /// Creates a new instance of REST Client for PredictNow CPO for a given user /// /// User identification + /// User Name /// - public PredictNowClient(string userId) : this(Config.Get("predict-now-url"), userId) { } + public PredictNowClient(string userId, string? userName=null) : this(Config.Get("predict-now-url"), userId, userName) { } /// /// Checks whether we can connect to the endpoint @@ -268,6 +272,112 @@ public PyDict GetLivePredictionWeights(PyObject portfolioParameters, DateTime re return ConvertCSharpDictionaryToPythonDict(weights); } + /// + /// Create the model + /// + /// The name of the model + /// Model parameters + /// The response to this request + public ModelResponse CreateModel(string name, ModelParameters parameters) + { + // TODO: understand the hyp_dict parameter + var value = new { params_ = parameters, model_name = name, username = _userName, hyp_dict = new Dictionary() }; + var requestParameters = JsonConvert.SerializeObject(value).Replace("params_", "params"); + + using var request = new HttpRequestMessage(HttpMethod.Post, "/models") + { + Content = new StringContent(requestParameters, Encoding.UTF8, "application/json") + }; + + TryRequest(request, out var result); + return result; + } + + /// + /// Train the model + /// + /// The name of the model + /// The path to the file with the data to train the model with + /// The label in the data + /// The response to this request + public TrainModelResponse Train(string modelName, string filename, string label) + { + var fileInfo = new FileInfo(filename); + if (!fileInfo.Exists) + { + return new TrainModelResponse { Message = $"{fileInfo.FullName} does not exist" }; + } + + using var stream = File.OpenRead(filename); + + using var request = new HttpRequestMessage(HttpMethod.Post, "/trainings") + { + Content = new MultipartFormDataContent + { + { new StringContent(Guid.NewGuid().ToString()), "train_id" }, + { new StreamContent(stream), "file", "parquet" }, + { new StringContent(modelName), "model_name" }, + { new StringContent(label), "label" }, + { new StringContent(_userId), "email" }, + { new StringContent(_userName), "username" }, + } + }; + + TryRequest(request, out var result); + return result; + } + + /// + /// Get the training results + /// + /// The name of the model + /// The prediction result + public TrainingResult GetTrainingResult(string modelName) + { + var parameters = JsonConvert.SerializeObject(new { model_name = modelName, username = _userName }); + + using var request = new HttpRequestMessage(HttpMethod.Post, "/get_result") + { + Content = new StringContent(parameters, Encoding.UTF8, "application/json") + }; + + return TryRequest(request, out var result) ? result : TrainingResult.Null; + } + + /// + /// Predict with the trained model + /// + /// Name of the model + /// Path to file with the input for the prediction + /// True if the predition should use exploratory data analysis + /// True if the model should refine the probability + /// The prediction result + public PredictResult Predict(string modelName, string filename, bool exploratoryDataAnalysis = false, bool probabilityCalibration = false) + { + var fileInfo = new FileInfo(filename); + if (!fileInfo.Exists) + { + return new PredictResult($"{fileInfo.FullName} does not exist"); + } + + using var stream = File.OpenRead(filename); + + using var request = new HttpRequestMessage(HttpMethod.Post, "/predictions") + { + Content = new MultipartFormDataContent + { + { new StreamContent(stream), "file", "parquet" }, + { new StringContent(modelName), "model_name" }, + { new StringContent(exploratoryDataAnalysis ? "yes" : "no"), "eda" }, + { new StringContent(probabilityCalibration ? "yes" : "no"), "prob_calib" }, + { new StringContent(_userName), "username" } + } + }; + + TryRequest(request, out var result); + return result; + } + /// /// Release unmanaged resource /// diff --git a/PredictNowNET-CAI-Example-Python.ipynb b/PredictNowNET-CAI-Example-Python.ipynb new file mode 100644 index 0000000..10bf18a --- /dev/null +++ b/PredictNowNET-CAI-Example-Python.ipynb @@ -0,0 +1,282 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "68b534ff-5b38-43ee-b6af-259a6eaa2f81", + "metadata": {}, + "source": [ + "## FX Strategy using CAI prediction\n", + "\n", + "This strategy trades the FX rate of USD and EUR. The hypothesis is that USD will rise against the EUR during EUR business hours and fall during the USD business hours. This is called the time of the day effect and seen due to HF OF and returns (https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2099321)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4554e706-aa36-4c15-a113-e9efe9c54686", + "metadata": {}, + "outputs": [], + "source": [ + "from AlgorithmImports import *\n", + "from datetime import datetime, time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29c691de-f4ee-4cde-a39d-f6801207b38d", + "metadata": {}, + "outputs": [], + "source": [ + "from QuantConnect.PredictNowNET import PredictNowClient\n", + "from QuantConnect.PredictNowNET.Models import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d472fbb-175b-49c3-9cdb-97a3b10f04f0", + "metadata": {}, + "outputs": [], + "source": [ + "client = PredictNowClient(\"user_email@home.com\", \"User_Name\");" + ] + }, + { + "cell_type": "markdown", + "id": "c0ee3df4-bac0-4b3a-9901-998f91e98e12", + "metadata": {}, + "source": [ + "### Prepare the data\n", + "In this notebook, we will create a strategy that short EUR.USD when Europe is open and long when Europe is closed and US is open. We will aggregate the daily return of this static strategy that is activate everyday, and use CAI to predict if the strategy is profitable for a given date. We will follow this On and Off signal to create a dynamic strategy and benchmark its performance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "339af376-5b66-4653-84c0-1c66e25b7e00", + "metadata": {}, + "outputs": [], + "source": [ + "# load minute bar data of EURUSD\n", + "qb = QuantBook()\n", + "symbol = qb.add_forex(\"EURUSD\").symbol\n", + "df_price = qb.History(symbol, datetime(2020,1,1), datetime(2021,1,1)).loc[symbol]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "041665eb-2c39-4276-b2bd-6dfc1dbd82ca", + "metadata": {}, + "outputs": [], + "source": [ + "# resample to hourly returns\n", + "minute_returns = df_price[\"close\"].pct_change()\n", + "hourly_returns = (minute_returns + 1).resample('H').prod() - 1\n", + "df_hourly_returns = hourly_returns.to_frame()\n", + "df_hourly_returns['time'] = df_hourly_returns.index.time\n", + "\n", + "# generate buy and sell signals and get strategy returns\n", + "# Sell EUR.USD when Europe is open\n", + "sell_eur = ((df_hourly_returns['time'] > time(3)) & (df_hourly_returns['time'] < time(9)))\n", + "\n", + "# Buy EUR.USD when Europe is closed and US is open\n", + "buy_eur = ((df_hourly_returns['time'] > time(11)) & (df_hourly_returns['time'] < time(15)))\n", + "\n", + "# signals as 1 and -1\n", + "ones = pd.DataFrame(1, index=df_hourly_returns.index, columns=['signals'])\n", + "minus_ones = pd.DataFrame(-1, index=df_hourly_returns.index, columns=['signals'])\n", + "signals = minus_ones.where(sell_eur, ones.where(buy_eur, 0))\n", + "\n", + "# strategy returns\n", + "strategy_returns = df_hourly_returns['close'] * signals['signals']\n", + "strategy_returns = (strategy_returns + 1).resample('D').prod() - 1\n", + "df_strategy_returns = strategy_returns.to_frame().ffill()" + ] + }, + { + "cell_type": "markdown", + "id": "9ff064d5-c498-4905-8a86-7a3c0b82bdff", + "metadata": {}, + "source": [ + "### Save the data\n", + "We will label the data and save it to disk (ObjectStore) with the model name. This file will be uploaded to PredictNow." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1063d6ad-8da6-4394-8b4f-99b962c29c00", + "metadata": {}, + "outputs": [], + "source": [ + "# Define the model name and data lable\n", + "model_name = \"fx-time-of-day\"\n", + "label = \"strategy_ret\"\n", + "\n", + "# Label the data and save it to the object store\n", + "df_strategy_returns = df_strategy_returns.rename(columns={df_strategy_returns.columns.to_list()[0]: \"strategy_ret\"})\n", + "parquet_path = qb.object_store.get_file_path(f'{model_name}.parquet')\n", + "df_strategy_returns.to_parquet(parquet_path)" + ] + }, + { + "cell_type": "markdown", + "id": "7f46a488-b018-4464-af8b-cad8d8454583", + "metadata": {}, + "source": [ + "### Create the Model\n", + "Create the model by sending the parameters to PredictNow" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d67682fb-4e00-47b7-aab1-4f01e7287480", + "metadata": {}, + "outputs": [], + "source": [ + "model_parameters = ModelParameters(\n", + " mode=Mode.TRAIN, \n", + " type=ModelType.CLASSIFICATION, \n", + " feature_selection=FeatureSelection.SHAP, \n", + " analysis=Analysis.SMALL, \n", + " boost=Boost.GBDT, \n", + " testsize=42.0, # testsize < 1 --> ratio, > 1 --> exact # of rows\n", + " timeseries=False,\n", + " probability_calibration=False, # refine your probability\n", + " exploratory_data_analysis=False,\n", + " weights=\"no\") # yes, no, custom)\n", + "\n", + "create_model_result = client.create_model(model_name, model_parameters)\n", + "str(create_model_result)" + ] + }, + { + "cell_type": "markdown", + "id": "1c01b02c-a519-4853-952c-c2ffad85a0d4", + "metadata": {}, + "source": [ + "### Train the Model\n", + "Provide the path to the data, and its label.\n", + "This task may take several minutes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a1e5da3-c3c9-4073-a2af-8fa00c88beb6", + "metadata": {}, + "outputs": [], + "source": [ + "train_request_result = client.train(model_name, parquet_path, label)\n", + "str(train_request_result)" + ] + }, + { + "cell_type": "markdown", + "id": "59e99965-f29a-41e2-a220-5551bccbd831", + "metadata": {}, + "source": [ + "### Get the training result\n", + "We can create dataframes using the training results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f176ff6d-01f6-4fb2-ac3b-f89d80d7c0a9", + "metadata": {}, + "outputs": [], + "source": [ + "training_result = client.get_training_result(model_name)\n", + "str(training_result)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6141ac2-361a-4655-a313-545c7874e499", + "metadata": {}, + "outputs": [], + "source": [ + "from io import StringIO\n", + "\n", + "# predicted probability (float between 0 and 1) for validation/training data set\n", + "# the last column notes the probability that it's a \"1\", i.e. positive return\n", + "predicted_prob_cv = pd.read_json(StringIO(training_result.predicted_prob_cv))\n", + "print(\"predicted_prob_cv\")\n", + "print(predicted_prob_cv)\n", + "\n", + "# predicted probability (float between 0 and 1) for the testing data set\n", + "predicted_prob_test = pd.read_json(StringIO(training_result.predicted_prob_test))\n", + "print(\"predicted_prob_test\")\n", + "print(predicted_prob_test)\n", + "\n", + "# predicted label, 0 or 1, for validation/training data set. Classified as class 1 if probability > 0.5\n", + "predicted_targets_cv = pd.read_json(StringIO(training_result.predicted_targets_cv))\n", + "print(\"predicted_targets_cv\")\n", + "print(predicted_targets_cv)\n", + "\n", + "# predicted label, 0 or 1, for testing data set. Classified as class 1 if probability > 0.5\n", + "predicted_targets_test = pd.read_json(StringIO(training_result.predicted_targets_test))\n", + "print(\"predicted_targets_test\")\n", + "print(predicted_targets_test)\n", + "\n", + "# feature importance score, shows what features are being used in the prediction\n", + "# more helpful when you include your features\n", + "# and only works when you set param['feature_selection'] to SHAP or CMDA\n", + "if training_result.feature_importance:\n", + " feature_importance = pd.read_json(StringIO(training_result.feature_importance))\n", + " print(\"feature_importance\")\n", + " print(feature_importance)\n", + "\n", + "# performance metrics in terms of accuracies and so on\n", + "performance_metrics = pd.read_json(StringIO(training_result.performance_metrics))\n", + "print(\"performance_metrics\")\n", + "print(performance_metrics)" + ] + }, + { + "cell_type": "markdown", + "id": "b9680e12-7f14-46e4-b11c-c5d1bce0aa02", + "metadata": {}, + "source": [ + "### Start predicting with the trained model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c12cc116-2363-4333-a134-119a4e8810de", + "metadata": {}, + "outputs": [], + "source": [ + "predict_result = client.predict(model_name, parquet_path, exploratory_data_analysis=False, probability_calibration=False)\n", + "str(predict_result)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Foundation-Py-Default", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/PredictNowNET.csproj b/PredictNowNET.csproj index 5d65c0f..b929393 100644 --- a/PredictNowNET.csproj +++ b/PredictNowNET.csproj @@ -8,7 +8,7 @@ $(OutputPath)\QuantConnect.PredictNowNET.xml enable enable - 1.0.2 + 1.0.3 diff --git a/Tests/PredictNowClientTests.cs b/Tests/PredictNowClientTests.cs index a526dc1..3f721f6 100644 --- a/Tests/PredictNowClientTests.cs +++ b/Tests/PredictNowClientTests.cs @@ -29,7 +29,8 @@ public void Setup() { Config.Set("predict-now-url", Environment.GetEnvironmentVariable("PREDICTNOW-BASEURL")); var email = Environment.GetEnvironmentVariable("PREDICTNOW-USER-EMAIL"); - _client = new PredictNowClient(email); + var userName = Environment.GetEnvironmentVariable("PREDICTNOW-USER-NAME"); + _client = new PredictNowClient(email, userName); _portfolioParameters = new PortfolioParameters("Demo_Project_20231211", "ETF_return.csv", "ETF_constrain.csv", 1.0, "month", 1, "first", 3, "sharpe"); } @@ -147,6 +148,34 @@ public void GetLivePreditionWeightsSuccessfully() Assert.That(weightsByDate, Is.Not.Empty); } + + [Test, Order(9)] + public void CreateModelSuccessfully() + { + var modelParameters = new ModelParameters(Mode.Train, ModelType.Classification, FeatureSelection.Shap, Analysis.Small, Boost.Gbdt, 42.0, false, false, false, "no"); + var message = _client.CreateModel("DemoModel", modelParameters); + } + + [Test, Order(10)] + public void TrainModelSuccessfully() + { + var filename = Path.Combine(Directory.GetCurrentDirectory(), "Data", "ETF_return.csv"); + var message = _client.Train("DemoModel", filename, "SPY"); + } + + [Test, Order(10)] + public void GetTrainingResultSuccessfully() + { + var result = _client.GetTrainingResult("DemoModel"); + } + + [Test, Order(11)] + public void PredictSuccessfully() + { + var filename = Path.Combine(Directory.GetCurrentDirectory(), "Data", "ETF_return.csv"); + var result = _client.Predict("DemoModel", filename); + } + private void GetJobForId(string jobId) { var maxRetries = 5;