From 9b68d71bf3d7e421f4abe0841dbc6836e44c4596 Mon Sep 17 00:00:00 2001 From: Dennis D'Ooghe Date: Fri, 1 Aug 2025 17:44:38 +0200 Subject: [PATCH] Dennis makes a bot of zo --- 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 ++++++++ 9 files changed, 313 insertions(+) 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 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; + } +}