diff --git a/.gitignore b/.gitignore index 2bcb9a8..c4c704b 100644 --- a/.gitignore +++ b/.gitignore @@ -132,14 +132,3 @@ $RECYCLE.BIN/ .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 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/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/Logging/HtmlLogger.cs b/Bots/YurritBot/Logging/HtmlLogger.cs new file mode 100644 index 0000000..e8adea9 --- /dev/null +++ b/Bots/YurritBot/Logging/HtmlLogger.cs @@ -0,0 +1,41 @@ +using static System.Net.Mime.MediaTypeNames; + +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); + } + + 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 679bcd9..d313b71 100644 --- a/Bots/YurritBot/Logging/ILogger.cs +++ b/Bots/YurritBot/Logging/ILogger.cs @@ -3,5 +3,10 @@ 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); + + 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 b73fbaf..abf3c4d 100644 --- a/Bots/YurritBot/Logging/NullLogger.cs +++ b/Bots/YurritBot/Logging/NullLogger.cs @@ -2,13 +2,10 @@ public class NullLogger : ILogger { - public void Log(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) { } } diff --git a/Bots/YurritBot/Logging/TextLogger.cs b/Bots/YurritBot/Logging/TextLogger.cs new file mode 100644 index 0000000..52b6f18 --- /dev/null +++ b/Bots/YurritBot/Logging/TextLogger.cs @@ -0,0 +1,40 @@ +namespace YurritBot.Logging; + +public class TextLogger(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) + { + File.AppendAllLines( + logFilePath, + [string.Empty, text, new string('=', text.Length)]); + } + + public void LogHeader2(string text) + { + File.AppendAllLines( + logFilePath, + [text, new string('-', text.Length)]); + } + + 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..3ec2cac --- /dev/null +++ b/Bots/YurritBot/Logging/style.css @@ -0,0 +1,26 @@ +body { + font-family: Arial, sans-serif; + background-color: #030303; + color: white; + margin: 0; + padding: 20px; +} + +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; +} 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 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/YurritBot/TradeHandlers/Buyer.cs b/Bots/YurritBot/TradeHandlers/Buyer.cs new file mode 100644 index 0000000..fa2f796 --- /dev/null +++ b/Bots/YurritBot/TradeHandlers/Buyer.cs @@ -0,0 +1,95 @@ +using NasdaqTrader.Bot.Core; +using System.Diagnostics; +using YurritBot.Logging; + +namespace YurritBot.TradeHandlers; + +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() + { + logger.LogHeader2($"Buy"); + + logger.Log($"€{systemContext.GetCurrentCash(traderBot):F2}"); + + 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 rankedListings) + { + if (systemContext.GetCurrentCash(traderBot) < cashlowerLimit) + { + logger.Log($"- cash below limit"); + return; + } + + if (systemContext.GetTradesLeftForToday(traderBot) <= 0) + { + logger.Log($"- no trades left"); + return; + } + + TryBuyListing(traderBot, systemContext, logger, buyCalculator, listing); + } + } + + private static IEnumerable RankListingsByExpectedIncrease(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) + { + var currentCash = systemContext.GetCurrentCash(traderBot); + + return systemContext.GetListings() + .Select(l => + { + 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; + return new + { + Listing = l, + PriceToday = priceToday, + PriceRatio = priceTomorrow / priceToday + }; + }) + .Where(x => x != null + && x.PriceToday > 0.0001m + && x.PriceToday <= currentCash + && x.PriceRatio > 1) + .OrderByDescending(x => x.PriceRatio) + .Select(x => x.Listing); + } + + private static void TryBuyListing(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger, + BuyCalculator buyCalculator, IStockListing listing) + { + var price = systemContext.GetPriceOnDay(listing); + var maxBuyAmount = buyCalculator.CalculateMaximuumBuyAmount( + currentCash: systemContext.GetCurrentCash(traderBot), + listingPrice: price); + var success = systemContext.BuyStock(traderBot, listing, maxBuyAmount); + + logger.LogTransaction( + category: success ? "- BUY" : "- BUY ERR", + ticker: listing.Ticker, + currentCash: systemContext.GetCurrentCash(traderBot), + pricePoint: price, + amount: maxBuyAmount); + } +} diff --git a/Bots/YurritBot/TradeHandlers/ITradeHandler.cs b/Bots/YurritBot/TradeHandlers/ITradeHandler.cs new file mode 100644 index 0000000..e4b84ce --- /dev/null +++ b/Bots/YurritBot/TradeHandlers/ITradeHandler.cs @@ -0,0 +1,7 @@ +namespace YurritBot.TradeHandlers +{ + public interface ITradeHandler + { + void ExecuteStrategy(); + } +} \ No newline at end of file diff --git a/Bots/YurritBot/TradeHandlers/Seller.cs b/Bots/YurritBot/TradeHandlers/Seller.cs new file mode 100644 index 0000000..fa42c3e --- /dev/null +++ b/Bots/YurritBot/TradeHandlers/Seller.cs @@ -0,0 +1,48 @@ +using NasdaqTrader.Bot.Core; +using YurritBot.Logging; + +namespace YurritBot.TradeHandlers; + +public class Seller(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger) : ITradeHandler +{ + private readonly ITraderBot traderBot = traderBot; + private readonly ITraderSystemContext systemContext = systemContext; + private readonly ILogger logger = logger; + + public void ExecuteStrategy() + { + logger.LogHeader2($"Sell"); + + logger.Log($"€{systemContext.GetCurrentCash(traderBot):F2}"); + + var holdings = systemContext.GetHoldings(traderBot) + .Where(holding => holding.Amount > 0); + if (!holdings.Any()) + { + logger.Log($"- no holdings"); + return; + } + + foreach (var holding in holdings) + { + TrySellHolding(traderBot, systemContext, logger, holding); + } + } + + private static void TrySellHolding(ITraderBot traderBot, ITraderSystemContext systemContext, ILogger logger, IHolding holding) + { + if (holding.Amount == 0) + { + return; + } + + var success = systemContext.SellStock(traderBot, holding.Listing, holding.Amount); + + logger.LogTransaction( + category: success ? "- SELL" : "- SELL ERR", + ticker: holding.Listing.Ticker, + currentCash: systemContext.GetCurrentCash(traderBot), + pricePoint: systemContext.GetPriceOnDay(holding.Listing), + amount: holding.Amount); + } +} 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 + + diff --git a/Bots/YurritBot/YurritTrader.cs b/Bots/YurritBot/YurritTrader.cs index e6789b0..9ad0a59 100644 --- a/Bots/YurritBot/YurritTrader.cs +++ b/Bots/YurritBot/YurritTrader.cs @@ -1,50 +1,29 @@ -using NasdaqTrader.Bot.Core; -using System.Diagnostics; +using NasdaqTrader.Bot.Core; using YurritBot.Logging; +using YurritBot.TradeHandlers; 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) { + //var logger = new HtmlLogger(Path.Combine(AppContext.BaseDirectory, logFileName)); var logger = new NullLogger(); - logger.Log($""); - logger.Log($"{systemContext.CurrentDate}"); - logger.Log($"========="); + if (!File.Exists(Path.Combine(AppContext.BaseDirectory, logFileName))) + { + logger.Initialize(); + } - int indexToday = new DateCalculator() - .DetermineDateIndex(systemContext.CurrentDate, systemContext.GetListings().First().PricePoints); + logger.LogHeader1($"{systemContext.CurrentDate}"); + logger.Log($"{DateTime.Now:dd-MMM-yyyy,hh:mm::ss.fff}"); - logger.Log($"- €{systemContext.GetCurrentCash(this):F2}"); + new Seller(this, systemContext, logger).ExecuteStrategy(); + new Buyer(this, systemContext, logger).ExecuteStrategy(); - 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}"); } - - } \ No newline at end of file diff --git a/Bots/YurritBotTests/BuyCalculatorTests.cs b/Bots/YurritBotTests/BuyCalculatorTests.cs index 8e4d37a..1861f73 100644 --- a/Bots/YurritBotTests/BuyCalculatorTests.cs +++ b/Bots/YurritBotTests/BuyCalculatorTests.cs @@ -1,4 +1,4 @@ -using FluentAssertions; +using FluentAssertions; using NUnit.Framework; using YurritBot; @@ -8,15 +8,15 @@ namespace YurritBotTests; public class BuyCalculatorTests { [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 BuyCalculator(1000); // 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 + + +