diff --git a/WebServer/AIPython/ai-predictor.py b/WebServer/AIPython/aipredictor.py
similarity index 88%
rename from WebServer/AIPython/ai-predictor.py
rename to WebServer/AIPython/aipredictor.py
index 9d817ca9..1bc36a7b 100644
--- a/WebServer/AIPython/ai-predictor.py
+++ b/WebServer/AIPython/aipredictor.py
@@ -1,4 +1,5 @@
import os
+import sys
import joblib
import numpy as np
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
@@ -7,7 +8,11 @@ import features
import matplotlib
matplotlib.use("Agg")
-def Predict(Symbol):
+def Predict():
+
+ # Get the Symbol from ARGV
+ Symbol = sys.argv[1]
+
# Define paths (consistent with your previous script)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(SCRIPT_DIR, "data")
@@ -21,8 +26,6 @@ def Predict(Symbol):
# Make the feature set
df = features.MakeFeatures(df)
- print(Symbol)
-
# Drop our predictor
df.drop('Target_Close', axis=1, inplace=True)
@@ -51,8 +54,7 @@ def Predict(Symbol):
# 'predictions' will be a 2D array, flatten it if you want a simple list
flat_predictions = actual_prediction.flatten().tolist()
- print(f"Predicted Target_Close: {flat_predictions}")
-
+ # Set the movement indicator
movement_indicator = 0
if (np.mean(flat_predictions) > 0.01):
movement_indicator = 1
@@ -61,7 +63,10 @@ def Predict(Symbol):
else:
movement_indicator = 0
- return movement_indicator
+ # Return to C# via stdout
+ print(f"---RESULT_START---")
+ print(movement_indicator)
+ print(f"---RESULT_END---")
if __name__ == "__main__":
- Predict("AAPL")
\ No newline at end of file
+ Predict()
\ No newline at end of file
diff --git a/WebServer/AIPython/ai-trainer.py b/WebServer/AIPython/aitrainer.py
similarity index 86%
rename from WebServer/AIPython/ai-trainer.py
rename to WebServer/AIPython/aitrainer.py
index 2177374a..717a433e 100644
--- a/WebServer/AIPython/ai-trainer.py
+++ b/WebServer/AIPython/aitrainer.py
@@ -75,12 +75,9 @@ def TrainAI():
test_results = dnn_model.evaluate(
test_features, test_labels, verbose=0
)
- print(f"Test Results: {test_results}")
# Save the model
dnn_model.save(os.path.join(DATA_DIR, "model.keras"))
if __name__ == "__main__":
- TrainAI()
-
-# Last train Predicted Target_Close: [0.0022113274317234755, 0.0021446370519697666, 0.0022628342267125845, 0.002175702480599284, 0.0021452796645462513, 0.0020838389173150063, 0.0017336219316348433, 0.002210840117186308, 0.0021144403144717216, 0.0021278387866914272, 0.0021266420371830463, 0.002261851681396365, 0.002108299173414707, 0.002121902070939541, 0.0022294146474450827]
\ No newline at end of file
+ TrainAI()
\ No newline at end of file
diff --git a/WebServer/AIPython/currentprice.py b/WebServer/AIPython/currentprice.py
index 8a50b48d..c2814b17 100644
--- a/WebServer/AIPython/currentprice.py
+++ b/WebServer/AIPython/currentprice.py
@@ -1,7 +1,19 @@
+import sys
import yfinance as yf
-def getCurrentPrice(symbol):
+def getCurrentPrice():
+
+ # Get the Symbol from ARGV
+ symbol = sys.argv[1]
+
ticker = yf.Ticker(symbol)
data = ticker.history(period="1d", interval="1m")
current_price = data['Close'].iloc[-1]
- return current_price
\ No newline at end of file
+
+ # Return to C# via stdout
+ print(f"---RESULT_START---")
+ print(current_price)
+ print(f"---RESULT_END---")
+
+if __name__ == "__main__":
+ getCurrentPrice()
\ No newline at end of file
diff --git a/WebServer/AIPython/datapuller.py b/WebServer/AIPython/datapuller.py
index 97a92a45..882ea3c9 100644
--- a/WebServer/AIPython/datapuller.py
+++ b/WebServer/AIPython/datapuller.py
@@ -15,7 +15,6 @@ def pull():
# Scrape the data
all_data = []
for i, symbol in enumerate(tickers):
- print(f"Processing: {i} of {len(tickers)}")
df = yf.download(symbol, period="max", auto_adjust=True)
if not df.empty:
# Remove the ticker column
@@ -24,10 +23,11 @@ def pull():
all_data.append(df)
# Concatinate into a combined list and cache
- print("Processing data")
final_df = pd.concat(all_data)
# Save to file
- print("Writing data to file")
final_df.to_parquet(os.path.join(DATA_DIR, "stocks.parquet"))
- final_df.head(200).to_csv(os.path.join(DATA_DIR, "stocks.preview.csv"))
\ No newline at end of file
+ final_df.head(200).to_csv(os.path.join(DATA_DIR, "stocks.preview.csv"))
+
+if __name__ == "__main__":
+ pull()
\ No newline at end of file
diff --git a/WebServer/Controllers/ProcessCreator.cs b/WebServer/Controllers/ProcessCreator.cs
new file mode 100644
index 00000000..d971a787
--- /dev/null
+++ b/WebServer/Controllers/ProcessCreator.cs
@@ -0,0 +1,57 @@
+using System.Diagnostics;
+
+namespace Controllers.Processes {
+
+ public class PyProcess {
+
+ public static (bool, string) RunPythonProcess(string PyExecutable, string PyScript, bool returns = false, string PyArgs = "") {
+ var start = new ProcessStartInfo {
+ FileName = PyExecutable,
+ Arguments = $"-u {PyScript} {PyArgs}",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true
+ };
+
+ using (Process? process = Process.Start(start)) {
+
+ // Try to start the process
+ if (process == null) {
+ return (false, "Failed to start the process");
+ }
+
+ // Read the stdouts and wait for process to end
+ string result = process.StandardOutput.ReadToEnd();
+ string errors = process.StandardError.ReadToEnd();
+ process.WaitForExit();
+
+ // If the process Errored
+ if (process.ExitCode != 0) {
+ return (false, $"Python Error : {errors}");
+ }
+
+ // If the process is supposed to return
+ if (returns) {
+ string markerStart = "---RESULT_START---";
+ string markerEnd = "---RESULT_END---";
+
+ int startPos = result.IndexOf(markerStart) + markerStart.Length;
+ int endPos = result.IndexOf(markerEnd);
+
+ if (startPos > -1 && endPos > startPos) {
+ string cleanResult = result.Substring(startPos, endPos - startPos).Trim();
+ return (true, cleanResult);
+ }
+ } else {
+ return (true, "");
+ }
+
+ // Fail Safe
+ return (false, "Result not found");
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/WebServer/Controllers/PythonInterop.cs b/WebServer/Controllers/PythonInterop.cs
index 5dbc4e23..26935936 100644
--- a/WebServer/Controllers/PythonInterop.cs
+++ b/WebServer/Controllers/PythonInterop.cs
@@ -1,65 +1,55 @@
-using Python.Runtime;
+using Controllers.Processes;
namespace Controllers.PythonInterop {
public class AIModule {
- public AIModule(string PythonPathBase = "/usr/local/", string PythonVersion = "python3.11") {
- // Use the user provided python runner
- Runtime.PythonDLL = PythonPathBase + $"lib/lib{PythonVersion}.so";
+ string _PyPath = "";
+ string _ExecPath = "";
- // Use our local environment for the python libraries
- PythonEngine.PythonHome = PythonPathBase;
-
- // Include all the paths for python packages most importantly our venv
- PythonEngine.PythonPath = $"{PythonPathBase}lib/{PythonVersion}:{PythonPathBase}lib/{PythonVersion}/lib-dynload:{PythonPathBase}lib/{PythonVersion}/site-packages:{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AIPython")}";
-
- // Initiilize python
- PythonEngine.Initialize();
-
- // Needed because C# calls the python from each connections worker thread
- PythonEngine.BeginAllowThreads();
+ public AIModule() {
+ _PyPath = "/usr/bin/python3.11";
+ _ExecPath = $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AIPython")}";
}
public void PullAI() {
- using (Py.GIL()) {
- dynamic datapuller = Py.Import("datapuller");
- using (datapuller.pull()){ }
+ (bool, string) Success = PyProcess.RunPythonProcess(_PyPath, _ExecPath + "/datapuller.py");
+ if (!Success.Item1) {
+ Console.WriteLine(Success.Item2);
}
}
public void TrainAI() {
- using (Py.GIL()) {
- dynamic trainer = Py.Import("ai-trainer");
- using (trainer.TrainAI()){ }
+ (bool, string) Success = PyProcess.RunPythonProcess(_PyPath, _ExecPath + "/aitrainer.py");
+ if (!Success.Item1) {
+ Console.WriteLine(Success.Item2);
}
}
// Return ( Error, Signal )
public (string, int) PredictAI(string StockSymbol) {
- try {
- using (Py.GIL()) {
- dynamic predictor = Py.Import("ai-predictor");
- using (dynamic x = predictor.Predict(StockSymbol)) {
- int result = (int)x;
- return ("", result);
- }
+ (bool, string) Success = PyProcess.RunPythonProcess(_PyPath, _ExecPath + "/aipredictor.py", returns: true, PyArgs: StockSymbol);
+ if (!Success.Item1) {
+ return (Success.Item2, 0);
+ } else {
+ if (int.TryParse(Success.Item2, out int parsed)) {
+ return ("", parsed);
}
- } catch (Exception ex) {
- return (ex.ToString(), 0);
+ return ("Python returns an unknown value", 0);
}
}
public float GetCurrentPrice(string StockSymbol) {
- using (Py.GIL()) {
- dynamic price = Py.Import("currentprice");
- using (dynamic x = price.getCurrentPrice(StockSymbol)) {
- float CurrentPrice = (float)x;
- return x;
+ (bool, string) Success = PyProcess.RunPythonProcess(_PyPath, _ExecPath + "/currentprice.py", returns: true, PyArgs: StockSymbol);
+ if (!Success.Item1) {
+ return 0;
+ } else {
+ if (float.TryParse(Success.Item2, out float parsed)) {
+ return parsed;
}
+ return 0;
}
}
}
-
}
\ No newline at end of file
diff --git a/WebServer/Program.cs b/WebServer/Program.cs
index 5227cbb4..6f8aa1b4 100644
--- a/WebServer/Program.cs
+++ b/WebServer/Program.cs
@@ -4,15 +4,29 @@ using Controllers.DataBase;
using Controllers.Payment;
// Load the module in globally and use correct path for local or docker runners
-#if DEBUG
- AIModule interopModule = new AIModule(PythonPathBase: "/usr/", PythonVersion: "python3.11");
-#else
- AIModule interopModule = new AIModule();
-#endif
+AIModule interopModule = new AIModule();
-if (args.Contains("Pull-Stock-Data")) {
+if (args.Contains("Retrain-AI")) {
+ // This runs once per month on the first -> set by the crontab.txt
+ interopModule.PullAI();
interopModule.TrainAI();
+} else if (args.Contains("Perform-AI")) {
+ // This runs every hour that the stock market is open -> set by the crontab.txt
+
+ // Get all current holdings for Stocks
+ // Perform the AI Signal per stock
+ // Perform action in background for each stock
} else {
+ // This runs when the server is started normally
+
+ // Make sure the data is ready before first run
+ string firstPullRan = (new DbDriver()).Get("FirstPull");
+ if (firstPullRan != "1") {
+ interopModule.PullAI();
+ interopModule.TrainAI();
+ (new DbDriver()).Set("FirstPull", "1");
+ }
+
// Create the webapp
var builder = WebApplication.CreateBuilder(args);
diff --git a/WebServer/WebServer.csproj b/WebServer/WebServer.csproj
index 879f2943..42ceb667 100644
--- a/WebServer/WebServer.csproj
+++ b/WebServer/WebServer.csproj
@@ -10,7 +10,6 @@
-