From e1841325324469c982340ba2cb4382b54d06354e Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Thu, 24 Jul 2025 21:32:30 +0200 Subject: [PATCH 01/34] Basic setup (trader and test project) --- .gitignore | 1 + Bots/YurritBot.sln | 31 +++++++++ Bots/YurritBot/BuyCalculator.cs | 18 +++++ Bots/YurritBot/YurritBot.csproj | 23 ++++++ Bots/YurritBot/YurritTrader.cs | 85 +++++++++++++++++++++++ Bots/YurritBotTests/BuyCalculatorTests.cs | 23 ++++++ Bots/YurritBotTests/YurritBotTests.csproj | 26 +++++++ 7 files changed, 207 insertions(+) create mode 100644 Bots/YurritBot.sln create mode 100644 Bots/YurritBot/BuyCalculator.cs create mode 100644 Bots/YurritBot/YurritBot.csproj create mode 100644 Bots/YurritBot/YurritTrader.cs create mode 100644 Bots/YurritBotTests/BuyCalculatorTests.cs create mode 100644 Bots/YurritBotTests/YurritBotTests.csproj diff --git a/.gitignore b/.gitignore index c4c704b..0519732 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,4 @@ $RECYCLE.BIN/ .DS_Store _NCrunch* +/Bots/.vs diff --git a/Bots/YurritBot.sln b/Bots/YurritBot.sln new file mode 100644 index 0000000..e792e6e --- /dev/null +++ b/Bots/YurritBot.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35825.156 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YurritBot", "YurritBot\YurritBot.csproj", "{72ECAA64-CEB3-800C-225A-4B492A06F254}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YurritBotTests", "YurritBotTests\YurritBotTests.csproj", "{A4420937-8102-4D25-ACB3-015176DEC9A9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {72ECAA64-CEB3-800C-225A-4B492A06F254}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72ECAA64-CEB3-800C-225A-4B492A06F254}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72ECAA64-CEB3-800C-225A-4B492A06F254}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72ECAA64-CEB3-800C-225A-4B492A06F254}.Release|Any CPU.Build.0 = Release|Any CPU + {A4420937-8102-4D25-ACB3-015176DEC9A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4420937-8102-4D25-ACB3-015176DEC9A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4420937-8102-4D25-ACB3-015176DEC9A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4420937-8102-4D25-ACB3-015176DEC9A9}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {71C45625-EE71-430A-9351-9B00F0B29110} + EndGlobalSection +EndGlobal diff --git a/Bots/YurritBot/BuyCalculator.cs b/Bots/YurritBot/BuyCalculator.cs new file mode 100644 index 0000000..0366aef --- /dev/null +++ b/Bots/YurritBot/BuyCalculator.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace YurritBot; + +public class BuyCalculator +{ + private const int maximumBuyAmountPerStock = 1000; + + public int CalculateMaximuumBuyAmount(decimal currentCash, decimal listingPrice) + { + decimal maximumBuyAmountWithCurrentCash = currentCash / listingPrice; + return (int)Math.Floor(Math.Min(maximumBuyAmountPerStock, maximumBuyAmountWithCurrentCash)); + } +} diff --git a/Bots/YurritBot/YurritBot.csproj b/Bots/YurritBot/YurritBot.csproj new file mode 100644 index 0000000..dda88a4 --- /dev/null +++ b/Bots/YurritBot/YurritBot.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + + + ..\..\Build\Bots + false + + + ..\..\Build\Bots + false + + + + ..\..\Build\NasdaqTrader.Bot.Core.dll + + + + diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs new file mode 100644 index 0000000..08a9b24 --- /dev/null +++ b/Bots/YurritBot/YurritTrader.cs @@ -0,0 +1,85 @@ +using NasdaqTrader.Bot.Core; + +namespace YurritBot; + +public class YurritBot : ITraderBot +{ + + private const int cashLowerLimit = 10; + + public string CompanyName => "Stock Out Like a Sore Thumb"; + + public async Task DoTurn(ITraderSystemContext systemContext) + { + LogToFile($"{systemContext.CurrentDate}"); + + int indexToday = DetermineTodayIndex(systemContext); + int indexYesterday = indexToday - 1; + int indexTomorrow = indexToday + 1; + + ExecuteSellStrategy(systemContext, indexToday, indexTomorrow); + ExecuteBuyStrategy(systemContext, indexToday, indexTomorrow); + } + + private void ExecuteSellStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow) + { + var sellableHoldings = systemContext.GetHoldings(this).Where(holding + => (holding.Listing.PricePoints[indexTomorrow].Price - holding.Listing.PricePoints[indexToday].Price) < 0); + + foreach (var holding in sellableHoldings) + { + var success = systemContext.SellStock(this, holding.Listing, holding.Amount); + + if (!success) + { + LogToFile($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[indexToday].Price} | Amount {holding.Amount}"); + } + } + } + + private void ExecuteBuyStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow) + { + var listingsByExpectedIncrease = systemContext.GetListings().OrderBy(listing + => (listing.PricePoints[indexTomorrow].Price - listing.PricePoints[indexToday].Price) / listing.PricePoints[indexToday].Price); + + var buyCalculator = new BuyCalculator(); + + foreach (var listing in listingsByExpectedIncrease) + { + var currentCash = systemContext.GetCurrentCash(this); + if (currentCash < cashLowerLimit + || systemContext.GetTradesLeftForToday(this) <= 0) + { + return; + } + + // TODO find more efficient way to spot this situation upfront + var pricePointToday = listing.PricePoints[indexToday].Price; + if (currentCash < pricePointToday) + { + continue; + } + + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, pricePointToday); + var success = systemContext.BuyStock(this, listing, maxBuyAmount); + if (!success) + { + LogToFile($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); + } + } + } + + private static void LogToFile(string text) + { + var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); + File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); + } + + private static int DetermineTodayIndex(ITraderSystemContext systemContext) + { + return (int)(systemContext.CurrentDate.ToDateTime(TimeOnly.MinValue) + - systemContext.StartDate.ToDateTime(TimeOnly.MinValue)).TotalDays; + } + + +} \ No newline at end of file diff --git a/Bots/YurritBotTests/BuyCalculatorTests.cs b/Bots/YurritBotTests/BuyCalculatorTests.cs new file mode 100644 index 0000000..e45639d --- /dev/null +++ b/Bots/YurritBotTests/BuyCalculatorTests.cs @@ -0,0 +1,23 @@ +using FluentAssertions; +using NUnit.Framework; +using YurritBot; + +namespace YurritBotTests; + +[TestFixture] +public class BuyCalculatorTests +{ + [TestCase(500, 20, 25)] + [TestCase(5000, 2, 1000)] + public void CalculateMaximuumBuyAmountTest(decimal currentCash, decimal listingPrice, int expectedAmount) + { + // Arrange + var buyCalculator = new BuyCalculator(); + + // Act + var maxAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listingPrice); + + // Assert + maxAmount.Should().Be(expectedAmount); + } +} \ No newline at end of file diff --git a/Bots/YurritBotTests/YurritBotTests.csproj b/Bots/YurritBotTests/YurritBotTests.csproj new file mode 100644 index 0000000..778ce8e --- /dev/null +++ b/Bots/YurritBotTests/YurritBotTests.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + latest + disable + enable + + + + + + + + + + + + + + + + + + + From 868a9e7795f9e12aac74da888361fb9d458b374d Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Fri, 25 Jul 2025 20:53:44 +0200 Subject: [PATCH 02/34] Separated Logger --- Bots/YurritBot/Logger.cs | 10 ++++++++++ Bots/YurritBot/YurritTrader.cs | 28 +++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 Bots/YurritBot/Logger.cs diff --git a/Bots/YurritBot/Logger.cs b/Bots/YurritBot/Logger.cs new file mode 100644 index 0000000..4bcad64 --- /dev/null +++ b/Bots/YurritBot/Logger.cs @@ -0,0 +1,10 @@ +namespace YurritBot; + +public class Logger +{ + public void LogToFile(string text) + { + var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); + File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); + } +} diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index 08a9b24..bd44512 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -4,27 +4,29 @@ namespace YurritBot; public class YurritBot : ITraderBot { - private const int cashLowerLimit = 10; public string CompanyName => "Stock Out Like a Sore Thumb"; public async Task DoTurn(ITraderSystemContext systemContext) { - LogToFile($"{systemContext.CurrentDate}"); + var logger = new Logger(); + logger.LogToFile($"{systemContext.CurrentDate}"); int indexToday = DetermineTodayIndex(systemContext); int indexYesterday = indexToday - 1; int indexTomorrow = indexToday + 1; - ExecuteSellStrategy(systemContext, indexToday, indexTomorrow); - ExecuteBuyStrategy(systemContext, indexToday, indexTomorrow); + ExecuteSellStrategy(systemContext, indexToday, indexTomorrow, logger); + ExecuteBuyStrategy(systemContext, indexToday, indexTomorrow, logger); } - private void ExecuteSellStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow) + private void ExecuteSellStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow, Logger logger) { + // TODO some are sold with losses because original price is not taken into account var sellableHoldings = systemContext.GetHoldings(this).Where(holding - => (holding.Listing.PricePoints[indexTomorrow].Price - holding.Listing.PricePoints[indexToday].Price) < 0); + => holding.Amount > 0 + && ((holding.Listing.PricePoints[indexTomorrow].Price - holding.Listing.PricePoints[indexToday].Price) < 0)); foreach (var holding in sellableHoldings) { @@ -32,12 +34,12 @@ private void ExecuteSellStrategy(ITraderSystemContext systemContext, int indexTo if (!success) { - LogToFile($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[indexToday].Price} | Amount {holding.Amount}"); + logger.LogToFile($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[indexToday].Price} | Amount {holding.Amount}"); } } } - private void ExecuteBuyStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow) + private void ExecuteBuyStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow, Logger logger) { var listingsByExpectedIncrease = systemContext.GetListings().OrderBy(listing => (listing.PricePoints[indexTomorrow].Price - listing.PricePoints[indexToday].Price) / listing.PricePoints[indexToday].Price); @@ -64,22 +66,14 @@ private void ExecuteBuyStrategy(ITraderSystemContext systemContext, int indexTod var success = systemContext.BuyStock(this, listing, maxBuyAmount); if (!success) { - LogToFile($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); + logger.LogToFile($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); } } } - private static void LogToFile(string text) - { - var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); - File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); - } - private static int DetermineTodayIndex(ITraderSystemContext systemContext) { return (int)(systemContext.CurrentDate.ToDateTime(TimeOnly.MinValue) - systemContext.StartDate.ToDateTime(TimeOnly.MinValue)).TotalDays; } - - } \ No newline at end of file From e69234f78069d6306bc36586b6ee542e2ab0402e Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Fri, 25 Jul 2025 21:14:10 +0200 Subject: [PATCH 03/34] Separated buyer and seller --- Bots/YurritBot/Buyer.cs | 46 +++++++++++++++++++++++++ Bots/YurritBot/ITrader.cs | 15 +++++++++ Bots/YurritBot/Seller.cs | 32 ++++++++++++++++++ Bots/YurritBot/YurritTrader.cs | 61 +++------------------------------- 4 files changed, 98 insertions(+), 56 deletions(-) create mode 100644 Bots/YurritBot/Buyer.cs create mode 100644 Bots/YurritBot/ITrader.cs create mode 100644 Bots/YurritBot/Seller.cs diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs new file mode 100644 index 0000000..3a2dbba --- /dev/null +++ b/Bots/YurritBot/Buyer.cs @@ -0,0 +1,46 @@ +using NasdaqTrader.Bot.Core; + +namespace YurritBot; + +class Buyer(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, Logger logger) : ITrader +{ + public ITraderBot TraderBot { get; private set; } = traderBot; + public ITraderSystemContext TraderSystemContext { get; private set; } = systemContext; + public int IndexToday { get; private set; } = indexToday; + public int IndexReferenceDay { get; private set; } = indexReferenceDay; + public Logger Logger { get; private set; } = logger; + + private const decimal cashlowerLimit = 50; + + public void ExecuteStrategy() + { + var listingsByExpectedIncrease = systemContext.GetListings().OrderBy(listing + => (listing.PricePoints[IndexReferenceDay].Price - listing.PricePoints[IndexToday].Price) / listing.PricePoints[IndexToday].Price); + + var buyCalculator = new BuyCalculator(); + + foreach (var listing in listingsByExpectedIncrease) + { + var currentCash = systemContext.GetCurrentCash(TraderBot); + if (currentCash < cashlowerLimit + || systemContext.GetTradesLeftForToday(TraderBot) <= 0) + { + return; + } + + // TODO find more efficient way to spot this situation upfront + var pricePointToday = listing.PricePoints[indexToday].Price; + if (currentCash < pricePointToday) + { + continue; + } + + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, pricePointToday); + var success = systemContext.BuyStock(TraderBot, listing, maxBuyAmount); + if (!success) + { + logger.LogToFile($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); + } + } + } +} diff --git a/Bots/YurritBot/ITrader.cs b/Bots/YurritBot/ITrader.cs new file mode 100644 index 0000000..ae15120 --- /dev/null +++ b/Bots/YurritBot/ITrader.cs @@ -0,0 +1,15 @@ +using NasdaqTrader.Bot.Core; + +namespace YurritBot +{ + internal interface ITrader + { + int IndexToday { get; } + int IndexReferenceDay { get; } + Logger Logger { get; } + ITraderBot TraderBot { get; } + ITraderSystemContext TraderSystemContext { get; } + + void ExecuteStrategy(); + } +} \ No newline at end of file diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs new file mode 100644 index 0000000..e98c8d5 --- /dev/null +++ b/Bots/YurritBot/Seller.cs @@ -0,0 +1,32 @@ +using NasdaqTrader.Bot.Core; + +namespace YurritBot; + +class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexTomorrow, Logger logger) : ITrader +{ + public ITraderBot TraderBot { get; private set; } = traderBot; + public ITraderSystemContext TraderSystemContext { get; private set; } = systemContext; + public int IndexToday { get; private set; } = indexToday; + public int IndexReferenceDay { get; private set; } = indexTomorrow; + public Logger Logger { get; private set; } = logger; + + private const int cashLowerLimit = 10; + + public void ExecuteStrategy() + { + // TODO some are sold with losses because original price is not taken into account + var sellableHoldings = systemContext.GetHoldings(TraderBot).Where(holding + => holding.Amount > 0 + && ((holding.Listing.PricePoints[indexTomorrow].Price - holding.Listing.PricePoints[indexToday].Price) < 0)); + + foreach (var holding in sellableHoldings) + { + var success = systemContext.SellStock(TraderBot, holding.Listing, holding.Amount); + + if (!success) + { + logger.LogToFile($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[indexToday].Price} | Amount {holding.Amount}"); + } + } + } +} diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index bd44512..95580f6 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -4,8 +4,6 @@ namespace YurritBot; public class YurritBot : ITraderBot { - private const int cashLowerLimit = 10; - public string CompanyName => "Stock Out Like a Sore Thumb"; public async Task DoTurn(ITraderSystemContext systemContext) @@ -14,61 +12,12 @@ public async Task DoTurn(ITraderSystemContext systemContext) logger.LogToFile($"{systemContext.CurrentDate}"); int indexToday = DetermineTodayIndex(systemContext); - int indexYesterday = indexToday - 1; - int indexTomorrow = indexToday + 1; - - ExecuteSellStrategy(systemContext, indexToday, indexTomorrow, logger); - ExecuteBuyStrategy(systemContext, indexToday, indexTomorrow, logger); - } - - private void ExecuteSellStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow, Logger logger) - { - // TODO some are sold with losses because original price is not taken into account - var sellableHoldings = systemContext.GetHoldings(this).Where(holding - => holding.Amount > 0 - && ((holding.Listing.PricePoints[indexTomorrow].Price - holding.Listing.PricePoints[indexToday].Price) < 0)); - - foreach (var holding in sellableHoldings) - { - var success = systemContext.SellStock(this, holding.Listing, holding.Amount); - - if (!success) - { - logger.LogToFile($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[indexToday].Price} | Amount {holding.Amount}"); - } - } - } - - private void ExecuteBuyStrategy(ITraderSystemContext systemContext, int indexToday, int indexTomorrow, Logger logger) - { - var listingsByExpectedIncrease = systemContext.GetListings().OrderBy(listing - => (listing.PricePoints[indexTomorrow].Price - listing.PricePoints[indexToday].Price) / listing.PricePoints[indexToday].Price); - - var buyCalculator = new BuyCalculator(); - - foreach (var listing in listingsByExpectedIncrease) - { - var currentCash = systemContext.GetCurrentCash(this); - if (currentCash < cashLowerLimit - || systemContext.GetTradesLeftForToday(this) <= 0) - { - return; - } - - // TODO find more efficient way to spot this situation upfront - var pricePointToday = listing.PricePoints[indexToday].Price; - if (currentCash < pricePointToday) - { - continue; - } + int indexReferenceDay = indexToday + 5; - var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, pricePointToday); - var success = systemContext.BuyStock(this, listing, maxBuyAmount); - if (!success) - { - logger.LogToFile($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); - } - } + new Seller(this, systemContext, indexToday, indexReferenceDay, logger) + .ExecuteStrategy(); + new Buyer(this, systemContext, indexToday, indexReferenceDay, logger) + .ExecuteStrategy(); } private static int DetermineTodayIndex(ITraderSystemContext systemContext) From 4918407e251b03d82868a6c2875c1d9a0ebfc1b3 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Fri, 25 Jul 2025 21:49:33 +0200 Subject: [PATCH 04/34] Logger namespace and NullLogger --- Bots/YurritBot/Logging/FileLogger.cs | 10 ++++++++++ Bots/YurritBot/Logging/ILogger.cs | 6 ++++++ Bots/YurritBot/Logging/NullLogger.cs | 9 +++++++++ 3 files changed, 25 insertions(+) create mode 100644 Bots/YurritBot/Logging/FileLogger.cs create mode 100644 Bots/YurritBot/Logging/ILogger.cs create mode 100644 Bots/YurritBot/Logging/NullLogger.cs diff --git a/Bots/YurritBot/Logging/FileLogger.cs b/Bots/YurritBot/Logging/FileLogger.cs new file mode 100644 index 0000000..a179395 --- /dev/null +++ b/Bots/YurritBot/Logging/FileLogger.cs @@ -0,0 +1,10 @@ +namespace YurritBot.Logging; + +public class FileLogger : ILogger +{ + public void Log(string text) + { + var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); + File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); + } +} diff --git a/Bots/YurritBot/Logging/ILogger.cs b/Bots/YurritBot/Logging/ILogger.cs new file mode 100644 index 0000000..661a459 --- /dev/null +++ b/Bots/YurritBot/Logging/ILogger.cs @@ -0,0 +1,6 @@ +namespace YurritBot.Logging; + +public interface ILogger +{ + void Log(string text); +} \ No newline at end of file diff --git a/Bots/YurritBot/Logging/NullLogger.cs b/Bots/YurritBot/Logging/NullLogger.cs new file mode 100644 index 0000000..52f4548 --- /dev/null +++ b/Bots/YurritBot/Logging/NullLogger.cs @@ -0,0 +1,9 @@ +namespace YurritBot.Logging; + +public class NullLogger : ILogger +{ + public void Log(string text) + { + + } +} From 27340d5010916212ced85c32f92318e6c5588a27 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Fri, 25 Jul 2025 21:50:13 +0200 Subject: [PATCH 05/34] Various minor corrections --- Bots/YurritBot/BuyCalculator.cs | 12 +++--------- Bots/YurritBot/Buyer.cs | 22 ++++++++++++---------- Bots/YurritBot/ITrader.cs | 5 +++-- Bots/YurritBot/Logger.cs | 10 ---------- Bots/YurritBot/Seller.cs | 17 +++++++++-------- Bots/YurritBot/YurritTrader.cs | 5 +++-- Bots/YurritBotTests/BuyCalculatorTests.cs | 5 +++-- 7 files changed, 33 insertions(+), 43 deletions(-) delete mode 100644 Bots/YurritBot/Logger.cs diff --git a/Bots/YurritBot/BuyCalculator.cs b/Bots/YurritBot/BuyCalculator.cs index 0366aef..c53f129 100644 --- a/Bots/YurritBot/BuyCalculator.cs +++ b/Bots/YurritBot/BuyCalculator.cs @@ -1,14 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +namespace YurritBot; -namespace YurritBot; - -public class BuyCalculator +public class BuyCalculator(int maximumBuyAmountPerStock) { - private const int maximumBuyAmountPerStock = 1000; + public int MaximumBuyAmountPerStock { get; private set; } = maximumBuyAmountPerStock; public int CalculateMaximuumBuyAmount(decimal currentCash, decimal listingPrice) { diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 3a2dbba..69754b0 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -1,45 +1,47 @@ using NasdaqTrader.Bot.Core; +using YurritBot.Logging; namespace YurritBot; -class Buyer(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, Logger logger) : ITrader +class Buyer(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) : ITrader { public ITraderBot TraderBot { get; private set; } = traderBot; - public ITraderSystemContext TraderSystemContext { get; private set; } = systemContext; + public ITraderSystemContext SystemContext { get; private set; } = systemContext; public int IndexToday { get; private set; } = indexToday; public int IndexReferenceDay { get; private set; } = indexReferenceDay; - public Logger Logger { get; private set; } = logger; + public ILogger Logger { get; private set; } = logger; private const decimal cashlowerLimit = 50; + private const int maxBuyAmountPerStock = 25; public void ExecuteStrategy() { - var listingsByExpectedIncrease = systemContext.GetListings().OrderBy(listing + var listingsByExpectedIncrease = SystemContext.GetListings().OrderBy(listing => (listing.PricePoints[IndexReferenceDay].Price - listing.PricePoints[IndexToday].Price) / listing.PricePoints[IndexToday].Price); - var buyCalculator = new BuyCalculator(); + var buyCalculator = new BuyCalculator(maxBuyAmountPerStock); foreach (var listing in listingsByExpectedIncrease) { - var currentCash = systemContext.GetCurrentCash(TraderBot); + var currentCash = SystemContext.GetCurrentCash(TraderBot); if (currentCash < cashlowerLimit - || systemContext.GetTradesLeftForToday(TraderBot) <= 0) + || SystemContext.GetTradesLeftForToday(TraderBot) <= 0) { return; } // TODO find more efficient way to spot this situation upfront - var pricePointToday = listing.PricePoints[indexToday].Price; + var pricePointToday = listing.PricePoints[IndexToday].Price; if (currentCash < pricePointToday) { continue; } var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, pricePointToday); - var success = systemContext.BuyStock(TraderBot, listing, maxBuyAmount); + var success = SystemContext.BuyStock(TraderBot, listing, maxBuyAmount); if (!success) { - logger.LogToFile($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); + Logger.Log($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); } } } diff --git a/Bots/YurritBot/ITrader.cs b/Bots/YurritBot/ITrader.cs index ae15120..b90d4e4 100644 --- a/Bots/YurritBot/ITrader.cs +++ b/Bots/YurritBot/ITrader.cs @@ -1,4 +1,5 @@ using NasdaqTrader.Bot.Core; +using YurritBot.Logging; namespace YurritBot { @@ -6,9 +7,9 @@ internal interface ITrader { int IndexToday { get; } int IndexReferenceDay { get; } - Logger Logger { get; } + ILogger Logger { get; } ITraderBot TraderBot { get; } - ITraderSystemContext TraderSystemContext { get; } + ITraderSystemContext SystemContext { get; } void ExecuteStrategy(); } diff --git a/Bots/YurritBot/Logger.cs b/Bots/YurritBot/Logger.cs deleted file mode 100644 index 4bcad64..0000000 --- a/Bots/YurritBot/Logger.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace YurritBot; - -public class Logger -{ - public void LogToFile(string text) - { - var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); - File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); - } -} diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index e98c8d5..83a06b5 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -1,31 +1,32 @@ using NasdaqTrader.Bot.Core; +using YurritBot.Logging; namespace YurritBot; -class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexTomorrow, Logger logger) : ITrader +class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) : ITrader { public ITraderBot TraderBot { get; private set; } = traderBot; - public ITraderSystemContext TraderSystemContext { get; private set; } = systemContext; + public ITraderSystemContext SystemContext { get; private set; } = systemContext; public int IndexToday { get; private set; } = indexToday; - public int IndexReferenceDay { get; private set; } = indexTomorrow; - public Logger Logger { get; private set; } = logger; + public int IndexReferenceDay { get; private set; } = indexReferenceDay; + public ILogger Logger { get; private set; } = logger; private const int cashLowerLimit = 10; public void ExecuteStrategy() { // TODO some are sold with losses because original price is not taken into account - var sellableHoldings = systemContext.GetHoldings(TraderBot).Where(holding + var sellableHoldings = SystemContext.GetHoldings(TraderBot).Where(holding => holding.Amount > 0 - && ((holding.Listing.PricePoints[indexTomorrow].Price - holding.Listing.PricePoints[indexToday].Price) < 0)); + && ((holding.Listing.PricePoints[IndexReferenceDay].Price - holding.Listing.PricePoints[IndexToday].Price) < 0)); foreach (var holding in sellableHoldings) { - var success = systemContext.SellStock(TraderBot, holding.Listing, holding.Amount); + var success = SystemContext.SellStock(TraderBot, holding.Listing, holding.Amount); if (!success) { - logger.LogToFile($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[indexToday].Price} | Amount {holding.Amount}"); + Logger.Log($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[IndexToday].Price} | Amount {holding.Amount}"); } } } diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index 95580f6..b6d0802 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -1,4 +1,5 @@ using NasdaqTrader.Bot.Core; +using YurritBot.Logging; namespace YurritBot; @@ -8,8 +9,8 @@ public class YurritBot : ITraderBot public async Task DoTurn(ITraderSystemContext systemContext) { - var logger = new Logger(); - logger.LogToFile($"{systemContext.CurrentDate}"); + var logger = new NullLogger(); + logger.Log($"{systemContext.CurrentDate}"); int indexToday = DetermineTodayIndex(systemContext); int indexReferenceDay = indexToday + 5; diff --git a/Bots/YurritBotTests/BuyCalculatorTests.cs b/Bots/YurritBotTests/BuyCalculatorTests.cs index e45639d..8e4d37a 100644 --- a/Bots/YurritBotTests/BuyCalculatorTests.cs +++ b/Bots/YurritBotTests/BuyCalculatorTests.cs @@ -8,11 +8,12 @@ namespace YurritBotTests; public class BuyCalculatorTests { [TestCase(500, 20, 25)] - [TestCase(5000, 2, 1000)] + [TestCase(5000, 2, 30)] + [TestCase(0, 10, 0)] public void CalculateMaximuumBuyAmountTest(decimal currentCash, decimal listingPrice, int expectedAmount) { // Arrange - var buyCalculator = new BuyCalculator(); + var buyCalculator = new BuyCalculator(30); // Act var maxAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listingPrice); From 87ff13ac777fa67f61fa83fa3bc9e848c128867e Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 27 Jul 2025 17:43:46 +0200 Subject: [PATCH 06/34] Logging cleanup --- Bots/YurritBot/Buyer.cs | 35 +++++++++++++++------------ Bots/YurritBot/Logging/FileLogger.cs | 5 ++++ Bots/YurritBot/Logging/ILogger.cs | 1 + Bots/YurritBot/Logging/NullLogger.cs | 5 ++++ Bots/YurritBot/Seller.cs | 24 +++++++++++++------ Bots/YurritBot/YurritTrader.cs | 36 ++++++++++++++++++++++++---- 6 files changed, 79 insertions(+), 27 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 69754b0..89d9d4d 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -11,38 +11,43 @@ class Buyer(ITraderBot traderBot, ITraderSystemContext systemContext, int indexT public int IndexReferenceDay { get; private set; } = indexReferenceDay; public ILogger Logger { get; private set; } = logger; - private const decimal cashlowerLimit = 50; - private const int maxBuyAmountPerStock = 25; + private const decimal cashlowerLimit = 100; + private const int maxBuyAmountPerStock = 1000; public void ExecuteStrategy() { - var listingsByExpectedIncrease = SystemContext.GetListings().OrderBy(listing - => (listing.PricePoints[IndexReferenceDay].Price - listing.PricePoints[IndexToday].Price) / listing.PricePoints[IndexToday].Price); + var currentCash = SystemContext.GetCurrentCash(TraderBot); + var listingsByExpectedIncrease = SystemContext.GetListings() + .Where(listing => listing.PricePoints[IndexToday].Price <= currentCash) + .OrderByDescending(listing => (listing.PricePoints[IndexReferenceDay].Price - listing.PricePoints[IndexToday].Price) + / listing.PricePoints[IndexToday].Price); var buyCalculator = new BuyCalculator(maxBuyAmountPerStock); foreach (var listing in listingsByExpectedIncrease) { - var currentCash = SystemContext.GetCurrentCash(TraderBot); - if (currentCash < cashlowerLimit - || SystemContext.GetTradesLeftForToday(TraderBot) <= 0) + currentCash = SystemContext.GetCurrentCash(TraderBot); + if (SystemContext.GetTradesLeftForToday(TraderBot) <= 0 + || currentCash < cashlowerLimit) { return; } - // TODO find more efficient way to spot this situation upfront - var pricePointToday = listing.PricePoints[IndexToday].Price; - if (currentCash < pricePointToday) + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[IndexToday].Price); + + if (maxBuyAmount < 1) { continue; } - var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, pricePointToday); var success = SystemContext.BuyStock(TraderBot, listing, maxBuyAmount); - if (!success) - { - Logger.Log($"Failed BUY - Current {currentCash} | Price {pricePointToday} | Amount {maxBuyAmount}"); - } + + Logger.LogTransaction( + category: success ? "" : "ERR", + ticker: listing.Ticker, + currentCash: SystemContext.GetCurrentCash(TraderBot), + pricePoint: listing.PricePoints[IndexToday].Price, + amount: maxBuyAmount); } } } diff --git a/Bots/YurritBot/Logging/FileLogger.cs b/Bots/YurritBot/Logging/FileLogger.cs index a179395..934f99c 100644 --- a/Bots/YurritBot/Logging/FileLogger.cs +++ b/Bots/YurritBot/Logging/FileLogger.cs @@ -7,4 +7,9 @@ public void Log(string text) var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); } + + public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) + { + Log($"- {category} {currentCash:F2} | {ticker} {amount} @ {pricePoint:F2}"); + } } diff --git a/Bots/YurritBot/Logging/ILogger.cs b/Bots/YurritBot/Logging/ILogger.cs index 661a459..679bcd9 100644 --- a/Bots/YurritBot/Logging/ILogger.cs +++ b/Bots/YurritBot/Logging/ILogger.cs @@ -3,4 +3,5 @@ public interface ILogger { void Log(string text); + void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount); } \ No newline at end of file diff --git a/Bots/YurritBot/Logging/NullLogger.cs b/Bots/YurritBot/Logging/NullLogger.cs index 52f4548..b73fbaf 100644 --- a/Bots/YurritBot/Logging/NullLogger.cs +++ b/Bots/YurritBot/Logging/NullLogger.cs @@ -6,4 +6,9 @@ public void Log(string text) { } + + public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) + { + + } } diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index 83a06b5..a5c5ceb 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -15,19 +15,29 @@ class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, int index public void ExecuteStrategy() { + var holdings = SystemContext.GetHoldings(TraderBot); + if (holdings.Length == 0) + { + return; + } + // TODO some are sold with losses because original price is not taken into account - var sellableHoldings = SystemContext.GetHoldings(TraderBot).Where(holding - => holding.Amount > 0 - && ((holding.Listing.PricePoints[IndexReferenceDay].Price - holding.Listing.PricePoints[IndexToday].Price) < 0)); + var sellableHoldings = holdings.Where(holding + => ((holding.Listing.PricePoints[IndexToday].Price - holding.Listing.PricePoints[IndexReferenceDay].Price) > 0) + && holding.Amount > 0); + + Logger.Log($"SELLABLE {sellableHoldings.Count()}"); foreach (var holding in sellableHoldings) { var success = SystemContext.SellStock(TraderBot, holding.Listing, holding.Amount); - if (!success) - { - Logger.Log($"Failed SELL - Current {holding.Listing.Name} | Price {holding.Listing.PricePoints[IndexToday].Price} | Amount {holding.Amount}"); - } + Logger.LogTransaction( + category: success ? "" : "ERR", + ticker: holding.Listing.Ticker, + currentCash: SystemContext.GetCurrentCash(TraderBot), + pricePoint: holding.Listing.PricePoints[IndexToday].Price, + amount: holding.Amount); } } } diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index b6d0802..ee0b03f 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -1,4 +1,5 @@ using NasdaqTrader.Bot.Core; +using System.Diagnostics; using YurritBot.Logging; namespace YurritBot; @@ -6,24 +7,49 @@ namespace YurritBot; public class YurritBot : ITraderBot { public string CompanyName => "Stock Out Like a Sore Thumb"; + + private const int timeScale = 1; + private const int indexOffset = 2; // GetPricePointOnDay starts on 3rd of the month only public async Task DoTurn(ITraderSystemContext systemContext) { - var logger = new NullLogger(); + var logger = new FileLogger(); + + logger.Log($""); logger.Log($"{systemContext.CurrentDate}"); + logger.Log($"========="); int indexToday = DetermineTodayIndex(systemContext); - int indexReferenceDay = indexToday + 5; - new Seller(this, systemContext, indexToday, indexReferenceDay, logger) + logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); + + logger.Log($"SELL"); + logger.Log($"----"); + var timerSell = new Stopwatch(); + timerSell.Start(); + new Seller(this, systemContext, indexToday, indexToday - timeScale, logger) .ExecuteStrategy(); - new Buyer(this, systemContext, indexToday, indexReferenceDay, logger) + timerSell.Stop(); + logger.Log($"- {timerSell.ElapsedMilliseconds}ms"); + + logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); + + logger.Log($"BUY"); + logger.Log($"---"); + var timerBuy = new Stopwatch(); + timerBuy.Start(); + new Buyer(this, systemContext, indexToday, indexToday + timeScale, logger) .ExecuteStrategy(); + timerBuy.Stop(); + logger.Log($"- {timerBuy.ElapsedMilliseconds}ms"); + + logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); } private static int DetermineTodayIndex(ITraderSystemContext systemContext) { return (int)(systemContext.CurrentDate.ToDateTime(TimeOnly.MinValue) - - systemContext.StartDate.ToDateTime(TimeOnly.MinValue)).TotalDays; + - systemContext.StartDate.ToDateTime(TimeOnly.MinValue)).TotalDays + - indexOffset; } } \ No newline at end of file From ee285610fa8c1c20bdc75cf0d482c47f8c1ab58e Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 27 Jul 2025 18:06:44 +0200 Subject: [PATCH 07/34] Simple daily buy strategy --- Bots/YurritBot/Buyer.cs | 5 +++-- Bots/YurritBot/Seller.cs | 9 +-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 89d9d4d..1847e13 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -1,4 +1,5 @@ using NasdaqTrader.Bot.Core; +using System.Reflection; using YurritBot.Logging; namespace YurritBot; @@ -19,8 +20,8 @@ public void ExecuteStrategy() var currentCash = SystemContext.GetCurrentCash(TraderBot); var listingsByExpectedIncrease = SystemContext.GetListings() .Where(listing => listing.PricePoints[IndexToday].Price <= currentCash) - .OrderByDescending(listing => (listing.PricePoints[IndexReferenceDay].Price - listing.PricePoints[IndexToday].Price) - / listing.PricePoints[IndexToday].Price); + .Where(listing => listing.PricePoints[IndexReferenceDay].Price / listing.PricePoints[IndexToday].Price > 1) + .OrderByDescending(listing => (listing.PricePoints[IndexReferenceDay].Price / listing.PricePoints[IndexToday].Price)); var buyCalculator = new BuyCalculator(maxBuyAmountPerStock); diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index a5c5ceb..ca3d464 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -21,14 +21,7 @@ public void ExecuteStrategy() return; } - // TODO some are sold with losses because original price is not taken into account - var sellableHoldings = holdings.Where(holding - => ((holding.Listing.PricePoints[IndexToday].Price - holding.Listing.PricePoints[IndexReferenceDay].Price) > 0) - && holding.Amount > 0); - - Logger.Log($"SELLABLE {sellableHoldings.Count()}"); - - foreach (var holding in sellableHoldings) + foreach (var holding in holdings) { var success = SystemContext.SellStock(TraderBot, holding.Listing, holding.Amount); From b2da2b4ce4fe69d604973913814e08e8693296b0 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 27 Jul 2025 19:10:57 +0200 Subject: [PATCH 08/34] DateCalculator --- Bots/YurritBot/DateCalculator.cs | 37 ++++++++++++++++++++++ Bots/YurritBotTests/DateCalculatorTests.cs | 32 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 Bots/YurritBot/DateCalculator.cs create mode 100644 Bots/YurritBotTests/DateCalculatorTests.cs diff --git a/Bots/YurritBot/DateCalculator.cs b/Bots/YurritBot/DateCalculator.cs new file mode 100644 index 0000000..46060a7 --- /dev/null +++ b/Bots/YurritBot/DateCalculator.cs @@ -0,0 +1,37 @@ +using NasdaqTrader.Bot.Core; + +namespace YurritBot +{ + public class DateCalculator + { + public int CalculateBusinessDaysBetween(DateOnly startDate, DateOnly endDate) + { + DateTime startDateTime = startDate.ToDateTime(TimeOnly.MinValue); + DateTime endDateTime = endDate.ToDateTime(TimeOnly.MinValue); + + int totalDays = (endDateTime - startDateTime).Days; + if (totalDays < 0) + { + return 0; + } + + int businessDays = 0; + for (int i = 0; i < totalDays; i++) + { + DateTime currentDate = startDateTime.AddDays(i); + if (currentDate.DayOfWeek != DayOfWeek.Saturday && + currentDate.DayOfWeek != DayOfWeek.Sunday) + { + businessDays++; + } + } + + return businessDays; + } + + public int DetermineDateIndex(DateOnly date, IEnumerable pricePoints) + => pricePoints.Select((pricePoint, index) => (pricePoint, index)) + .First(pointIndexPair => pointIndexPair.pricePoint.Date.Equals(date)) + .index; + } +} diff --git a/Bots/YurritBotTests/DateCalculatorTests.cs b/Bots/YurritBotTests/DateCalculatorTests.cs new file mode 100644 index 0000000..925ff9f --- /dev/null +++ b/Bots/YurritBotTests/DateCalculatorTests.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using YurritBot; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using FluentAssertions; + +namespace YurritBot.Tests +{ + [TestFixture] + public class DateCalculatorTests + { + [TestCase("01-01-2025", "01-01-2025", 0)] + [TestCase("01-01-2025", "01-03-2025", 2)] + [TestCase("01-01-2025", "01-08-2025", 5)] + [TestCase("01-01-2025", "01-13-2025", 8)] + public void CalculateBusinessDaysBetweenTest(DateOnly startDate, DateOnly endDate, int expectedIndex) + { + // Arrange + var dateCalculator = new DateCalculator(); + + // Act + var index = dateCalculator.CalculateBusinessDaysBetween(startDate, endDate); + + // Assert + index.Should().Be(expectedIndex); + } + } +} \ No newline at end of file From 4fe3ba99f5adb11e5e3223d6cd485bb1a92cfddc Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 27 Jul 2025 19:11:48 +0200 Subject: [PATCH 09/34] Skip holding with 0 amount while selling --- Bots/YurritBot/Seller.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index ca3d464..c40f0e9 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -23,6 +23,11 @@ public void ExecuteStrategy() foreach (var holding in holdings) { + if (holding.Amount == 0) + { + continue; + } + var success = SystemContext.SellStock(TraderBot, holding.Listing, holding.Amount); Logger.LogTransaction( From b22eb27ec59b0c9f32688b1030c734225f017780 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 27 Jul 2025 19:12:49 +0200 Subject: [PATCH 10/34] Corrected date index calculation --- Bots/YurritBot/YurritTrader.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index ee0b03f..64393d9 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -9,7 +9,6 @@ public class YurritBot : ITraderBot public string CompanyName => "Stock Out Like a Sore Thumb"; private const int timeScale = 1; - private const int indexOffset = 2; // GetPricePointOnDay starts on 3rd of the month only public async Task DoTurn(ITraderSystemContext systemContext) { @@ -19,7 +18,8 @@ public async Task DoTurn(ITraderSystemContext systemContext) logger.Log($"{systemContext.CurrentDate}"); logger.Log($"========="); - int indexToday = DetermineTodayIndex(systemContext); + int indexToday = new DateCalculator() + .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); @@ -46,10 +46,5 @@ public async Task DoTurn(ITraderSystemContext systemContext) logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); } - private static int DetermineTodayIndex(ITraderSystemContext systemContext) - { - return (int)(systemContext.CurrentDate.ToDateTime(TimeOnly.MinValue) - - systemContext.StartDate.ToDateTime(TimeOnly.MinValue)).TotalDays - - indexOffset; - } + } \ No newline at end of file From 31dee6473d59edfd392db45cc4a89314239031d5 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 27 Jul 2025 19:15:36 +0200 Subject: [PATCH 11/34] Turned off logger for fork PR --- Bots/YurritBot/YurritTrader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index 64393d9..e6789b0 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -12,7 +12,7 @@ public class YurritBot : ITraderBot public async Task DoTurn(ITraderSystemContext systemContext) { - var logger = new FileLogger(); + var logger = new NullLogger(); logger.Log($""); logger.Log($"{systemContext.CurrentDate}"); From bcd3b9462d5d6a0e4695d7da405509430cad2d12 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 27 Jul 2025 19:24:26 +0200 Subject: [PATCH 12/34] Reverted accidentally committed gitignore changes --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 0519732..d2880c1 100644 --- a/.gitignore +++ b/.gitignore @@ -131,5 +131,4 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store -_NCrunch* -/Bots/.vs +_NCrunch* \ No newline at end of file From 822d5c03c2355d429033ad3a2d58fa9f9f7fab50 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Mon, 28 Jul 2025 21:54:28 +0200 Subject: [PATCH 13/34] First attempt at optimizing --- Bots/YurritBot/Buyer.cs | 51 +++++++++---------- Bots/YurritBot/ITrader.cs | 16 ------ Bots/YurritBot/Logging/FileLogger.cs | 19 ++++++- Bots/YurritBot/Logging/ILogger.cs | 2 + Bots/YurritBot/Logging/NullLogger.cs | 10 ++++ Bots/YurritBot/Seller.cs | 26 ++++------ Bots/YurritBot/YurritTrader.cs | 44 +++++++--------- .../{BuyCalculatorTests.cs => BuyerTests.cs} | 8 +-- Bots/YurritBotTests/DateCalculatorTests.cs | 10 +--- Bots/YurritBotTests/YurritBotTests.csproj | 7 +++ 10 files changed, 94 insertions(+), 99 deletions(-) delete mode 100644 Bots/YurritBot/ITrader.cs rename Bots/YurritBotTests/{BuyCalculatorTests.cs => BuyerTests.cs} (65%) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 1847e13..c1e296a 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -1,54 +1,53 @@ using NasdaqTrader.Bot.Core; -using System.Reflection; using YurritBot.Logging; namespace YurritBot; -class Buyer(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) : ITrader +public class Buyer() { - public ITraderBot TraderBot { get; private set; } = traderBot; - public ITraderSystemContext SystemContext { get; private set; } = systemContext; - public int IndexToday { get; private set; } = indexToday; - public int IndexReferenceDay { get; private set; } = indexReferenceDay; - public ILogger Logger { get; private set; } = logger; - private const decimal cashlowerLimit = 100; - private const int maxBuyAmountPerStock = 1000; + private const int maximumBuyAmountPerStock = 1000; - public void ExecuteStrategy() + public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) { - var currentCash = SystemContext.GetCurrentCash(TraderBot); - var listingsByExpectedIncrease = SystemContext.GetListings() - .Where(listing => listing.PricePoints[IndexToday].Price <= currentCash) - .Where(listing => listing.PricePoints[IndexReferenceDay].Price / listing.PricePoints[IndexToday].Price > 1) - .OrderByDescending(listing => (listing.PricePoints[IndexReferenceDay].Price / listing.PricePoints[IndexToday].Price)); + var currentCash = systemContext.GetCurrentCash(traderBot); + var listingsByExpectedIncrease = systemContext.GetListings() + .Where(listing => listing.PricePoints[indexToday].Price <= currentCash) + .Where(listing => listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price > 1) + .OrderByDescending(listing => (listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price)); - var buyCalculator = new BuyCalculator(maxBuyAmountPerStock); + //var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); foreach (var listing in listingsByExpectedIncrease) { - currentCash = SystemContext.GetCurrentCash(TraderBot); - if (SystemContext.GetTradesLeftForToday(TraderBot) <= 0 + currentCash = systemContext.GetCurrentCash(traderBot); + if (systemContext.GetTradesLeftForToday(traderBot) <= 0 || currentCash < cashlowerLimit) { return; } - var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[IndexToday].Price); + var maxBuyAmount = CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[indexToday].Price); if (maxBuyAmount < 1) { continue; } - var success = SystemContext.BuyStock(TraderBot, listing, maxBuyAmount); + var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); - Logger.LogTransaction( - category: success ? "" : "ERR", - ticker: listing.Ticker, - currentCash: SystemContext.GetCurrentCash(TraderBot), - pricePoint: listing.PricePoints[IndexToday].Price, - amount: maxBuyAmount); + //Logger.LogTransaction( + // category: success ? "" : "ERR", + // ticker: listing.Ticker, + // currentCash: SystemContext.GetCurrentCash(TraderBot), + // pricePoint: listing.PricePoints[IndexToday].Price, + // amount: maxBuyAmount); } } + + public int CalculateMaximuumBuyAmount(decimal currentCash, decimal listingPrice) + { + decimal maximumBuyAmountWithCurrentCash = currentCash / listingPrice; + return (int)Math.Floor(Math.Min(maximumBuyAmountPerStock, maximumBuyAmountWithCurrentCash)); + } } diff --git a/Bots/YurritBot/ITrader.cs b/Bots/YurritBot/ITrader.cs deleted file mode 100644 index b90d4e4..0000000 --- a/Bots/YurritBot/ITrader.cs +++ /dev/null @@ -1,16 +0,0 @@ -using NasdaqTrader.Bot.Core; -using YurritBot.Logging; - -namespace YurritBot -{ - internal interface ITrader - { - int IndexToday { get; } - int IndexReferenceDay { get; } - ILogger Logger { get; } - ITraderBot TraderBot { get; } - ITraderSystemContext SystemContext { get; } - - void ExecuteStrategy(); - } -} \ No newline at end of file diff --git a/Bots/YurritBot/Logging/FileLogger.cs b/Bots/YurritBot/Logging/FileLogger.cs index 934f99c..1852c19 100644 --- a/Bots/YurritBot/Logging/FileLogger.cs +++ b/Bots/YurritBot/Logging/FileLogger.cs @@ -1,10 +1,11 @@ namespace YurritBot.Logging; -public class FileLogger : ILogger +public class FileLogger(string logFilePath) : ILogger { + private string logFilePath = logFilePath; + public void Log(string text) { - var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); } @@ -12,4 +13,18 @@ public void LogTransaction(string category, string ticker, decimal currentCash, { Log($"- {category} {currentCash:F2} | {ticker} {amount} @ {pricePoint:F2}"); } + + public void LogHeader1(string text) + { + File.AppendAllLines( + logFilePath, + [string.Empty, text, new string('=', text.Length)]); + } + + public void LogHeader2(string text) + { + File.AppendAllLines( + logFilePath, + [text, new string('-', text.Length)]); + } } diff --git a/Bots/YurritBot/Logging/ILogger.cs b/Bots/YurritBot/Logging/ILogger.cs index 679bcd9..1ff71c7 100644 --- a/Bots/YurritBot/Logging/ILogger.cs +++ b/Bots/YurritBot/Logging/ILogger.cs @@ -3,5 +3,7 @@ public interface ILogger { void Log(string text); + void LogHeader1(string text); + void LogHeader2(string text); void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount); } \ No newline at end of file diff --git a/Bots/YurritBot/Logging/NullLogger.cs b/Bots/YurritBot/Logging/NullLogger.cs index b73fbaf..95b04d1 100644 --- a/Bots/YurritBot/Logging/NullLogger.cs +++ b/Bots/YurritBot/Logging/NullLogger.cs @@ -7,6 +7,16 @@ public void Log(string text) } + public void LogHeader1(string text) + { + + } + + public void LogHeader2(string text) + { + + } + public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) { diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index c40f0e9..06698a0 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -3,19 +3,13 @@ namespace YurritBot; -class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) : ITrader +class Seller() { - public ITraderBot TraderBot { get; private set; } = traderBot; - public ITraderSystemContext SystemContext { get; private set; } = systemContext; - public int IndexToday { get; private set; } = indexToday; - public int IndexReferenceDay { get; private set; } = indexReferenceDay; - public ILogger Logger { get; private set; } = logger; - private const int cashLowerLimit = 10; - public void ExecuteStrategy() + public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) { - var holdings = SystemContext.GetHoldings(TraderBot); + var holdings = systemContext.GetHoldings(traderBot); if (holdings.Length == 0) { return; @@ -28,14 +22,14 @@ public void ExecuteStrategy() continue; } - var success = SystemContext.SellStock(TraderBot, holding.Listing, holding.Amount); + var success = systemContext.SellStock(traderBot, holding.Listing, holding.Amount); - Logger.LogTransaction( - category: success ? "" : "ERR", - ticker: holding.Listing.Ticker, - currentCash: SystemContext.GetCurrentCash(TraderBot), - pricePoint: holding.Listing.PricePoints[IndexToday].Price, - amount: holding.Amount); + //Logger.LogTransaction( + // category: success ? "" : "ERR", + // ticker: holding.Listing.Ticker, + // currentCash: SystemContext.GetCurrentCash(TraderBot), + // pricePoint: holding.Listing.PricePoints[IndexToday].Price, + // amount: holding.Amount); } } } diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index e6789b0..a73f867 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -9,41 +9,31 @@ public class YurritBot : ITraderBot public string CompanyName => "Stock Out Like a Sore Thumb"; private const int timeScale = 1; + private const string logFileName = "yurritbot_errors.log"; public async Task DoTurn(ITraderSystemContext systemContext) { - var logger = new NullLogger(); + var logger = new FileLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); - logger.Log($""); - logger.Log($"{systemContext.CurrentDate}"); - logger.Log($"========="); + logger.LogHeader1($"{systemContext.CurrentDate}"); + // SLOW int indexToday = new DateCalculator() .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); - logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); - - logger.Log($"SELL"); - logger.Log($"----"); - var timerSell = new Stopwatch(); - timerSell.Start(); - new Seller(this, systemContext, indexToday, indexToday - timeScale, logger) - .ExecuteStrategy(); - timerSell.Stop(); - logger.Log($"- {timerSell.ElapsedMilliseconds}ms"); - - logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); - - logger.Log($"BUY"); - logger.Log($"---"); - var timerBuy = new Stopwatch(); - timerBuy.Start(); - new Buyer(this, systemContext, indexToday, indexToday + timeScale, logger) - .ExecuteStrategy(); - timerBuy.Stop(); - logger.Log($"- {timerBuy.ElapsedMilliseconds}ms"); - - logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); + //logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); + + //logger.LogHeader2($"SELL"); + + new Seller().ExecuteStrategy(this, systemContext, logger); + + //logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); + + //logger.LogHeader2($"BUY"); + + new Buyer().ExecuteStrategy(this, systemContext, indexToday, indexToday + timeScale, logger); + + //logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); } diff --git a/Bots/YurritBotTests/BuyCalculatorTests.cs b/Bots/YurritBotTests/BuyerTests.cs similarity index 65% rename from Bots/YurritBotTests/BuyCalculatorTests.cs rename to Bots/YurritBotTests/BuyerTests.cs index 8e4d37a..11e899e 100644 --- a/Bots/YurritBotTests/BuyCalculatorTests.cs +++ b/Bots/YurritBotTests/BuyerTests.cs @@ -5,18 +5,18 @@ namespace YurritBotTests; [TestFixture] -public class BuyCalculatorTests +public class BuyerTests { [TestCase(500, 20, 25)] - [TestCase(5000, 2, 30)] + [TestCase(5000, 2, 1000)] [TestCase(0, 10, 0)] public void CalculateMaximuumBuyAmountTest(decimal currentCash, decimal listingPrice, int expectedAmount) { // Arrange - var buyCalculator = new BuyCalculator(30); + var buyer = new Buyer(); // Act - var maxAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listingPrice); + var maxAmount = buyer.CalculateMaximuumBuyAmount(currentCash, listingPrice); // Assert maxAmount.Should().Be(expectedAmount); diff --git a/Bots/YurritBotTests/DateCalculatorTests.cs b/Bots/YurritBotTests/DateCalculatorTests.cs index 925ff9f..3ee82b5 100644 --- a/Bots/YurritBotTests/DateCalculatorTests.cs +++ b/Bots/YurritBotTests/DateCalculatorTests.cs @@ -1,12 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using YurritBot; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using FluentAssertions; using NUnit.Framework; -using FluentAssertions; +using System; namespace YurritBot.Tests { diff --git a/Bots/YurritBotTests/YurritBotTests.csproj b/Bots/YurritBotTests/YurritBotTests.csproj index 778ce8e..b08d343 100644 --- a/Bots/YurritBotTests/YurritBotTests.csproj +++ b/Bots/YurritBotTests/YurritBotTests.csproj @@ -11,6 +11,7 @@ + @@ -23,4 +24,10 @@ + + + ..\..\Build\NasdaqTrader.Bot.Core.dll + + + From 1be2cb09bd5e42186bb67c57e9741aef46f28f79 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sat, 9 Aug 2025 23:32:41 +0200 Subject: [PATCH 14/34] Moved most logging into buyer and seller for debug purposes --- Bots/YurritBot/Buyer.cs | 24 +++++++++++------------- Bots/YurritBot/Seller.cs | 20 +++++++++++--------- Bots/YurritBot/YurritTrader.cs | 17 +++-------------- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index c1e296a..7516c2f 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -10,13 +10,15 @@ public class Buyer() public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) { + logger.LogHeader2($"BUY"); + var currentCash = systemContext.GetCurrentCash(traderBot); var listingsByExpectedIncrease = systemContext.GetListings() .Where(listing => listing.PricePoints[indexToday].Price <= currentCash) .Where(listing => listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price > 1) .OrderByDescending(listing => (listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price)); - //var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); + var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); foreach (var listing in listingsByExpectedIncrease) { @@ -27,7 +29,7 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon return; } - var maxBuyAmount = CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[indexToday].Price); + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[indexToday].Price); if (maxBuyAmount < 1) { @@ -36,18 +38,14 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); - //Logger.LogTransaction( - // category: success ? "" : "ERR", - // ticker: listing.Ticker, - // currentCash: SystemContext.GetCurrentCash(TraderBot), - // pricePoint: listing.PricePoints[IndexToday].Price, - // amount: maxBuyAmount); + logger.LogTransaction( + category: success ? "" : "ERR", + ticker: listing.Ticker, + currentCash: systemContext.GetCurrentCash(traderBot), + pricePoint: listing.PricePoints[indexToday].Price, + amount: maxBuyAmount); } - } - public int CalculateMaximuumBuyAmount(decimal currentCash, decimal listingPrice) - { - decimal maximumBuyAmountWithCurrentCash = currentCash / listingPrice; - return (int)Math.Floor(Math.Min(maximumBuyAmountPerStock, maximumBuyAmountWithCurrentCash)); + logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } } diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index 06698a0..1700df7 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -5,10 +5,10 @@ namespace YurritBot; class Seller() { - private const int cashLowerLimit = 10; - - public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) + public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger) { + logger.LogHeader2($"SELL"); + var holdings = systemContext.GetHoldings(traderBot); if (holdings.Length == 0) { @@ -24,12 +24,14 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon var success = systemContext.SellStock(traderBot, holding.Listing, holding.Amount); - //Logger.LogTransaction( - // category: success ? "" : "ERR", - // ticker: holding.Listing.Ticker, - // currentCash: SystemContext.GetCurrentCash(TraderBot), - // pricePoint: holding.Listing.PricePoints[IndexToday].Price, - // amount: holding.Amount); + logger.LogTransaction( + category: success ? "" : "ERR", + ticker: holding.Listing.Ticker, + currentCash: systemContext.GetCurrentCash(traderBot), + pricePoint: holding.Listing.PricePoints[indexToday].Price, + amount: holding.Amount); } + + logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } } diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index a73f867..da8c2dc 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -14,27 +14,16 @@ public class YurritBot : ITraderBot public async Task DoTurn(ITraderSystemContext systemContext) { var logger = new FileLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); - logger.LogHeader1($"{systemContext.CurrentDate}"); // SLOW int indexToday = new DateCalculator() .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); - //logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); - - //logger.LogHeader2($"SELL"); - - new Seller().ExecuteStrategy(this, systemContext, logger); - - //logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); - - //logger.LogHeader2($"BUY"); - + new Seller().ExecuteStrategy(this, systemContext, indexToday, logger); new Buyer().ExecuteStrategy(this, systemContext, indexToday, indexToday + timeScale, logger); - - //logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); + } - + } \ No newline at end of file From d811214f96d1fd9f60caa0fb5dc07013f299b86e Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sat, 9 Aug 2025 23:33:08 +0200 Subject: [PATCH 15/34] Launchsettings --- Bots/YurritBot/Properties/launchSettings.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Bots/YurritBot/Properties/launchSettings.json diff --git a/Bots/YurritBot/Properties/launchSettings.json b/Bots/YurritBot/Properties/launchSettings.json new file mode 100644 index 0000000..05d4cb1 --- /dev/null +++ b/Bots/YurritBot/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "YurritBot": { + "commandName": "Project" + }, + "NasdaqTrader": { + "commandName": "Executable", + "executablePath": "C:\\Dev\\ZomerCompetitie2025\\Build\\NasdaqTrader.CLI.exe", + "commandLineArgs": "-d ../../Data -n 100 -s 1000 -t 2500" + } + } +} \ No newline at end of file From 71beb5e17b466c4b34c41e1d59968d4ab893050d Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 10 Aug 2025 00:23:38 +0200 Subject: [PATCH 16/34] Basic seller refactoring --- Bots/YurritBot/Seller.cs | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index 1700df7..e4968fb 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -8,30 +8,37 @@ class Seller() public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger) { logger.LogHeader2($"SELL"); + logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); var holdings = systemContext.GetHoldings(traderBot); if (holdings.Length == 0) { + logger.Log($"no holdings"); return; } foreach (var holding in holdings) { - if (holding.Amount == 0) - { - continue; - } - - var success = systemContext.SellStock(traderBot, holding.Listing, holding.Amount); - - logger.LogTransaction( - category: success ? "" : "ERR", - ticker: holding.Listing.Ticker, - currentCash: systemContext.GetCurrentCash(traderBot), - pricePoint: holding.Listing.PricePoints[indexToday].Price, - amount: holding.Amount); + TrySellHolding(traderBot, systemContext, indexToday, logger, holding); } logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } + + private static void TrySellHolding(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger, IHolding holding) + { + if (holding.Amount == 0) + { + return; + } + + var success = systemContext.SellStock(traderBot, holding.Listing, holding.Amount); + + logger.LogTransaction( + category: success ? "" : "ERR", + ticker: holding.Listing.Ticker, + currentCash: systemContext.GetCurrentCash(traderBot), + pricePoint: holding.Listing.PricePoints[indexToday].Price, + amount: holding.Amount); + } } From 4d24214a363caacc42b432951658d82542c4c41c Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 10 Aug 2025 00:24:27 +0200 Subject: [PATCH 17/34] Basic buyer refactoring and improved linq query to reduce amount of recalculating. --- Bots/YurritBot/Buyer.cs | 82 +++++++++++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 7516c2f..527b757 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -1,4 +1,5 @@ using NasdaqTrader.Bot.Core; +using System.Diagnostics; using YurritBot.Logging; namespace YurritBot; @@ -11,41 +12,82 @@ public class Buyer() public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) { logger.LogHeader2($"BUY"); + logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); - var currentCash = systemContext.GetCurrentCash(traderBot); - var listingsByExpectedIncrease = systemContext.GetListings() - .Where(listing => listing.PricePoints[indexToday].Price <= currentCash) - .Where(listing => listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price > 1) - .OrderByDescending(listing => (listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price)); + logger.Log($"listings: {systemContext.GetListings().Count()}"); + + var listingsByExpectedIncrease = RankListings(traderBot, systemContext, indexToday, indexReferenceDay); + logger.Log($"ranked listings: {listingsByExpectedIncrease.Count()}"); var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); + if (!listingsByExpectedIncrease.Any()) + { + logger.Log($"no listings"); + return; + } + foreach (var listing in listingsByExpectedIncrease) { - currentCash = systemContext.GetCurrentCash(traderBot); - if (systemContext.GetTradesLeftForToday(traderBot) <= 0 - || currentCash < cashlowerLimit) + var currentCash = systemContext.GetCurrentCash(traderBot); + if (systemContext.GetTradesLeftForToday(traderBot) <= 0) { + logger.Log($"no trades left"); return; } - var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[indexToday].Price); - - if (maxBuyAmount < 1) + if (currentCash < cashlowerLimit) { - continue; + logger.Log($"cash below limit"); + return; } - var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); - - logger.LogTransaction( - category: success ? "" : "ERR", - ticker: listing.Ticker, - currentCash: systemContext.GetCurrentCash(traderBot), - pricePoint: listing.PricePoints[indexToday].Price, - amount: maxBuyAmount); + TryBuyListing(traderBot, systemContext, indexToday, logger, currentCash, buyCalculator, listing); } logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } + + private static List RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay) + { + var currentCash = systemContext.GetCurrentCash(traderBot); + //return systemContext.GetListings() + // .Where(listing => listing.PricePoints[indexToday].Price <= currentCash) + // .Where(listing => listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price > 1) + // .OrderByDescending(listing => (listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price)); + + return systemContext.GetListings() + .Select(l => new + { + Listing = l, + PriceToday = l.PricePoints[indexToday].Price, + PriceRatio = l.PricePoints[indexReferenceDay].Price / l.PricePoints[indexToday].Price + }) + .Where(x => x.PriceToday <= currentCash + && x.PriceRatio > 1) + .OrderByDescending(x => x.PriceRatio) + .Select(x => x.Listing) + .ToList(); + } + + private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger, decimal currentCash, BuyCalculator buyCalculator, IStockListing listing) + { + logger.Log($"try buy {listing.Ticker}"); + + if (currentCash < listing.PricePoints[indexToday].Price) + { + logger.Log($"too expensive"); + return; + } + + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[indexToday].Price); + var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); + + logger.LogTransaction( + category: success ? "" : "ERR", + ticker: listing.Ticker, + currentCash: systemContext.GetCurrentCash(traderBot), + pricePoint: listing.PricePoints[indexToday].Price, + amount: maxBuyAmount); + } } From 3ac3957684534eb35d8e4942e95722abd72405a8 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Mon, 11 Aug 2025 22:59:56 +0200 Subject: [PATCH 18/34] HTMLLogger + added erase functionality for Log --- Bots/YurritBot/Logging/HtmlLogger.cs | 31 +++++++++++++++++++ Bots/YurritBot/Logging/ILogger.cs | 1 + Bots/YurritBot/Logging/NullLogger.cs | 5 +++ .../Logging/{FileLogger.cs => TextLogger.cs} | 7 ++++- Bots/YurritBot/YurritTrader.cs | 10 ++++-- 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 Bots/YurritBot/Logging/HtmlLogger.cs rename Bots/YurritBot/Logging/{FileLogger.cs => TextLogger.cs} (85%) diff --git a/Bots/YurritBot/Logging/HtmlLogger.cs b/Bots/YurritBot/Logging/HtmlLogger.cs new file mode 100644 index 0000000..86f6693 --- /dev/null +++ b/Bots/YurritBot/Logging/HtmlLogger.cs @@ -0,0 +1,31 @@ +namespace YurritBot.Logging; + +public class HtmlLogger(string logFilePath) : ILogger +{ + private readonly string logFilePath = logFilePath; + + public void Log(string text) + { + File.AppendAllText(logFilePath, $"

{text}

{Environment.NewLine}"); + } + + public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) + { + Log($"

- {category} {currentCash:F2} | {ticker} {amount} @ {pricePoint:F2}

"); + } + + public void LogHeader1(string text) + { + Log($"

{text}

"); + } + + public void LogHeader2(string text) + { + Log($"

{text}

"); + } + + public void Erase() + { + File.Delete(logFilePath); + } +} diff --git a/Bots/YurritBot/Logging/ILogger.cs b/Bots/YurritBot/Logging/ILogger.cs index 1ff71c7..1dce771 100644 --- a/Bots/YurritBot/Logging/ILogger.cs +++ b/Bots/YurritBot/Logging/ILogger.cs @@ -6,4 +6,5 @@ public interface ILogger void LogHeader1(string text); void LogHeader2(string text); void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount); + void Erase(); } \ No newline at end of file diff --git a/Bots/YurritBot/Logging/NullLogger.cs b/Bots/YurritBot/Logging/NullLogger.cs index 95b04d1..c4d00dd 100644 --- a/Bots/YurritBot/Logging/NullLogger.cs +++ b/Bots/YurritBot/Logging/NullLogger.cs @@ -2,6 +2,11 @@ public class NullLogger : ILogger { + public void Erase() + { + + } + public void Log(string text) { diff --git a/Bots/YurritBot/Logging/FileLogger.cs b/Bots/YurritBot/Logging/TextLogger.cs similarity index 85% rename from Bots/YurritBot/Logging/FileLogger.cs rename to Bots/YurritBot/Logging/TextLogger.cs index 1852c19..1613188 100644 --- a/Bots/YurritBot/Logging/FileLogger.cs +++ b/Bots/YurritBot/Logging/TextLogger.cs @@ -2,7 +2,7 @@ public class FileLogger(string logFilePath) : ILogger { - private string logFilePath = logFilePath; + private readonly string logFilePath = logFilePath; public void Log(string text) { @@ -27,4 +27,9 @@ public void LogHeader2(string text) logFilePath, [text, new string('-', text.Length)]); } + + public void Erase() + { + File.Delete(logFilePath); + } } diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index da8c2dc..4436a56 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -9,17 +9,23 @@ public class YurritBot : ITraderBot public string CompanyName => "Stock Out Like a Sore Thumb"; private const int timeScale = 1; - private const string logFileName = "yurritbot_errors.log"; + private const string logFileName = "yurritbot_log.html"; public async Task DoTurn(ITraderSystemContext systemContext) { - var logger = new FileLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); + var logger = new HtmlLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); logger.LogHeader1($"{systemContext.CurrentDate}"); // SLOW int indexToday = new DateCalculator() .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); + if (indexToday == 0) + { + logger.Erase(); + logger.Log($"{DateTime.Now}"); + } + new Seller().ExecuteStrategy(this, systemContext, indexToday, logger); new Buyer().ExecuteStrategy(this, systemContext, indexToday, indexToday + timeScale, logger); From c4dcda94646e6d75e8f7b735f21898dab2abda28 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Mon, 11 Aug 2025 23:22:25 +0200 Subject: [PATCH 19/34] Skip holdings without amount + logging cleanup --- Bots/YurritBot/Buyer.cs | 18 ++++++++++-------- Bots/YurritBot/Logging/HtmlLogger.cs | 2 +- Bots/YurritBot/Seller.cs | 12 ++++++------ Bots/YurritBot/YurritTrader.cs | 4 ++-- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 527b757..3b43d53 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -11,12 +11,11 @@ public class Buyer() public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) { - logger.LogHeader2($"BUY"); - logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); + logger.LogHeader2($"Buy"); - logger.Log($"listings: {systemContext.GetListings().Count()}"); + logger.Log($"€{systemContext.GetCurrentCash(traderBot):F2}"); - var listingsByExpectedIncrease = RankListings(traderBot, systemContext, indexToday, indexReferenceDay); + var listingsByExpectedIncrease = RankListings(traderBot, systemContext, indexToday, indexReferenceDay, logger); logger.Log($"ranked listings: {listingsByExpectedIncrease.Count()}"); var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); @@ -48,7 +47,7 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } - private static List RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay) + private static List RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) { var currentCash = systemContext.GetCurrentCash(traderBot); //return systemContext.GetListings() @@ -56,7 +55,8 @@ private static List RankListings(ITraderBot traderBot, ITraderSys // .Where(listing => listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price > 1) // .OrderByDescending(listing => (listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price)); - return systemContext.GetListings() + Stopwatch stopwatch = Stopwatch.StartNew(); + var rankedListings = systemContext.GetListings() .Select(l => new { Listing = l, @@ -68,12 +68,14 @@ private static List RankListings(ITraderBot traderBot, ITraderSys .OrderByDescending(x => x.PriceRatio) .Select(x => x.Listing) .ToList(); + stopwatch.Stop(); + logger.Log($"Ranking took {stopwatch.ElapsedTicks} ticks"); + + return rankedListings; } private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger, decimal currentCash, BuyCalculator buyCalculator, IStockListing listing) { - logger.Log($"try buy {listing.Ticker}"); - if (currentCash < listing.PricePoints[indexToday].Price) { logger.Log($"too expensive"); diff --git a/Bots/YurritBot/Logging/HtmlLogger.cs b/Bots/YurritBot/Logging/HtmlLogger.cs index 86f6693..54ea620 100644 --- a/Bots/YurritBot/Logging/HtmlLogger.cs +++ b/Bots/YurritBot/Logging/HtmlLogger.cs @@ -11,7 +11,7 @@ public void Log(string text) public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) { - Log($"

- {category} {currentCash:F2} | {ticker} {amount} @ {pricePoint:F2}

"); + Log($"

{category} | €{currentCash:F2} | {ticker} {amount} @ {pricePoint:F2}

"); } public void LogHeader1(string text) diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index e4968fb..b2a7309 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -7,11 +7,13 @@ class Seller() { public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger) { - logger.LogHeader2($"SELL"); - logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); + logger.LogHeader2($"Sell"); - var holdings = systemContext.GetHoldings(traderBot); - if (holdings.Length == 0) + logger.Log($"€{systemContext.GetCurrentCash(traderBot):F2}"); + + var holdings = systemContext.GetHoldings(traderBot) + .Where(holding => holding.Amount > 0); + if (!holdings.Any()) { logger.Log($"no holdings"); return; @@ -21,8 +23,6 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon { TrySellHolding(traderBot, systemContext, indexToday, logger, holding); } - - logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } private static void TrySellHolding(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger, IHolding holding) diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index 4436a56..990688a 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -14,7 +14,6 @@ public class YurritBot : ITraderBot public async Task DoTurn(ITraderSystemContext systemContext) { var logger = new HtmlLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); - logger.LogHeader1($"{systemContext.CurrentDate}"); // SLOW int indexToday = new DateCalculator() @@ -23,8 +22,9 @@ public async Task DoTurn(ITraderSystemContext systemContext) if (indexToday == 0) { logger.Erase(); - logger.Log($"{DateTime.Now}"); } + logger.LogHeader1($"{systemContext.CurrentDate}"); + logger.Log($"{DateTime.Now:dd-MMM-yyyy,hh:mm::ss.fff}"); new Seller().ExecuteStrategy(this, systemContext, indexToday, logger); new Buyer().ExecuteStrategy(this, systemContext, indexToday, indexToday + timeScale, logger); From ed2179ede7ccf9064c260d6d5158b2d6642bf592 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Fri, 29 Aug 2025 17:14:58 +0200 Subject: [PATCH 20/34] Error checking for failed date and price calculation --- Bots/YurritBot/Buyer.cs | 11 ++++++++++- Bots/YurritBot/DateCalculator.cs | 18 +++++++++++++++--- Bots/YurritBot/YurritTrader.cs | 20 ++++++++++++++------ Bots/YurritBotTests/BuyerTests.cs | 2 +- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 3b43d53..fc1765e 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -63,7 +63,7 @@ private static List RankListings(ITraderBot traderBot, ITraderSys PriceToday = l.PricePoints[indexToday].Price, PriceRatio = l.PricePoints[indexReferenceDay].Price / l.PricePoints[indexToday].Price }) - .Where(x => x.PriceToday <= currentCash + .Where(x => x.PriceToday <= currentCash && x.PriceRatio > 1) .OrderByDescending(x => x.PriceRatio) .Select(x => x.Listing) @@ -82,7 +82,16 @@ private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext sys return; } + var price = systemContext.GetPriceOnDay(listing); + + if (price < 0.0001m) + { + logger.Log($"Failed to get price"); + return; + } + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[indexToday].Price); + var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); logger.LogTransaction( diff --git a/Bots/YurritBot/DateCalculator.cs b/Bots/YurritBot/DateCalculator.cs index 46060a7..8706735 100644 --- a/Bots/YurritBot/DateCalculator.cs +++ b/Bots/YurritBot/DateCalculator.cs @@ -30,8 +30,20 @@ public int CalculateBusinessDaysBetween(DateOnly startDate, DateOnly endDate) } public int DetermineDateIndex(DateOnly date, IEnumerable pricePoints) - => pricePoints.Select((pricePoint, index) => (pricePoint, index)) - .First(pointIndexPair => pointIndexPair.pricePoint.Date.Equals(date)) - .index; + { + var currentPricePoint = pricePoints.Select((pricePoint, index) + => (pricePoint, index)) + .FirstOrDefault(pointIndexPair => pointIndexPair.pricePoint.Date.Equals(date)); + + if (currentPricePoint.pricePoint != null) + { + return currentPricePoint.index; + } + else + { + // Date not found in price points + throw new ArgumentException($"Date {date} not found in price points."); + } + } } } diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index 990688a..770fc17 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -1,5 +1,4 @@ using NasdaqTrader.Bot.Core; -using System.Diagnostics; using YurritBot.Logging; namespace YurritBot; @@ -7,7 +6,7 @@ namespace YurritBot; public class YurritBot : ITraderBot { public string CompanyName => "Stock Out Like a Sore Thumb"; - + private const int timeScale = 1; private const string logFileName = "yurritbot_log.html"; @@ -15,9 +14,18 @@ public async Task DoTurn(ITraderSystemContext systemContext) { var logger = new HtmlLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); - // SLOW - int indexToday = new DateCalculator() - .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); + int indexToday = -1; + try + { + // SLOW + indexToday = new DateCalculator() + .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); + } + catch (ArgumentException ex) + { + logger.Log($"Error determining date index: {ex.Message}"); + return; + } if (indexToday == 0) { @@ -28,7 +36,7 @@ public async Task DoTurn(ITraderSystemContext systemContext) new Seller().ExecuteStrategy(this, systemContext, indexToday, logger); new Buyer().ExecuteStrategy(this, systemContext, indexToday, indexToday + timeScale, logger); - + } diff --git a/Bots/YurritBotTests/BuyerTests.cs b/Bots/YurritBotTests/BuyerTests.cs index 11e899e..2f4d367 100644 --- a/Bots/YurritBotTests/BuyerTests.cs +++ b/Bots/YurritBotTests/BuyerTests.cs @@ -13,7 +13,7 @@ public class BuyerTests public void CalculateMaximuumBuyAmountTest(decimal currentCash, decimal listingPrice, int expectedAmount) { // Arrange - var buyer = new Buyer(); + var buyer = new BuyCalculator(1000); // Act var maxAmount = buyer.CalculateMaximuumBuyAmount(currentCash, listingPrice); From 04f85ac4e66f9bccbe6412fd53100f10aefe0046 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 31 Aug 2025 20:04:09 +0200 Subject: [PATCH 21/34] Stylesheet for logging --- Bots/YurritBot/Logging/HtmlLogger.cs | 12 +++++++++++- Bots/YurritBot/Logging/ILogger.cs | 2 ++ Bots/YurritBot/Logging/NullLogger.cs | 5 +++++ Bots/YurritBot/Logging/TextLogger.cs | 5 +++++ Bots/YurritBot/Logging/style.css | 7 +++++++ Bots/YurritBot/YurritBot.csproj | 5 +++++ 6 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Bots/YurritBot/Logging/style.css diff --git a/Bots/YurritBot/Logging/HtmlLogger.cs b/Bots/YurritBot/Logging/HtmlLogger.cs index 54ea620..e8adea9 100644 --- a/Bots/YurritBot/Logging/HtmlLogger.cs +++ b/Bots/YurritBot/Logging/HtmlLogger.cs @@ -1,4 +1,6 @@ -namespace YurritBot.Logging; +using static System.Net.Mime.MediaTypeNames; + +namespace YurritBot.Logging; public class HtmlLogger(string logFilePath) : ILogger { @@ -28,4 +30,12 @@ public void Erase() { File.Delete(logFilePath); } + + public void Initialize() + { + File.AppendAllText(logFilePath, $"{Environment.NewLine}"); + File.AppendAllText(logFilePath, $"{Environment.NewLine}"); + File.AppendAllText(logFilePath, $"{Environment.NewLine}"); + File.AppendAllText(logFilePath, $"{Environment.NewLine}"); + } } diff --git a/Bots/YurritBot/Logging/ILogger.cs b/Bots/YurritBot/Logging/ILogger.cs index 1dce771..d313b71 100644 --- a/Bots/YurritBot/Logging/ILogger.cs +++ b/Bots/YurritBot/Logging/ILogger.cs @@ -6,5 +6,7 @@ public interface ILogger void LogHeader1(string text); void LogHeader2(string text); void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount); + + void Initialize(); void Erase(); } \ No newline at end of file diff --git a/Bots/YurritBot/Logging/NullLogger.cs b/Bots/YurritBot/Logging/NullLogger.cs index c4d00dd..f60ee1c 100644 --- a/Bots/YurritBot/Logging/NullLogger.cs +++ b/Bots/YurritBot/Logging/NullLogger.cs @@ -7,6 +7,11 @@ public void Erase() } + public void Initialize() + { + + } + public void Log(string text) { diff --git a/Bots/YurritBot/Logging/TextLogger.cs b/Bots/YurritBot/Logging/TextLogger.cs index 1613188..4d4a4f1 100644 --- a/Bots/YurritBot/Logging/TextLogger.cs +++ b/Bots/YurritBot/Logging/TextLogger.cs @@ -32,4 +32,9 @@ public void Erase() { File.Delete(logFilePath); } + + public void Initialize() + { + + } } diff --git a/Bots/YurritBot/Logging/style.css b/Bots/YurritBot/Logging/style.css new file mode 100644 index 0000000..6e92d51 --- /dev/null +++ b/Bots/YurritBot/Logging/style.css @@ -0,0 +1,7 @@ +body { + font-family: Arial, sans-serif; + background-color: #030303; + color: white; + margin: 0; + padding: 20px; +} \ No newline at end of file diff --git a/Bots/YurritBot/YurritBot.csproj b/Bots/YurritBot/YurritBot.csproj index dda88a4..6b718c1 100644 --- a/Bots/YurritBot/YurritBot.csproj +++ b/Bots/YurritBot/YurritBot.csproj @@ -19,5 +19,10 @@ ..\..\Build\NasdaqTrader.Bot.Core.dll + + + Always + + From e518a2b701ec7726397c74eaad4694f92d02c966 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Sun, 31 Aug 2025 20:05:14 +0200 Subject: [PATCH 22/34] Removed the use of indexes as the index of a day differs per listing. --- Bots/YurritBot/Buyer.cs | 46 ++++++++++++++++++---------------- Bots/YurritBot/Seller.cs | 8 +++--- Bots/YurritBot/YurritTrader.cs | 7 +++--- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index fc1765e..65e9e8a 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -9,18 +9,18 @@ public class Buyer() private const decimal cashlowerLimit = 100; private const int maximumBuyAmountPerStock = 1000; - public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) + public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) { logger.LogHeader2($"Buy"); logger.Log($"€{systemContext.GetCurrentCash(traderBot):F2}"); - var listingsByExpectedIncrease = RankListings(traderBot, systemContext, indexToday, indexReferenceDay, logger); + var listingsByExpectedIncrease = RankListings(traderBot, systemContext, logger); logger.Log($"ranked listings: {listingsByExpectedIncrease.Count()}"); var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); - if (!listingsByExpectedIncrease.Any()) + if (listingsByExpectedIncrease.Count == 0) { logger.Log($"no listings"); return; @@ -41,31 +41,30 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon return; } - TryBuyListing(traderBot, systemContext, indexToday, logger, currentCash, buyCalculator, listing); + TryBuyListing(traderBot, systemContext, logger, buyCalculator, listing); } logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } - private static List RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) + private static List RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) { var currentCash = systemContext.GetCurrentCash(traderBot); - //return systemContext.GetListings() - // .Where(listing => listing.PricePoints[indexToday].Price <= currentCash) - // .Where(listing => listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price > 1) - // .OrderByDescending(listing => (listing.PricePoints[indexReferenceDay].Price / listing.PricePoints[indexToday].Price)); Stopwatch stopwatch = Stopwatch.StartNew(); var rankedListings = systemContext.GetListings() - .Select(l => new + .Select(l => { - Listing = l, - PriceToday = l.PricePoints[indexToday].Price, - PriceRatio = l.PricePoints[indexReferenceDay].Price / l.PricePoints[indexToday].Price + var indexToday = Array.FindIndex(l.PricePoints, 0, l.PricePoints.Length, p => p.Date == systemContext.CurrentDate); + return new { Listing = l, IndexToday = indexToday }; }) - .Where(x => x.PriceToday <= currentCash - && x.PriceRatio > 1) - .OrderByDescending(x => x.PriceRatio) + .Where(x => + //x.IndexToday >= 0 && + x.Listing.PricePoints[x.IndexToday].Price <= currentCash && + //x.IndexToday + 1 < x.Listing.PricePoints.Count() && + x.Listing.PricePoints[x.IndexToday + 1].Price > x.Listing.PricePoints[x.IndexToday].Price) + .OrderByDescending(x => + x.Listing.PricePoints[x.IndexToday + 1].Price / x.Listing.PricePoints[x.IndexToday].Price) .Select(x => x.Listing) .ToList(); stopwatch.Stop(); @@ -74,23 +73,26 @@ private static List RankListings(ITraderBot traderBot, ITraderSys return rankedListings; } - private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger, decimal currentCash, BuyCalculator buyCalculator, IStockListing listing) + private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger, + BuyCalculator buyCalculator, IStockListing listing) { - if (currentCash < listing.PricePoints[indexToday].Price) + var price = systemContext.GetPriceOnDay(listing); + var currentCash = systemContext.GetCurrentCash(traderBot); + + // Redundant? Already in Linq + if (currentCash < systemContext.GetPriceOnDay(listing)) { logger.Log($"too expensive"); return; } - var price = systemContext.GetPriceOnDay(listing); - if (price < 0.0001m) { logger.Log($"Failed to get price"); return; } - var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[indexToday].Price); + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, price); var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); @@ -98,7 +100,7 @@ private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext sys category: success ? "" : "ERR", ticker: listing.Ticker, currentCash: systemContext.GetCurrentCash(traderBot), - pricePoint: listing.PricePoints[indexToday].Price, + pricePoint: price, amount: maxBuyAmount); } } diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs index b2a7309..42d2e39 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/Seller.cs @@ -5,7 +5,7 @@ namespace YurritBot; class Seller() { - public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger) + public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) { logger.LogHeader2($"Sell"); @@ -21,11 +21,11 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon foreach (var holding in holdings) { - TrySellHolding(traderBot, systemContext, indexToday, logger, holding); + TrySellHolding(traderBot, systemContext, logger, holding); } } - private static void TrySellHolding(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, ILogger logger, IHolding holding) + private static void TrySellHolding(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger, IHolding holding) { if (holding.Amount == 0) { @@ -38,7 +38,7 @@ private static void TrySellHolding(ITraderBot traderBot, ITraderSystemContext sy category: success ? "" : "ERR", ticker: holding.Listing.Ticker, currentCash: systemContext.GetCurrentCash(traderBot), - pricePoint: holding.Listing.PricePoints[indexToday].Price, + pricePoint: systemContext.GetPriceOnDay(holding.Listing), amount: holding.Amount); } } diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index 770fc17..d8750b0 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -6,8 +6,6 @@ namespace YurritBot; public class YurritBot : ITraderBot { public string CompanyName => "Stock Out Like a Sore Thumb"; - - private const int timeScale = 1; private const string logFileName = "yurritbot_log.html"; public async Task DoTurn(ITraderSystemContext systemContext) @@ -30,12 +28,13 @@ public async Task DoTurn(ITraderSystemContext systemContext) if (indexToday == 0) { logger.Erase(); + logger.Initialize(); } logger.LogHeader1($"{systemContext.CurrentDate}"); logger.Log($"{DateTime.Now:dd-MMM-yyyy,hh:mm::ss.fff}"); - new Seller().ExecuteStrategy(this, systemContext, indexToday, logger); - new Buyer().ExecuteStrategy(this, systemContext, indexToday, indexToday + timeScale, logger); + new Seller().ExecuteStrategy(this, systemContext, logger); + new Buyer().ExecuteStrategy(this, systemContext, logger); } From bfc85a396bfb55c9dd0b60074c30a101123f174e Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Tue, 2 Sep 2025 12:35:34 +0200 Subject: [PATCH 23/34] Fixed ranking Linq --- Bots/YurritBot/Buyer.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 65e9e8a..6dce1a6 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -56,15 +56,18 @@ private static List RankListings(ITraderBot traderBot, ITraderSys .Select(l => { var indexToday = Array.FindIndex(l.PricePoints, 0, l.PricePoints.Length, p => p.Date == systemContext.CurrentDate); - return new { Listing = l, IndexToday = indexToday }; + var priceToday = l.PricePoints[indexToday].Price; + var priceTomorrow = l.PricePoints[indexToday + 1].Price; + var priceRatio = priceTomorrow / priceToday; + return new { + Listing = l, + PriceToday = priceToday, + PriceRatio = priceRatio + }; }) - .Where(x => - //x.IndexToday >= 0 && - x.Listing.PricePoints[x.IndexToday].Price <= currentCash && - //x.IndexToday + 1 < x.Listing.PricePoints.Count() && - x.Listing.PricePoints[x.IndexToday + 1].Price > x.Listing.PricePoints[x.IndexToday].Price) - .OrderByDescending(x => - x.Listing.PricePoints[x.IndexToday + 1].Price / x.Listing.PricePoints[x.IndexToday].Price) + .Where(x => x.PriceToday <= currentCash + && x.PriceRatio > 1) + .OrderByDescending(x => x.PriceRatio) .Select(x => x.Listing) .ToList(); stopwatch.Stop(); From 7b8c090ea828c3f2f4d56dc3fb7f2c42bc3b68f2 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Tue, 2 Sep 2025 12:52:00 +0200 Subject: [PATCH 24/34] Experimented with further Linq optimization --- Bots/YurritBot/Buyer.cs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs index 6dce1a6..ed7c846 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/Buyer.cs @@ -16,46 +16,45 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon logger.Log($"€{systemContext.GetCurrentCash(traderBot):F2}"); var listingsByExpectedIncrease = RankListings(traderBot, systemContext, logger); - logger.Log($"ranked listings: {listingsByExpectedIncrease.Count()}"); var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); - if (listingsByExpectedIncrease.Count == 0) - { - logger.Log($"no listings"); - return; - } - foreach (var listing in listingsByExpectedIncrease) { + logger.Log($"Listing {listing.Ticker}"); + var currentCash = systemContext.GetCurrentCash(traderBot); if (systemContext.GetTradesLeftForToday(traderBot) <= 0) { - logger.Log($"no trades left"); + logger.Log($"- no trades left"); return; } if (currentCash < cashlowerLimit) { - logger.Log($"cash below limit"); + logger.Log($"- cash below limit"); return; } + logger.Log($"- buying"); TryBuyListing(traderBot, systemContext, logger, buyCalculator, listing); } - - logger.Log($"- €{systemContext.GetCurrentCash(traderBot):F2}"); } - private static List RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) + private static IEnumerable RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) { var currentCash = systemContext.GetCurrentCash(traderBot); + logger.Log($"Ranking"); Stopwatch stopwatch = Stopwatch.StartNew(); var rankedListings = systemContext.GetListings() .Select(l => { + // Very slow the first time around var indexToday = Array.FindIndex(l.PricePoints, 0, l.PricePoints.Length, p => p.Date == systemContext.CurrentDate); + //var indexToday = l.PricePoints + // .Select((v, i) => new { v, i }) + // .First(p => p.v.Date == systemContext.CurrentDate).i; var priceToday = l.PricePoints[indexToday].Price; var priceTomorrow = l.PricePoints[indexToday + 1].Price; var priceRatio = priceTomorrow / priceToday; @@ -65,13 +64,13 @@ private static List RankListings(ITraderBot traderBot, ITraderSys PriceRatio = priceRatio }; }) - .Where(x => x.PriceToday <= currentCash - && x.PriceRatio > 1) + .Where(x =>// x.PriceToday <= currentCash && + x.PriceRatio > 1) .OrderByDescending(x => x.PriceRatio) - .Select(x => x.Listing) - .ToList(); + .Select(x => x.Listing); + stopwatch.Stop(); - logger.Log($"Ranking took {stopwatch.ElapsedTicks} ticks"); + logger.Log($"- took {stopwatch.ElapsedTicks} ticks"); return rankedListings; } @@ -85,13 +84,13 @@ private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext sys // Redundant? Already in Linq if (currentCash < systemContext.GetPriceOnDay(listing)) { - logger.Log($"too expensive"); + logger.Log($" - too expensive"); return; } if (price < 0.0001m) { - logger.Log($"Failed to get price"); + logger.Log($" - failed to get price"); return; } From 98e9dd03823bb7cd1021fd327c14d991d88b4f55 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 20:36:24 +0200 Subject: [PATCH 25/34] CSS headers --- Bots/YurritBot/Logging/style.css | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Bots/YurritBot/Logging/style.css b/Bots/YurritBot/Logging/style.css index 6e92d51..3ec2cac 100644 --- a/Bots/YurritBot/Logging/style.css +++ b/Bots/YurritBot/Logging/style.css @@ -4,4 +4,23 @@ body { color: white; margin: 0; padding: 20px; -} \ No newline at end of file +} + +p { + margin: 0 0 2px 0; +} + +h1, +h2 { + width: 100%; +} + +h1 { + border-bottom: 2px solid white; + margin: 30px 0 5px 0; +} + +h2 { + border-bottom: 1px solid white; + margin: 10px 0 5px 0; +} From 7cd13cc15e6399933c4ef54835f271cbac280d3e Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 21:42:13 +0200 Subject: [PATCH 26/34] Fixed Linq for sorting listings. Code cleanup and removed unused code. --- Bots/YurritBot/BuyCalculator.cs | 2 +- Bots/YurritBot/DateCalculator.cs | 49 ---------- Bots/YurritBot/{ => TradeHandlers}/Buyer.cs | 91 ++++++++----------- Bots/YurritBot/TradeHandlers/ITradeHandler.cs | 6 ++ Bots/YurritBot/{ => TradeHandlers}/Seller.cs | 14 ++- Bots/YurritBot/YurritTrader.cs | 27 ++---- .../{BuyerTests.cs => BuyCalculatorTests.cs} | 2 +- Bots/YurritBotTests/DateCalculatorTests.cs | 26 ------ 8 files changed, 63 insertions(+), 154 deletions(-) delete mode 100644 Bots/YurritBot/DateCalculator.cs rename Bots/YurritBot/{ => TradeHandlers}/Buyer.cs (50%) create mode 100644 Bots/YurritBot/TradeHandlers/ITradeHandler.cs rename Bots/YurritBot/{ => TradeHandlers}/Seller.cs (70%) rename Bots/YurritBotTests/{BuyerTests.cs => BuyCalculatorTests.cs} (94%) delete mode 100644 Bots/YurritBotTests/DateCalculatorTests.cs diff --git a/Bots/YurritBot/BuyCalculator.cs b/Bots/YurritBot/BuyCalculator.cs index c53f129..4b45945 100644 --- a/Bots/YurritBot/BuyCalculator.cs +++ b/Bots/YurritBot/BuyCalculator.cs @@ -2,7 +2,7 @@ public class BuyCalculator(int maximumBuyAmountPerStock) { - public int MaximumBuyAmountPerStock { get; private set; } = maximumBuyAmountPerStock; + private int maximumBuyAmountPerStock = maximumBuyAmountPerStock; public int CalculateMaximuumBuyAmount(decimal currentCash, decimal listingPrice) { diff --git a/Bots/YurritBot/DateCalculator.cs b/Bots/YurritBot/DateCalculator.cs deleted file mode 100644 index 8706735..0000000 --- a/Bots/YurritBot/DateCalculator.cs +++ /dev/null @@ -1,49 +0,0 @@ -using NasdaqTrader.Bot.Core; - -namespace YurritBot -{ - public class DateCalculator - { - public int CalculateBusinessDaysBetween(DateOnly startDate, DateOnly endDate) - { - DateTime startDateTime = startDate.ToDateTime(TimeOnly.MinValue); - DateTime endDateTime = endDate.ToDateTime(TimeOnly.MinValue); - - int totalDays = (endDateTime - startDateTime).Days; - if (totalDays < 0) - { - return 0; - } - - int businessDays = 0; - for (int i = 0; i < totalDays; i++) - { - DateTime currentDate = startDateTime.AddDays(i); - if (currentDate.DayOfWeek != DayOfWeek.Saturday && - currentDate.DayOfWeek != DayOfWeek.Sunday) - { - businessDays++; - } - } - - return businessDays; - } - - public int DetermineDateIndex(DateOnly date, IEnumerable pricePoints) - { - var currentPricePoint = pricePoints.Select((pricePoint, index) - => (pricePoint, index)) - .FirstOrDefault(pointIndexPair => pointIndexPair.pricePoint.Date.Equals(date)); - - if (currentPricePoint.pricePoint != null) - { - return currentPricePoint.index; - } - else - { - // Date not found in price points - throw new ArgumentException($"Date {date} not found in price points."); - } - } - } -} diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/TradeHandlers/Buyer.cs similarity index 50% rename from Bots/YurritBot/Buyer.cs rename to Bots/YurritBot/TradeHandlers/Buyer.cs index ed7c846..6a69880 100644 --- a/Bots/YurritBot/Buyer.cs +++ b/Bots/YurritBot/TradeHandlers/Buyer.cs @@ -2,104 +2,91 @@ using System.Diagnostics; using YurritBot.Logging; -namespace YurritBot; +namespace YurritBot.TradeHandlers; -public class Buyer() +public class Buyer(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) : ITradeHandler { + private readonly ITraderBot traderBot = traderBot; + private readonly ITraderSystemContext systemContext = systemContext; + private readonly ILogger logger = logger; + private const decimal cashlowerLimit = 100; private const int maximumBuyAmountPerStock = 1000; - public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) + public void ExecuteStrategy() { logger.LogHeader2($"Buy"); logger.Log($"€{systemContext.GetCurrentCash(traderBot):F2}"); - var listingsByExpectedIncrease = RankListings(traderBot, systemContext, logger); - + logger.Log($"Ranking"); + Stopwatch stopwatch = Stopwatch.StartNew(); + var rankedListings = RankListingsByExpectedIncrease(traderBot, systemContext, logger); + stopwatch.Stop(); + logger.Log($"- took {stopwatch.Elapsed}"); + + logger.Log($"Buying"); var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); - - foreach (var listing in listingsByExpectedIncrease) + foreach (var listing in rankedListings) { - logger.Log($"Listing {listing.Ticker}"); - - var currentCash = systemContext.GetCurrentCash(traderBot); - if (systemContext.GetTradesLeftForToday(traderBot) <= 0) + if (systemContext.GetCurrentCash(traderBot) < cashlowerLimit) { - logger.Log($"- no trades left"); + logger.Log($"- cash below limit"); return; } - if (currentCash < cashlowerLimit) + if (systemContext.GetTradesLeftForToday(traderBot) <= 0) { - logger.Log($"- cash below limit"); + logger.Log($"- no trades left"); return; } - logger.Log($"- buying"); TryBuyListing(traderBot, systemContext, logger, buyCalculator, listing); } } - private static IEnumerable RankListings(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) + private static IEnumerable RankListingsByExpectedIncrease(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) { var currentCash = systemContext.GetCurrentCash(traderBot); - logger.Log($"Ranking"); - Stopwatch stopwatch = Stopwatch.StartNew(); - var rankedListings = systemContext.GetListings() + return systemContext.GetListings() .Select(l => { - // Very slow the first time around - var indexToday = Array.FindIndex(l.PricePoints, 0, l.PricePoints.Length, p => p.Date == systemContext.CurrentDate); - //var indexToday = l.PricePoints - // .Select((v, i) => new { v, i }) - // .First(p => p.v.Date == systemContext.CurrentDate).i; + DateOnly[] dateArray = Array.ConvertAll(l.PricePoints, p => p.Date); + int indexToday = Array.BinarySearch(dateArray, systemContext.CurrentDate); + if (indexToday < 0 || indexToday >= l.PricePoints.Length -1) + { + return null; + } + var priceToday = l.PricePoints[indexToday].Price; var priceTomorrow = l.PricePoints[indexToday + 1].Price; - var priceRatio = priceTomorrow / priceToday; - return new { + return new + { Listing = l, PriceToday = priceToday, - PriceRatio = priceRatio + PriceRatio = priceTomorrow / priceToday }; }) - .Where(x =>// x.PriceToday <= currentCash && - x.PriceRatio > 1) + .Where(x => x != null + && x.PriceToday > 0.0001m + && x.PriceToday <= currentCash + && x.PriceRatio > 1) .OrderByDescending(x => x.PriceRatio) .Select(x => x.Listing); - - stopwatch.Stop(); - logger.Log($"- took {stopwatch.ElapsedTicks} ticks"); - - return rankedListings; } private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger, BuyCalculator buyCalculator, IStockListing listing) { var price = systemContext.GetPriceOnDay(listing); - var currentCash = systemContext.GetCurrentCash(traderBot); - - // Redundant? Already in Linq - if (currentCash < systemContext.GetPriceOnDay(listing)) - { - logger.Log($" - too expensive"); - return; - } - - if (price < 0.0001m) - { - logger.Log($" - failed to get price"); - return; - } - - var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, price); - + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount( + currentCash: systemContext.GetCurrentCash(traderBot), + listingPrice: price); var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); logger.LogTransaction( - category: success ? "" : "ERR", + category: success ? "- BUY" : "- BUY ERR", ticker: listing.Ticker, currentCash: systemContext.GetCurrentCash(traderBot), pricePoint: price, diff --git a/Bots/YurritBot/TradeHandlers/ITradeHandler.cs b/Bots/YurritBot/TradeHandlers/ITradeHandler.cs new file mode 100644 index 0000000..01733f2 --- /dev/null +++ b/Bots/YurritBot/TradeHandlers/ITradeHandler.cs @@ -0,0 +1,6 @@ +namespace YurritBot.TradeHandlers +{ + public interface ITradeHandler + { + } +} \ No newline at end of file diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/TradeHandlers/Seller.cs similarity index 70% rename from Bots/YurritBot/Seller.cs rename to Bots/YurritBot/TradeHandlers/Seller.cs index 42d2e39..fa42c3e 100644 --- a/Bots/YurritBot/Seller.cs +++ b/Bots/YurritBot/TradeHandlers/Seller.cs @@ -1,11 +1,15 @@ using NasdaqTrader.Bot.Core; using YurritBot.Logging; -namespace YurritBot; +namespace YurritBot.TradeHandlers; -class Seller() +public class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) : ITradeHandler { - public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) + private readonly ITraderBot traderBot = traderBot; + private readonly ITraderSystemContext systemContext = systemContext; + private readonly ILogger logger = logger; + + public void ExecuteStrategy() { logger.LogHeader2($"Sell"); @@ -15,7 +19,7 @@ public void ExecuteStrategy(ITraderBot traderBot, ITraderSystemContext systemCon .Where(holding => holding.Amount > 0); if (!holdings.Any()) { - logger.Log($"no holdings"); + logger.Log($"- no holdings"); return; } @@ -35,7 +39,7 @@ private static void TrySellHolding(ITraderBot traderBot, ITraderSystemContext sy var success = systemContext.SellStock(traderBot, holding.Listing, holding.Amount); logger.LogTransaction( - category: success ? "" : "ERR", + category: success ? "- SELL" : "- SELL ERR", ticker: holding.Listing.Ticker, currentCash: systemContext.GetCurrentCash(traderBot), pricePoint: systemContext.GetPriceOnDay(holding.Listing), diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index d8750b0..9b09c39 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -1,5 +1,6 @@ using NasdaqTrader.Bot.Core; using YurritBot.Logging; +using YurritBot.TradeHandlers; namespace YurritBot; @@ -10,33 +11,19 @@ public class YurritBot : ITraderBot public async Task DoTurn(ITraderSystemContext systemContext) { - var logger = new HtmlLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); + //var logger = new HtmlLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); + var logger = new NullLogger(); - int indexToday = -1; - try + if (!File.Exists(Path.Combine(AppContext.BaseDirectory, logFileName))) { - // SLOW - indexToday = new DateCalculator() - .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); - } - catch (ArgumentException ex) - { - logger.Log($"Error determining date index: {ex.Message}"); - return; - } - - if (indexToday == 0) - { - logger.Erase(); logger.Initialize(); } + logger.LogHeader1($"{systemContext.CurrentDate}"); logger.Log($"{DateTime.Now:dd-MMM-yyyy,hh:mm::ss.fff}"); - new Seller().ExecuteStrategy(this, systemContext, logger); - new Buyer().ExecuteStrategy(this, systemContext, logger); + new Seller(this, systemContext, logger).ExecuteStrategy(); + new Buyer(this, systemContext, logger).ExecuteStrategy(); } - - } \ No newline at end of file diff --git a/Bots/YurritBotTests/BuyerTests.cs b/Bots/YurritBotTests/BuyCalculatorTests.cs similarity index 94% rename from Bots/YurritBotTests/BuyerTests.cs rename to Bots/YurritBotTests/BuyCalculatorTests.cs index 2f4d367..0e4abef 100644 --- a/Bots/YurritBotTests/BuyerTests.cs +++ b/Bots/YurritBotTests/BuyCalculatorTests.cs @@ -5,7 +5,7 @@ namespace YurritBotTests; [TestFixture] -public class BuyerTests +public class BuyCalculatorTests { [TestCase(500, 20, 25)] [TestCase(5000, 2, 1000)] diff --git a/Bots/YurritBotTests/DateCalculatorTests.cs b/Bots/YurritBotTests/DateCalculatorTests.cs deleted file mode 100644 index 3ee82b5..0000000 --- a/Bots/YurritBotTests/DateCalculatorTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using System; - -namespace YurritBot.Tests -{ - [TestFixture] - public class DateCalculatorTests - { - [TestCase("01-01-2025", "01-01-2025", 0)] - [TestCase("01-01-2025", "01-03-2025", 2)] - [TestCase("01-01-2025", "01-08-2025", 5)] - [TestCase("01-01-2025", "01-13-2025", 8)] - public void CalculateBusinessDaysBetweenTest(DateOnly startDate, DateOnly endDate, int expectedIndex) - { - // Arrange - var dateCalculator = new DateCalculator(); - - // Act - var index = dateCalculator.CalculateBusinessDaysBetween(startDate, endDate); - - // Assert - index.Should().Be(expectedIndex); - } - } -} \ No newline at end of file From 2e99f5a0e19c905fdef6d4c9924f13814314a979 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 21:47:54 +0200 Subject: [PATCH 27/34] Updated gitignore changes to make sure it does not show up in the pull request --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d2880c1..49855e2 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,4 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store -_NCrunch* \ No newline at end of file +/Bots/.vs \ No newline at end of file From 7c6401e015fa201da99c3237c8e93299f9b87e96 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 21:49:47 +0200 Subject: [PATCH 28/34] Restored gitignore from base repo --- .gitignore | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 49855e2..8a868f3 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,15 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store -/Bots/.vs \ No newline at end of file +_NCrunch* +/Bots/AleixBot/.vs +/Bots/ExampleTraderBot/.vs/ExampleTraderBot +/Source/.vs +/Build/NasdaqTrader.Bot.Core.dll +/Build/NasdaqTrader.CLI.dll +/Build/NasdaqTrader.CLI.exe +/Build/NasdaqTraderSystem.Core.dll +/Build/NasdaqTraderSystem.Html.dll +/Bots/AleixBot.Tests/AleixBot.Tests.csproj +/Bots/AleixBot.Tests/ListingPickerTests.cs +/Bots/AleixBot/Properties/launchSettings.json \ No newline at end of file From 29c29021c51a49b9147b4416b09de71c69513bdd Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 21:52:14 +0200 Subject: [PATCH 29/34] Removed lines added since original fork from gitignore --- .gitignore | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 8a868f3..d2880c1 100644 --- a/.gitignore +++ b/.gitignore @@ -131,15 +131,4 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store -_NCrunch* -/Bots/AleixBot/.vs -/Bots/ExampleTraderBot/.vs/ExampleTraderBot -/Source/.vs -/Build/NasdaqTrader.Bot.Core.dll -/Build/NasdaqTrader.CLI.dll -/Build/NasdaqTrader.CLI.exe -/Build/NasdaqTraderSystem.Core.dll -/Build/NasdaqTraderSystem.Html.dll -/Bots/AleixBot.Tests/AleixBot.Tests.csproj -/Bots/AleixBot.Tests/ListingPickerTests.cs -/Bots/AleixBot/Properties/launchSettings.json \ No newline at end of file +_NCrunch* \ No newline at end of file From 37ae8878085268368798b2fbba28ad50581b63f7 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 21:53:46 +0200 Subject: [PATCH 30/34] Shortened unnecessarily long NullLogger file --- Bots/YurritBot/Logging/NullLogger.cs | 35 +++++----------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/Bots/YurritBot/Logging/NullLogger.cs b/Bots/YurritBot/Logging/NullLogger.cs index f60ee1c..abf3c4d 100644 --- a/Bots/YurritBot/Logging/NullLogger.cs +++ b/Bots/YurritBot/Logging/NullLogger.cs @@ -2,33 +2,10 @@ public class NullLogger : ILogger { - public void Erase() - { - - } - - public void Initialize() - { - - } - - public void Log(string text) - { - - } - - public void LogHeader1(string text) - { - - } - - public void LogHeader2(string text) - { - - } - - public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) - { - - } + public void Erase() { } + public void Initialize() { } + public void Log(string text) { } + public void LogHeader1(string text) { } + public void LogHeader2(string text) { } + public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) { } } From e974484d5a2f94f35fc817232e824a0eb3c96775 Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 21:54:31 +0200 Subject: [PATCH 31/34] Renamed TextLogger class to match file name --- Bots/YurritBot/Logging/TextLogger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bots/YurritBot/Logging/TextLogger.cs b/Bots/YurritBot/Logging/TextLogger.cs index 4d4a4f1..52b6f18 100644 --- a/Bots/YurritBot/Logging/TextLogger.cs +++ b/Bots/YurritBot/Logging/TextLogger.cs @@ -1,6 +1,6 @@ namespace YurritBot.Logging; -public class FileLogger(string logFilePath) : ILogger +public class TextLogger(string logFilePath) : ILogger { private readonly string logFilePath = logFilePath; From 5f3099258d380354ad149d452373328478d9e09c Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Wed, 3 Sep 2025 21:55:47 +0200 Subject: [PATCH 32/34] Actually put something in the ITradeHandler --- Bots/YurritBot/TradeHandlers/ITradeHandler.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Bots/YurritBot/TradeHandlers/ITradeHandler.cs b/Bots/YurritBot/TradeHandlers/ITradeHandler.cs index 01733f2..e4b84ce 100644 --- a/Bots/YurritBot/TradeHandlers/ITradeHandler.cs +++ b/Bots/YurritBot/TradeHandlers/ITradeHandler.cs @@ -2,5 +2,6 @@ { public interface ITradeHandler { + void ExecuteStrategy(); } } \ No newline at end of file From 7b374ed65c59ddab347ccc3798c796d77ab03e7b Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Fri, 5 Sep 2025 13:23:39 +0200 Subject: [PATCH 33/34] Merged main repo changes into fork --- .gitignore | 2 +- Bots/AleixBot/AleixBot.cs | 70 ++++++++++++++- Bots/AleixBot/AleixBot.sln | 14 ++- Bots/AleixBot/ListingPicker.cs | 89 +++++++++++++++++++ Bots/AleixBot/Models/IntermediateListing.cs | 5 ++ Bots/AleixBot/Models/Period.cs | 3 + .../Models/WindowedSumPerListingPerDate.cs | 3 + Bots/DennisBot/DennisMoneyBot.csproj | 23 +++++ Bots/DennisBot/DennisMoneyBot.sln | 25 ++++++ Bots/DennisBot/ITrader.cs | 7 ++ Bots/DennisBot/MoneyBot.cs | 18 ++++ Bots/DennisBot/StolenDateExtensions.cs | 87 ++++++++++++++++++ Bots/DennisBot/Traders/DayTrader.cs | 45 ++++++++++ Bots/DennisBot/Traders/ExperimentalTrader.cs | 52 +++++++++++ Bots/DennisBot/Utility/DateCalculator.cs | 23 +++++ Bots/DennisBot/Utility/PriceCalculator.cs | 33 +++++++ Bots/RafBot/RafBot.cs | 45 ++++++++++ Bots/RafBot/RafBot.csproj | 23 +++++ Bots/YurritBot/TradeHandlers/Buyer.cs | 2 +- 19 files changed, 565 insertions(+), 4 deletions(-) create mode 100644 Bots/AleixBot/ListingPicker.cs create mode 100644 Bots/AleixBot/Models/IntermediateListing.cs create mode 100644 Bots/AleixBot/Models/Period.cs create mode 100644 Bots/AleixBot/Models/WindowedSumPerListingPerDate.cs create mode 100644 Bots/DennisBot/DennisMoneyBot.csproj create mode 100644 Bots/DennisBot/DennisMoneyBot.sln create mode 100644 Bots/DennisBot/ITrader.cs create mode 100644 Bots/DennisBot/MoneyBot.cs create mode 100644 Bots/DennisBot/StolenDateExtensions.cs create mode 100644 Bots/DennisBot/Traders/DayTrader.cs create mode 100644 Bots/DennisBot/Traders/ExperimentalTrader.cs create mode 100644 Bots/DennisBot/Utility/DateCalculator.cs create mode 100644 Bots/DennisBot/Utility/PriceCalculator.cs create mode 100644 Bots/RafBot/RafBot.cs create mode 100644 Bots/RafBot/RafBot.csproj diff --git a/.gitignore b/.gitignore index d2880c1..c4c704b 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,4 @@ $RECYCLE.BIN/ # Mac desktop service store files .DS_Store -_NCrunch* \ No newline at end of file +_NCrunch* diff --git a/Bots/AleixBot/AleixBot.cs b/Bots/AleixBot/AleixBot.cs index 28c230f..81100e1 100644 --- a/Bots/AleixBot/AleixBot.cs +++ b/Bots/AleixBot/AleixBot.cs @@ -1,10 +1,16 @@ using NasdaqTrader.Bot.Core; +using System.Diagnostics; +using System.Diagnostics.Metrics; + namespace AleixBot; public class AleixBot : ITraderBot { public string CompanyName => "Beginner Investments"; + public static ListingPicker _listingPicker = null; + private static int _counter = 0; + private const int WINDOWSIZE = 3; public async Task DoTurn(ITraderSystemContext systemContext) { @@ -13,6 +19,68 @@ public async Task DoTurn(ITraderSystemContext systemContext) var currentDate = systemContext.CurrentDate; var tradesLeft = systemContext.GetTradesLeftForToday(this); - systemContext.BuyStock(this, listings[0], 1); + if (_listingPicker == null) + { + InitializeListingPicker(listings, WINDOWSIZE); + } + + if (_counter % WINDOWSIZE == 0 || _counter == 0) + { + //TODO implement while loop for trading + if (tradesLeft <= 0) + { + _counter++; + return; + } + + if (systemContext.GetHoldings(this).Any() && tradesLeft > 0) + { + tradesLeft = Sell(systemContext, tradesLeft); + } + + cash = systemContext.GetCurrentCash(this); + + if (tradesLeft > 0 && cash > 0) + { + tradesLeft = Buy(systemContext, cash, currentDate, tradesLeft); + } + } + + _counter++; + } + + private int Buy(ITraderSystemContext systemContext, decimal cash, DateOnly currentDate, int tradesLeft) + { + var listing = _listingPicker.GetXBestListingForDate(1, currentDate, cash); + var pricePoint = listing?.PricePoints.FirstOrDefault(l => l.Date == currentDate); + + if (pricePoint is not null && cash >= pricePoint.Price) + { + systemContext.BuyStock(this, listing, (int)(cash / pricePoint.Price)); + tradesLeft--; + } + + return tradesLeft; + } + + private int Sell(ITraderSystemContext systemContext, int tradesLeft) + { + var holding = systemContext.GetHoldings(this).OrderByDescending(h => h.Amount).FirstOrDefault(); + if (holding != null) + { + systemContext.SellStock(this, holding.Listing, holding.Amount); + tradesLeft--; + } + + return tradesLeft; + } + + private static void InitializeListingPicker(System.Collections.ObjectModel.ReadOnlyCollection listings, int windowSize) + { + var sw = new Stopwatch(); + sw.Start(); + _listingPicker = new ListingPicker(listings, windowSize); + sw.Stop(); + Debug.WriteLine($"Building ListingPicker took {sw.ElapsedMilliseconds} milliseconds"); } } \ No newline at end of file diff --git a/Bots/AleixBot/AleixBot.sln b/Bots/AleixBot/AleixBot.sln index 9fb532a..f9a0d2a 100644 --- a/Bots/AleixBot/AleixBot.sln +++ b/Bots/AleixBot/AleixBot.sln @@ -1,10 +1,13 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.12.35527.113 d17.12 +VisualStudioVersion = 17.12.35527.113 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AleixBot", "AleixBot.csproj", "{4EED0EB0-3BBC-41E5-A1F1-AB1C2A9DDB36}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AleixBot.Tests", "..\AleixBot.Tests\AleixBot.Tests.csproj", "{716FB5B8-EB7F-4FC1-90C7-7FD2FAE3DBA4}" +EndProject + Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,8 +18,17 @@ Global {4EED0EB0-3BBC-41E5-A1F1-AB1C2A9DDB36}.Debug|Any CPU.Build.0 = Debug|Any CPU {4EED0EB0-3BBC-41E5-A1F1-AB1C2A9DDB36}.Release|Any CPU.ActiveCfg = Release|Any CPU {4EED0EB0-3BBC-41E5-A1F1-AB1C2A9DDB36}.Release|Any CPU.Build.0 = Release|Any CPU + {716FB5B8-EB7F-4FC1-90C7-7FD2FAE3DBA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {716FB5B8-EB7F-4FC1-90C7-7FD2FAE3DBA4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {716FB5B8-EB7F-4FC1-90C7-7FD2FAE3DBA4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {716FB5B8-EB7F-4FC1-90C7-7FD2FAE3DBA4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C00082EE-EABB-41B9-A550-6A1DEB725E5E} + EndGlobalSection EndGlobal diff --git a/Bots/AleixBot/ListingPicker.cs b/Bots/AleixBot/ListingPicker.cs new file mode 100644 index 0000000..fa8abf4 --- /dev/null +++ b/Bots/AleixBot/ListingPicker.cs @@ -0,0 +1,89 @@ +using AleixBot.Models; +using NasdaqTrader.Bot.Core; +using System.Collections.ObjectModel; + +namespace AleixBot; + +public class ListingPicker +{ + public ReadOnlyCollection Listings { get; } + public int WindowSize { get; } + public Collection IntermediateListings { get; } = []; + public List WindowedListingSumPerDate = []; + + private List _periods = []; + + public ListingPicker(ReadOnlyCollection listings, int windowSize) + { + Listings = listings; + WindowSize = windowSize; + ConstructIntermediateDictionary(); + } + + public void ConstructIntermediateDictionary() + { + + foreach (var listing in Listings) + { + for (int i = 0; i < listing.PricePoints.Length - 2; i++) + { + var date = listing.PricePoints[i].Date; + var priceDifference = listing.PricePoints[i + 1].Price - listing.PricePoints[i].Price; + IntermediateListings.Add(new IntermediateListing(listing.Name, date, priceDifference, listing.PricePoints[i].Price)); + } + } + + var allDates = IntermediateListings.Select(il => il.Date); + if (!allDates.Any()) return; + + DateOnly begindate = allDates.Min(); + DateOnly endDate = allDates.Max(); + + var groupedIntermediateListings = IntermediateListings + .GroupBy(il => il.Name) + .ToDictionary(g => g.Key, g => g.ToList()); + + DateOnly j = begindate; + while (j.AddDays(WindowSize) < endDate) + { + _periods.Add(new Period(j, j.AddDays(WindowSize))); + j = j.AddDays(WindowSize); + } + + foreach (var (name, listings) in groupedIntermediateListings) + { + foreach (var period in _periods) + { + // Filter once per group and period + var windowSum = listings + .Where(il => il.Date >= period.Start && il.Date <= period.End) + .Sum(s => s.PriceDifference); + + var priceOnStartDate = listings + .Where(il => il.Date == period.Start) + .Select(il => il.Price) + .FirstOrDefault(); + + WindowedListingSumPerDate.Add(new WindowedSumPerListingPerDate(period.Start, name, windowSum, priceOnStartDate)); + } + } + } + + public IStockListing? GetXBestListingForDate(int number, DateOnly date, decimal cash) + { + var periodForDate = _periods.Where(p => p.Start <= date && p.End >= date).FirstOrDefault(); + + if (periodForDate == null) + { + return null; + } + + var bestListingName = WindowedListingSumPerDate + .Where(wl => wl.Date == periodForDate.Start && wl.PriceOnStartDate < cash) + .OrderByDescending(wl => wl.Sum) + .Select(wl => wl.Name) + .FirstOrDefault(); + + return Listings.FirstOrDefault(l => l.Name.Equals(bestListingName)); + } +} \ No newline at end of file diff --git a/Bots/AleixBot/Models/IntermediateListing.cs b/Bots/AleixBot/Models/IntermediateListing.cs new file mode 100644 index 0000000..1321ee4 --- /dev/null +++ b/Bots/AleixBot/Models/IntermediateListing.cs @@ -0,0 +1,5 @@ +using NasdaqTrader.Bot.Core; + +namespace AleixBot.Models; + +public record IntermediateListing(string Name, DateOnly Date, decimal PriceDifference, decimal Price); \ No newline at end of file diff --git a/Bots/AleixBot/Models/Period.cs b/Bots/AleixBot/Models/Period.cs new file mode 100644 index 0000000..8cdfc81 --- /dev/null +++ b/Bots/AleixBot/Models/Period.cs @@ -0,0 +1,3 @@ +namespace AleixBot.Models; + +internal record Period(DateOnly Start, DateOnly End); \ No newline at end of file diff --git a/Bots/AleixBot/Models/WindowedSumPerListingPerDate.cs b/Bots/AleixBot/Models/WindowedSumPerListingPerDate.cs new file mode 100644 index 0000000..7538119 --- /dev/null +++ b/Bots/AleixBot/Models/WindowedSumPerListingPerDate.cs @@ -0,0 +1,3 @@ +namespace AleixBot.Models; + +public record WindowedSumPerListingPerDate(DateOnly Date, string Name, decimal Sum, decimal PriceOnStartDate); \ No newline at end of file diff --git a/Bots/DennisBot/DennisMoneyBot.csproj b/Bots/DennisBot/DennisMoneyBot.csproj new file mode 100644 index 0000000..dda88a4 --- /dev/null +++ b/Bots/DennisBot/DennisMoneyBot.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + + + ..\..\Build\Bots + false + + + ..\..\Build\Bots + false + + + + ..\..\Build\NasdaqTrader.Bot.Core.dll + + + + diff --git a/Bots/DennisBot/DennisMoneyBot.sln b/Bots/DennisBot/DennisMoneyBot.sln new file mode 100644 index 0000000..0e0c5e5 --- /dev/null +++ b/Bots/DennisBot/DennisMoneyBot.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36221.1 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DennisMoneyBot", "DennisMoneyBot.csproj", "{D477DB95-F8AC-0D1D-C1CF-1B7D73E0C2BA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D477DB95-F8AC-0D1D-C1CF-1B7D73E0C2BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D477DB95-F8AC-0D1D-C1CF-1B7D73E0C2BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D477DB95-F8AC-0D1D-C1CF-1B7D73E0C2BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D477DB95-F8AC-0D1D-C1CF-1B7D73E0C2BA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CC4A2F4E-7123-498D-83E9-1206EC1B00EF} + EndGlobalSection +EndGlobal diff --git a/Bots/DennisBot/ITrader.cs b/Bots/DennisBot/ITrader.cs new file mode 100644 index 0000000..d3dcf5b --- /dev/null +++ b/Bots/DennisBot/ITrader.cs @@ -0,0 +1,7 @@ +using NasdaqTrader.Bot.Core; + +namespace DennisMoneyBot; +internal interface ITrader +{ + void DoTrades(ITraderSystemContext systemContext, ITraderBot bot); +} diff --git a/Bots/DennisBot/MoneyBot.cs b/Bots/DennisBot/MoneyBot.cs new file mode 100644 index 0000000..a481412 --- /dev/null +++ b/Bots/DennisBot/MoneyBot.cs @@ -0,0 +1,18 @@ +using DennisMoneyBot.Traders; + +using NasdaqTrader.Bot.Core; + +namespace DennisMoneyBot; + +public class MoneyBot : ITraderBot +{ + public string CompanyName => "Dennis Makes It Rain"; + + //ITrader _selectedTrader = new DayTrader(); + ITrader _selectedTrader = new ExperimentalTrader(); + + public async Task DoTurn(ITraderSystemContext systemContext) + { + _selectedTrader.DoTrades(systemContext, this); + } +} \ No newline at end of file diff --git a/Bots/DennisBot/StolenDateExtensions.cs b/Bots/DennisBot/StolenDateExtensions.cs new file mode 100644 index 0000000..2d448d7 --- /dev/null +++ b/Bots/DennisBot/StolenDateExtensions.cs @@ -0,0 +1,87 @@ +namespace DennisMoneyBot; +internal static class StolenDateExtensions +{ + /// + /// Determines if this date is a federal holiday. + /// + /// This date + /// True if this date is a federal holiday + public static bool IsFederalHoliday(this DateOnly date) + { + // to ease typing + int nthWeekDay = (int)(Math.Ceiling((double)date.Day / 7.0d)); + DayOfWeek dayName = date.DayOfWeek; + bool isThursday = dayName == DayOfWeek.Thursday; + bool isFriday = dayName == DayOfWeek.Friday; + bool isMonday = dayName == DayOfWeek.Monday; + bool isWeekend = dayName == DayOfWeek.Saturday || dayName == DayOfWeek.Sunday; + + + //Junteeth + if (new DateOnly(date.Year, 6, 19) == date) return true; + //good friday + if (DateOnly.FromDateTime(EasterSunday(date.Year)).AddDays(-2) == date) return true; + + // New Years Day (Jan 1, or preceding Friday/following Monday if weekend) + if ((date.Month == 12 && date.Day == 31 && isFriday) || + (date.Month == 1 && date.Day == 1 && !isWeekend) || + (date.Month == 1 && date.Day == 2 && isMonday)) return true; + + // MLK day (3rd monday in January) + if (date.Month == 1 && isMonday && nthWeekDay == 3) return true; + + // President’s Day (3rd Monday in February) + if (date.Month == 2 && isMonday && nthWeekDay == 3) return true; + + // Memorial Day (Last Monday in May) + if (date.Month == 5 && isMonday && date.AddDays(7).Month == 6) return true; + + // Independence Day (July 4, or preceding Friday/following Monday if weekend) + if ((date.Month == 7 && date.Day == 3 && isFriday) || + (date.Month == 7 && date.Day == 4 && !isWeekend) || + (date.Month == 7 && date.Day == 5 && isMonday)) return true; + + // Labor Day (1st Monday in September) + if (date.Month == 9 && isMonday && nthWeekDay == 1) return true; + + // Columbus Day (2nd Monday in October) + if (date.Month == 10 && isMonday && nthWeekDay == 2) return true; + + // Veteran’s Day (November 11, or preceding Friday/following Monday if weekend)) + if ((date.Month == 11 && date.Day == 10 && isFriday) || + (date.Month == 11 && date.Day == 11 && !isWeekend) || + (date.Month == 11 && date.Day == 12 && isMonday)) return true; + + // Thanksgiving Day (4th Thursday in November) + if (date.Month == 11 && isThursday && nthWeekDay == 4) return true; + + // Christmas Day (December 25, or preceding Friday/following Monday if weekend)) + if ((date.Month == 12 && date.Day == 24 && isFriday) || + (date.Month == 12 && date.Day == 25 && !isWeekend) || + (date.Month == 12 && date.Day == 26 && isMonday)) return true; + + return false; + } + + public static DateTime EasterSunday(int year) + { + int day = 0; + int month = 0; + + int g = year % 19; + int c = year / 100; + int h = (c - (int)(c / 4) - (int)((8 * c + 13) / 25) + 19 * g + 15) % 30; + int i = h - (int)(h / 28) * (1 - (int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11)); + + day = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28; + month = 3; + + if (day > 31) + { + month++; + day -= 31; + } + + return new DateTime(year, month, day); + } +} diff --git a/Bots/DennisBot/Traders/DayTrader.cs b/Bots/DennisBot/Traders/DayTrader.cs new file mode 100644 index 0000000..0ec4d11 --- /dev/null +++ b/Bots/DennisBot/Traders/DayTrader.cs @@ -0,0 +1,45 @@ +using DennisMoneyBot.Utility; + +using NasdaqTrader.Bot.Core; + +namespace DennisMoneyBot.Traders; +internal class DayTrader : ITrader +{ + public void DoTrades(ITraderSystemContext systemContext, ITraderBot bot) + { + var today = systemContext.CurrentDate; + var nextTradingDay = DateCalculator.GetNextValidDate(today); + var currentHoldings = systemContext.GetHoldings(bot); + + foreach (var holding in currentHoldings) + { + systemContext.SellStock(bot, holding.Listing, holding.Amount); + } + + if (nextTradingDay.Year != today.Year) + { + return; + } + + var bestOneDayTrades = systemContext.GetListings() + .OrderByDescending(listing => + listing.PricePoints.FirstOrDefault(p => p.Date == nextTradingDay)?.Price - listing.PricePoints.FirstOrDefault(p => p.Date == today)?.Price); + + int tradesLeft = 2; + foreach (var listing in bestOneDayTrades) + { + int amountToBuy = (int)Math.Min(1000, Math.Floor(systemContext.GetCurrentCash(bot) / listing.PricePoints.First(p => p.Date == today).Price)); + if (amountToBuy == 0) + { + continue; + } + + systemContext.BuyStock(bot, listing, amountToBuy); + + if (--tradesLeft < 1) + { + break; + } + } + } +} diff --git a/Bots/DennisBot/Traders/ExperimentalTrader.cs b/Bots/DennisBot/Traders/ExperimentalTrader.cs new file mode 100644 index 0000000..6f7770e --- /dev/null +++ b/Bots/DennisBot/Traders/ExperimentalTrader.cs @@ -0,0 +1,52 @@ +using DennisMoneyBot.Utility; + +using NasdaqTrader.Bot.Core; + +namespace DennisMoneyBot.Traders; +internal class ExperimentalTrader : ITrader +{ + public void DoTrades(ITraderSystemContext systemContext, ITraderBot bot) + { + int windowSize = 1; + decimal saleCutoffRatio = 1.15M; + int daysToIgnoreSaleRules = 14; + int minimalSaleValue = 800; + + var today = systemContext.CurrentDate; + var nextTradingDay = DateCalculator.GetBusinessDaysInTheFuture(today, windowSize); + IHolding[]? currentHoldings = systemContext.GetHoldings(bot); + + foreach (var holding in currentHoldings) + { + if ((PriceCalculator.GetValueOfHoldingOnDate(holding, today) > minimalSaleValue) + && + (PriceCalculator.GetPricePointRatio(holding.Listing, today, nextTradingDay) < saleCutoffRatio + || (systemContext.StartDate.AddDays(daysToIgnoreSaleRules) > systemContext.CurrentDate))) + { + systemContext.SellStock(bot, holding.Listing, holding.Amount); + } + } + + var bestOneDayTrades = systemContext.GetListings() + .OrderByDescending(listing => + PriceCalculator.CalculateProfitMargin(systemContext, bot, listing, today, nextTradingDay)); + + int tradesLeft = systemContext.GetTradesLeftForToday(bot); + foreach (var listing in bestOneDayTrades) + { + int amountToBuy = (int)Math.Min(1000, Math.Floor(systemContext.GetCurrentCash(bot) / PriceCalculator.GetPriceForListingOnDate(listing, today))); + if (amountToBuy == 0) + { + continue; + } + + systemContext.BuyStock(bot, listing, amountToBuy); + + if (--tradesLeft < 1) + { + break; + } + } + // nog iets doen met voorrang geven aan dingen die gewoon veel waard zijn en niet meer gaan dalen, zodat dat kan accumuleren + } +} diff --git a/Bots/DennisBot/Utility/DateCalculator.cs b/Bots/DennisBot/Utility/DateCalculator.cs new file mode 100644 index 0000000..d4925d1 --- /dev/null +++ b/Bots/DennisBot/Utility/DateCalculator.cs @@ -0,0 +1,23 @@ +namespace DennisMoneyBot.Utility; +internal static class DateCalculator +{ + public static DateOnly GetNextValidDate(DateOnly currentDate) + { + DateOnly nextDate = currentDate.AddDays(1); + while (nextDate.DayOfWeek == DayOfWeek.Saturday || nextDate.DayOfWeek == DayOfWeek.Sunday || nextDate.IsFederalHoliday()) + { + nextDate = nextDate.AddDays(1); + } + return nextDate; + } + + public static DateOnly GetBusinessDaysInTheFuture(DateOnly currentDate, int amountOfDays) + { + var nextDay = currentDate; + for (int i = 0; i < amountOfDays; i++) + { + nextDay = GetNextValidDate(nextDay); + } + return nextDay; + } +} diff --git a/Bots/DennisBot/Utility/PriceCalculator.cs b/Bots/DennisBot/Utility/PriceCalculator.cs new file mode 100644 index 0000000..5e7256b --- /dev/null +++ b/Bots/DennisBot/Utility/PriceCalculator.cs @@ -0,0 +1,33 @@ +using NasdaqTrader.Bot.Core; + +namespace DennisMoneyBot.Utility; +internal static class PriceCalculator +{ + public static decimal GetPriceForListingOnDate(IStockListing listing, DateOnly referenceDate) + { + return listing.PricePoints.FirstOrDefault(p => p.Date == referenceDate)?.Price ?? 0; + } + + public static decimal GetPricePointRatio(IStockListing listing, DateOnly today, DateOnly nextReferenceDay) + { + return GetPriceForListingOnDate(listing, nextReferenceDay) / GetPriceForListingOnDate(listing, today); + } + + public static decimal GetValueOfHoldingOnDate(IHolding holding, DateOnly referenceDate) + { + return holding.Amount * GetPriceForListingOnDate(holding.Listing, referenceDate); + } + + public static decimal CalculateProfitMargin(ITraderSystemContext systemContext, ITraderBot bot, IStockListing listing, DateOnly today, DateOnly nextReferenceDay) + { + var nextPrice = GetPriceForListingOnDate(listing, nextReferenceDay); + var currentPrice = GetPriceForListingOnDate(listing, today); + var priceDifference = nextPrice - currentPrice; + + var amountToBuy = + currentPrice == 0 + ? 0 + : Math.Min(1000, Math.Floor(systemContext.GetCurrentCash(bot) / currentPrice)); + return amountToBuy * priceDifference; + } +} diff --git a/Bots/RafBot/RafBot.cs b/Bots/RafBot/RafBot.cs new file mode 100644 index 0000000..e9acc39 --- /dev/null +++ b/Bots/RafBot/RafBot.cs @@ -0,0 +1,45 @@ +using NasdaqTrader.Bot.Core; + +namespace RafBot; + +public class RafBot : ITraderBot +{ + public string CompanyName => "Scrooge McChicken"; + + public async Task DoTurn(ITraderSystemContext systemContext) + { + var tradesLeft = 5; + foreach (var holding in systemContext.GetHoldings(this)) + { + if (holding.Amount > 0) + { + systemContext.SellStock(this, holding.Listing, holding.Amount); + tradesLeft--; + } + } + + var cash = systemContext.GetCurrentCash(this); + var listings = systemContext.GetListings().Where(listing => + listing.PricePoints.Any(pricePoint => pricePoint.Date == systemContext.CurrentDate) + && listing.PricePoints.Any(pricePoint => pricePoint.Date > systemContext.CurrentDate)) + .OrderBy(listing => + (listing.PricePoints.FirstOrDefault(pricePoint => pricePoint.Date == systemContext.CurrentDate)?.Price - + listing.PricePoints.FirstOrDefault(pricePoint => pricePoint.Date > systemContext.CurrentDate)?.Price) + * Math.Min(1000, cash / listing.PricePoints.FirstOrDefault(pricePoint => pricePoint.Date == systemContext.CurrentDate).Price)); + + foreach (var listing in listings) + { + if (tradesLeft == 0) + { + break; + } + + int amount = Math.Min(1000, (int)(systemContext.GetCurrentCash(this) / listing.PricePoints.FirstOrDefault(pricePoint => pricePoint.Date == systemContext.CurrentDate).Price)); + if (amount > 0) + { + systemContext.BuyStock(this, listing, amount); + tradesLeft--; + } + } + } +} \ No newline at end of file diff --git a/Bots/RafBot/RafBot.csproj b/Bots/RafBot/RafBot.csproj new file mode 100644 index 0000000..dda88a4 --- /dev/null +++ b/Bots/RafBot/RafBot.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + + + ..\..\Build\Bots + false + + + ..\..\Build\Bots + false + + + + ..\..\Build\NasdaqTrader.Bot.Core.dll + + + + diff --git a/Bots/YurritBot/TradeHandlers/Buyer.cs b/Bots/YurritBot/TradeHandlers/Buyer.cs index 6a69880..fa2f796 100644 --- a/Bots/YurritBot/TradeHandlers/Buyer.cs +++ b/Bots/YurritBot/TradeHandlers/Buyer.cs @@ -24,7 +24,7 @@ public void ExecuteStrategy() var rankedListings = RankListingsByExpectedIncrease(traderBot, systemContext, logger); stopwatch.Stop(); logger.Log($"- took {stopwatch.Elapsed}"); - + logger.Log($"Buying"); var buyCalculator = new BuyCalculator(maximumBuyAmountPerStock); foreach (var listing in rankedListings) From d5f71c3664aa5eaf3edc14bc2aa38de48148a3fd Mon Sep 17 00:00:00 2001 From: YurritAvonds Date: Fri, 5 Sep 2025 13:27:26 +0200 Subject: [PATCH 34/34] Fixed merge issues --- Bots/YurritBot/Buyer.cs | 54 ---------------------- Bots/YurritBot/ITrader.cs | 16 ------- Bots/YurritBot/Logging/FileLogger.cs | 15 ------ Bots/YurritBot/Seller.cs | 41 ---------------- Bots/YurritBotTests/DateCalculatorTests.cs | 10 +--- 5 files changed, 2 insertions(+), 134 deletions(-) delete mode 100644 Bots/YurritBot/Buyer.cs delete mode 100644 Bots/YurritBot/ITrader.cs delete mode 100644 Bots/YurritBot/Logging/FileLogger.cs delete mode 100644 Bots/YurritBot/Seller.cs diff --git a/Bots/YurritBot/Buyer.cs b/Bots/YurritBot/Buyer.cs deleted file mode 100644 index 1847e13..0000000 --- a/Bots/YurritBot/Buyer.cs +++ /dev/null @@ -1,54 +0,0 @@ -using NasdaqTrader.Bot.Core; -using System.Reflection; -using YurritBot.Logging; - -namespace YurritBot; - -class Buyer(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) : ITrader -{ - public ITraderBot TraderBot { get; private set; } = traderBot; - public ITraderSystemContext SystemContext { get; private set; } = systemContext; - public int IndexToday { get; private set; } = indexToday; - public int IndexReferenceDay { get; private set; } = indexReferenceDay; - public ILogger Logger { get; private set; } = logger; - - private const decimal cashlowerLimit = 100; - private const int maxBuyAmountPerStock = 1000; - - public void ExecuteStrategy() - { - var currentCash = SystemContext.GetCurrentCash(TraderBot); - var listingsByExpectedIncrease = SystemContext.GetListings() - .Where(listing => listing.PricePoints[IndexToday].Price <= currentCash) - .Where(listing => listing.PricePoints[IndexReferenceDay].Price / listing.PricePoints[IndexToday].Price > 1) - .OrderByDescending(listing => (listing.PricePoints[IndexReferenceDay].Price / listing.PricePoints[IndexToday].Price)); - - var buyCalculator = new BuyCalculator(maxBuyAmountPerStock); - - foreach (var listing in listingsByExpectedIncrease) - { - currentCash = SystemContext.GetCurrentCash(TraderBot); - if (SystemContext.GetTradesLeftForToday(TraderBot) <= 0 - || currentCash < cashlowerLimit) - { - return; - } - - var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount(currentCash, listing.PricePoints[IndexToday].Price); - - if (maxBuyAmount < 1) - { - continue; - } - - var success = SystemContext.BuyStock(TraderBot, listing, maxBuyAmount); - - Logger.LogTransaction( - category: success ? "" : "ERR", - ticker: listing.Ticker, - currentCash: SystemContext.GetCurrentCash(TraderBot), - pricePoint: listing.PricePoints[IndexToday].Price, - amount: maxBuyAmount); - } - } -} diff --git a/Bots/YurritBot/ITrader.cs b/Bots/YurritBot/ITrader.cs deleted file mode 100644 index b90d4e4..0000000 --- a/Bots/YurritBot/ITrader.cs +++ /dev/null @@ -1,16 +0,0 @@ -using NasdaqTrader.Bot.Core; -using YurritBot.Logging; - -namespace YurritBot -{ - internal interface ITrader - { - int IndexToday { get; } - int IndexReferenceDay { get; } - ILogger Logger { get; } - ITraderBot TraderBot { get; } - ITraderSystemContext SystemContext { get; } - - void ExecuteStrategy(); - } -} \ No newline at end of file diff --git a/Bots/YurritBot/Logging/FileLogger.cs b/Bots/YurritBot/Logging/FileLogger.cs deleted file mode 100644 index 934f99c..0000000 --- a/Bots/YurritBot/Logging/FileLogger.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace YurritBot.Logging; - -public class FileLogger : ILogger -{ - public void Log(string text) - { - var logFilePath = Path.Combine(AppContext.BaseDirectory, "yurritbot_errors.log"); - File.AppendAllText(logFilePath, $"{text}{Environment.NewLine}"); - } - - public void LogTransaction(string category, string ticker, decimal currentCash, decimal pricePoint, int amount) - { - Log($"- {category} {currentCash:F2} | {ticker} {amount} @ {pricePoint:F2}"); - } -} diff --git a/Bots/YurritBot/Seller.cs b/Bots/YurritBot/Seller.cs deleted file mode 100644 index c40f0e9..0000000 --- a/Bots/YurritBot/Seller.cs +++ /dev/null @@ -1,41 +0,0 @@ -using NasdaqTrader.Bot.Core; -using YurritBot.Logging; - -namespace YurritBot; - -class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, int indexToday, int indexReferenceDay, ILogger logger) : ITrader -{ - public ITraderBot TraderBot { get; private set; } = traderBot; - public ITraderSystemContext SystemContext { get; private set; } = systemContext; - public int IndexToday { get; private set; } = indexToday; - public int IndexReferenceDay { get; private set; } = indexReferenceDay; - public ILogger Logger { get; private set; } = logger; - - private const int cashLowerLimit = 10; - - public void ExecuteStrategy() - { - var holdings = SystemContext.GetHoldings(TraderBot); - if (holdings.Length == 0) - { - return; - } - - foreach (var holding in holdings) - { - if (holding.Amount == 0) - { - continue; - } - - var success = SystemContext.SellStock(TraderBot, holding.Listing, holding.Amount); - - Logger.LogTransaction( - category: success ? "" : "ERR", - ticker: holding.Listing.Ticker, - currentCash: SystemContext.GetCurrentCash(TraderBot), - pricePoint: holding.Listing.PricePoints[IndexToday].Price, - amount: holding.Amount); - } - } -} diff --git a/Bots/YurritBotTests/DateCalculatorTests.cs b/Bots/YurritBotTests/DateCalculatorTests.cs index 925ff9f..3ee82b5 100644 --- a/Bots/YurritBotTests/DateCalculatorTests.cs +++ b/Bots/YurritBotTests/DateCalculatorTests.cs @@ -1,12 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using YurritBot; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using FluentAssertions; using NUnit.Framework; -using FluentAssertions; +using System; namespace YurritBot.Tests {