From d9fd9b25816aff0b2ae8aa2a49ff773e3edd130e Mon Sep 17 00:00:00 2001 From: Derek Holloway Date: Tue, 10 Mar 2026 20:20:42 -0700 Subject: [PATCH] Start pulling AI out of the UI and into the background service --- WebServer/Components/Pages/Home.razor | 330 ++++++++------------------ WebServer/Controllers/Automation.cs | 145 +++++++++++ WebServer/Entities/LoginSession.cs | 3 +- WebServer/Entities/PurchasedStock.cs | 5 +- WebServer/Entities/Stock.cs | 8 + 5 files changed, 252 insertions(+), 239 deletions(-) create mode 100644 WebServer/Controllers/Automation.cs create mode 100644 WebServer/Entities/Stock.cs diff --git a/WebServer/Components/Pages/Home.razor b/WebServer/Components/Pages/Home.razor index e5f72b2b..def39b53 100644 --- a/WebServer/Components/Pages/Home.razor +++ b/WebServer/Components/Pages/Home.razor @@ -1,5 +1,6 @@ @page "/" @using Controllers.Payment +@using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments @rendermode InteractiveServer Home @@ -36,28 +37,14 @@ Money: $@Session.Money +

Actions

- @if (Debounce){ - - - }else{ - - - } -
- @if (Debounce){ - - - - } else { - - - - } + +
@resultError
@@ -66,36 +53,34 @@ @if (Session != null){ -
-
-

Current Signal

-
- @foreach (PurchasedStock cur in Session.TrackedStocks){ -
-
-

@cur.Symbol

-

Purchased Price: @cur.PurchasePrice

-

Purchased Quantity: @cur.Quantity

-

Purchased Date: @cur.PurchaseDate.ToString("M-dd-yyyy")

- @if (Debounce){ - - }else{ - - } -
-
- -> -
- @if (cur.PredictedMovement == -1){ -

Sell

- } else if (cur.PredictedMovement == 1){ -

Buy

- } else{ -

Hold

- } - +
+ +
+
+

Current Signal

- } + @foreach (Stock cur in Session.TrackedStocks){ +
+

@cur.Symbol

+

AI Predicted Score: @cur.Score

+ +
+ } +
+ +
+
+

Trade History

+
+ @foreach (PurchasedStock cur in Session.TradeHistory){ +
+

@cur.Symbol

+

Purchased Quantity: @cur.Quantity

+

Purchased Price: @cur.PurchasePrice

+

Sell Price: @cur.PurchasePrice

+
+ } +
} @@ -108,16 +93,8 @@ ///////////////////////////////////////////////////////////////////////////////////////////// loginSession? Session = null; - - // On Page Load - string PaymentKey = ""; - protected override async Task OnInitializedAsync(){ - - } - - - - + string resultError = ""; + @@ -126,213 +103,96 @@ string passWord = ""; string loginError = ""; async Task LoginSession(){ - string dbPrefix = $"[{userName.ToLower()}]:"; // Set the DB prefix for the get and set - string passwordhash = dbDriver.Get( dbPrefix + "password" ); // Pull the password hash + string dbPrefix = $"[{userName.ToLower()}]:"; + + // Check if user already exists + string passwordhash = dbDriver.Get( dbPrefix + "password" ); if (string.IsNullOrEmpty(passwordhash)){ loginError = "no account found with that username"; return; } - if (BCrypt.Verify( passWord, passwordhash )){ // If the password is valid - List? stocks = JsonConvert.DeserializeObject>( dbDriver.Get( dbPrefix + "Stocks" ) ); - bool moneyLoaded = float.TryParse(dbDriver.Get( dbPrefix + "money" ), out float moneyResult); - Session = new loginSession(){ - UserName = userName.ToLower(), - TrackedStocks = stocks != null ? stocks : new List(), - Money = moneyLoaded ? moneyResult : 1000 - }; - (bool, string) result = PaymentProcessor.CreatePayment(Session.UserName); - if (!result.Item1){ - resultError = result.Item2; - } - PaymentKey = result.Item2; - }else{ + + // Check the password is valid + if (!BCrypt.Verify( passWord, passwordhash )){ loginError = "wrong password"; + return; } + + // Load the users account + List? history = JsonConvert.DeserializeObject>( dbDriver.Get( dbPrefix + "history" ) ); + List? stocks = JsonConvert.DeserializeObject>( dbDriver.Get( dbPrefix + "watched" ) ); + bool moneyLoaded = float.TryParse(dbDriver.Get( dbPrefix + "money" ), out float moneyResult); + Session = new loginSession(){ + UserName = userName.ToLower(), + TradeHistory = history != null ? history : new List(), + TrackedStocks = stocks != null ? stocks : new List(), + Money = moneyLoaded ? moneyResult : 1000 + }; } async Task registerSession(){ string dbPrefix = $"[{userName.ToLower()}]:"; + + // Check if user already exists string passwordhash = dbDriver.Get( dbPrefix + "password" ); - if (string.IsNullOrEmpty(passwordhash)){ - dbDriver.Set( dbPrefix + "password", BCrypt.HashPassword( passWord, BCrypt.GenerateSalt() ) ); - dbDriver.Set( dbPrefix + "money", "1000" ); - Session = new loginSession(){ - UserName = userName.ToLower(), - TrackedStocks = new List(), - Money = 1000 - }; - (bool, string) result = PaymentProcessor.CreatePayment(Session.UserName); - if (!result.Item1){ - resultError = result.Item2; - } - PaymentKey = result.Item2; - }else{ + if (!string.IsNullOrEmpty(passwordhash)){ loginError = "account is taken"; + return; } + + // Create User Object on the database + dbDriver.Set( dbPrefix + "password", BCrypt.HashPassword( passWord, BCrypt.GenerateSalt() ) ); + dbDriver.Set( dbPrefix + "money", "1000" ); + Session = new loginSession(){ + UserName = userName.ToLower(), + TradeHistory = new List(), + TrackedStocks = new List(), + Money = 1000 + }; + + // Add the users to a global table for automation + List? Users = JsonConvert.DeserializeObject>( dbDriver.Get( "Users" ) ); + List verifiedUsers = Users != null ? Users : new List(); + verifiedUsers.Add(userName.ToLower()); + dbDriver.Set( "Users", JsonConvert.SerializeObject(verifiedUsers) ); } + string addStockSymbol = ""; + async Task addStock(){ - - - // AI Stuff - - string trainButtonText = "Force Retrain AI"; - string predictButtonText = "Predict AI"; - string resultError = ""; - bool Debounce = true; - - async Task train(){ - resultError = ""; - if (Debounce){ - Debounce = false; - trainButtonText = "Do not refresh the page. The AI is training."; - await Task.Delay(1); - Task thread = new Task(async () => { - aiModule.TrainAI(); - trainButtonText = "AI Trained"; - await Task.Delay(2000); - trainButtonText = "Train AI"; - await Task.Delay(1); - Debounce = true; - }); - thread.Start(); + // Make sure a session exists + if (Session == null){ + return; } - } - async Task predict(){ - resultError = ""; - if (Debounce && Session != null){ - Debounce = false; - predictButtonText = "Do not refresh the page. The AI is predicting"; - await Task.Delay(1); - List threadpool = new List(); - foreach(PurchasedStock cur in Session.TrackedStocks){ - Task thread = new Task(() => { - (string, int)Result = aiModule.PredictAI(cur.Symbol); - if (string.IsNullOrEmpty(Result.Item1)){ - cur.PredictedMovement = Result.Item2; - }else{ - resultError = Result.Item1; - } - Console.WriteLine("Received Signal [" + cur.Symbol + "] : " + cur.PredictedMovement); - }); - thread.Start(); - threadpool.Add(thread); - } - await Task.WhenAll(threadpool); - predictButtonText = "Predictions loaded"; - await Task.Delay(2000); - predictButtonText = "Predict AI"; - Debounce = true; - await Task.Delay(1); - } + // Add a tracked stock to the users account + Session.TrackedStocks.Add(new Stock(){ + Symbol = addStockSymbol.ToUpper() + }); + + // Save the users account + string dbPrefix = $"[{userName.ToLower()}]:"; + dbDriver.Set(dbPrefix + "watched", JsonConvert.SerializeObject(Session.TrackedStocks) ); } + async Task removeStock(Stock stock){ - - - // Stock Manipulation - - string buyStockSymbol = ""; - string buyStockQuantity = ""; - async Task buyStock(){ - if (Debounce && Session != null){ - Debounce = false; - await Task.Delay(1); - - string dbPrefix = $"[{userName.ToLower()}]:"; - // Try Parse the quantitiy input - bool success = float.TryParse(buyStockQuantity, out float QuantityResult); - if (!success){ - resultError = "Quantity field is not a number"; - Debounce = true; - await Task.Delay(1); - return; - } - - // Get Stock Price - float stockPrice = aiModule.GetCurrentPrice( buyStockSymbol ); - - // Try Pay for the stock - (bool, string) result = PaymentProcessor.TryPayment(PaymentKey, QuantityResult * stockPrice); - if (!result.Item1){ - resultError = result.Item2; - Debounce = true; - await Task.Delay(1); - return; - } - - // Add the stock - Session.TrackedStocks.Add( new PurchasedStock(){ - Symbol = buyStockSymbol.ToUpper(), - PurchasePrice = stockPrice, - Quantity = QuantityResult, - PurchaseDate = DateTime.Now - } ); - dbDriver.Set( dbPrefix + "Stocks", Newtonsoft.Json.JsonConvert.SerializeObject(Session.TrackedStocks) ); - - // Reload the users money - bool moneyLoaded = float.TryParse(dbDriver.Get( dbPrefix + "money" ), out float moneyResult); - Session.Money = moneyLoaded ? moneyResult : 1000; - - // Reset the Impodent Key - result = PaymentProcessor.CreatePayment(Session.UserName); - if (!result.Item1){ - resultError = "[New payment session failed] : " + result.Item2; - Debounce = true; - await Task.Delay(1); - return; - } - PaymentKey = result.Item2; - Debounce = true; - await Task.Delay(1); + // Make sure a session exists + if (Session == null){ + return; } - } + + // Remove the stock + Session.TrackedStocks.Remove(stock); - async Task sellStock(PurchasedStock stock){ - string dbPrefix = $"[{userName.ToLower()}]:"; - if (Debounce && Session != null){ - Debounce = false; - await Task.Delay(1); - - // Get sell price - float sellPrice = stock.Quantity * aiModule.GetCurrentPrice( stock.Symbol ); - - // Try to sell the stock - (bool, string) result = PaymentProcessor.TrySell(PaymentKey, sellPrice); - if (!result.Item1){ - resultError = result.Item2; - Debounce = true; - await Task.Delay(1); - return; - } - - // Remove Stock - Session.TrackedStocks.Remove(stock); - dbDriver.Set( dbPrefix + "Stocks", Newtonsoft.Json.JsonConvert.SerializeObject(Session.TrackedStocks) ); - - // Reload the users money - bool moneyLoaded = float.TryParse(dbDriver.Get( dbPrefix + "money" ), out float moneyResult); - Session.Money = moneyLoaded ? moneyResult : 1000; - - // Reset the Impodent Key - result = PaymentProcessor.CreatePayment(Session.UserName); - if (!result.Item1){ - resultError = "[New payment session failed] : " + result.Item2; - Debounce = true; - await Task.Delay(1); - return; - } - PaymentKey = result.Item2; - - Debounce = true; - await Task.Delay(1); - } + // Save the users account + string dbPrefix = $"[{userName.ToLower()}]:"; + dbDriver.Set(dbPrefix + "watched", JsonConvert.SerializeObject(Session.TrackedStocks) ); } } \ No newline at end of file diff --git a/WebServer/Controllers/Automation.cs b/WebServer/Controllers/Automation.cs new file mode 100644 index 00000000..85a18f8e --- /dev/null +++ b/WebServer/Controllers/Automation.cs @@ -0,0 +1,145 @@ +using Controllers.DataBase; +using Entities; +using Newtonsoft.Json; + +namespace Controllers.PythonInterop { + + public class Automation { + + AIModule _aiModule; + DbDriver _dbDriver; + public Automation(AIModule aiModule, DbDriver dbDriver) { + _aiModule = aiModule; + _dbDriver = dbDriver; + } + + public void GlobalPredictAI() { + + // Start this process on a background thread so its non-blocking + Task thread = new Task(async () => { + + // Load the userlist + List? UserList = JsonConvert.DeserializeObject>(_dbDriver.Get("Users")); + List VerifiedUserList = UserList != null ? UserList : new List(); + + // Process each request at the same time for speed improvement + Parallel.ForEach(VerifiedUserList, async (username) => { + string dbPrefix = $"[{username.ToLower()}]:"; + + // Load the Tracked stocks for each user + List? TrackedStocks = JsonConvert.DeserializeObject>( _dbDriver.Get( dbPrefix + "watched" ) ); + List VerifiedTrackedStocks = TrackedStocks != null ? TrackedStocks : new List(); + + // Go through each stock + List threadpool = new List(); + foreach(Stock cur in VerifiedTrackedStocks) { + + // Predict the trend on a new thread + Task thread = new Task(() => { + (string, float)Result = _aiModule.PredictAI(cur.Symbol); + + // If error log it + if (!string.IsNullOrEmpty(Result.Item1)){ + Console.WriteLine(Result.Item1); + } + + // Write the score to the users tracked stocks + cur.Score = Result.Item2; + }); + thread.Start(); + threadpool.Add(thread); + } + + // Wait for all the threads to finish + await Task.WhenAll(threadpool); + + // Process Stocks -> Buy Sell Hold / Based on the global data from earlier + + // Save the new scores to the database for reference from the UI + _dbDriver.Set( dbPrefix + "watched", JsonConvert.SerializeObject( VerifiedTrackedStocks ) ); + + }); + }); + thread.Start(); + } + + public void GlobalTrainAI(){ + Task thread = new Task(async () => { + _aiModule.TrainAI(); + }); + thread.Start(); + } + + void sellStock(PurchasedStock stock){ + string dbPrefix = $"[{userName.ToLower()}]:"; + if (Session != null){ + + // Get sell price + float sellPrice = stock.Quantity * aiModule.GetCurrentPrice( stock.Symbol ); + + // Try to sell the stock + (bool, string) result = PaymentProcessor.TrySell(PaymentKey, sellPrice); + if (!result.Item1){ + resultError = result.Item2; + return; + } + + // Remove Stock + Session.TradeHistory.Remove(stock); + dbDriver.Set( dbPrefix + "Stocks", Newtonsoft.Json.JsonConvert.SerializeObject(Session.TradeHistory) ); + + // Reload the users money + bool moneyLoaded = float.TryParse(dbDriver.Get( dbPrefix + "money" ), out float moneyResult); + Session.Money = moneyLoaded ? moneyResult : 1000; + + // Reset the Impodent Key + result = PaymentProcessor.CreatePayment(Session.UserName); + if (!result.Item1){ + resultError = "[New payment session failed] : " + result.Item2; + return; + } + PaymentKey = result.Item2; + } + } + + void buyStock(string StockSymbol){ + if (Session != null){ + string dbPrefix = $"[{userName.ToLower()}]:"; + + // Try Parse the quantitiy input + // Get the max quantity that I can Afford + float QuantityResult = 0; + + // Get Stock Price + float stockPrice = aiModule.GetCurrentPrice( StockSymbol ); + + // Try Pay for the stock + (bool, string) result = PaymentProcessor.TryPayment(PaymentKey, QuantityResult * stockPrice); + if (!result.Item1){ + resultError = result.Item2; + return; + } + + // Add the stock + Session.TradeHistory.Add( new PurchasedStock(){ + Symbol = StockSymbol.ToUpper(), + PurchasePrice = stockPrice, + Quantity = QuantityResult, + } ); + dbDriver.Set( dbPrefix + "Stocks", Newtonsoft.Json.JsonConvert.SerializeObject(Session.TradeHistory) ); + + // Reload the users money + bool moneyLoaded = float.TryParse(dbDriver.Get( dbPrefix + "money" ), out float moneyResult); + Session.Money = moneyLoaded ? moneyResult : 1000; + + // Reset the Impodent Key + result = PaymentProcessor.CreatePayment(Session.UserName); + if (!result.Item1){ + resultError = "[New payment session failed] : " + result.Item2; + return; + } + PaymentKey = result.Item2; + } + } + } +} \ No newline at end of file diff --git a/WebServer/Entities/LoginSession.cs b/WebServer/Entities/LoginSession.cs index ce4d1dc7..a384c1f0 100644 --- a/WebServer/Entities/LoginSession.cs +++ b/WebServer/Entities/LoginSession.cs @@ -3,7 +3,8 @@ namespace Entities { class loginSession { public string UserName { get; set; } = ""; public float Money { get; set; } = 0; - public List TrackedStocks { get; set; } = new List(); + public List TrackedStocks { get; set; } = new List(); + public List TradeHistory { get; set; } = new List(); } } \ No newline at end of file diff --git a/WebServer/Entities/PurchasedStock.cs b/WebServer/Entities/PurchasedStock.cs index 7936aa19..1cfd5046 100644 --- a/WebServer/Entities/PurchasedStock.cs +++ b/WebServer/Entities/PurchasedStock.cs @@ -2,10 +2,9 @@ namespace Entities { class PurchasedStock { public string Symbol { get; set; } = ""; - public float PurchasePrice { get; set; } = 0; public float Quantity { get; set; } = 0; - public DateTime PurchaseDate { get; set; } = DateTime.Now; - public int PredictedMovement { get; set; } = 0; + public float PurchasePrice { get; set; } = 0; + public float SellPrice { get; set; } = 0; } } \ No newline at end of file diff --git a/WebServer/Entities/Stock.cs b/WebServer/Entities/Stock.cs new file mode 100644 index 00000000..077ef013 --- /dev/null +++ b/WebServer/Entities/Stock.cs @@ -0,0 +1,8 @@ +namespace Entities { + + class Stock { + public string Symbol { get; set; } = ""; + public float Score { get; set; } = 0; + } + +} \ No newline at end of file