Module:BassaridMarketActivity: Difference between revisions

From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs)
No edit summary
NewZimiaGov (talk | contribs)
No edit summary
Line 1: Line 1:
---------------------------------------------------------
---------------------------------------------------------
-- Module: BassaridiaDailyInvestorTable
-- Module: BassaridiaDailyInvestorTable
-- Generates a daily table of investor stock activity
-- Updates each day with new buy/sell, but uses the
-- and associated reasons for increases/decreases.
-- "previous day" simulation to set 'Stocks at Start'
-- to 'Stocks at End' from that previous day.
--
--
-- Each day:
-- NOTES:
-- • A unique random seed is derived from the current
-- 1) Because we do not store data externally, we
--    year and the day of the year (e.g. 2024 * 1000 + 45).
--    re-simulate "yesterday" each day with a slightly
-- • Every cell (stocks, value, reasons) auto-updates
--   different random seed. Then we use that to set the
--    but remains stable for that particular day.
--    starting stocks for "today."
-- 2) Everything is still stable for the current day, but
--    tomorrow it re-simulates again. Therefore, actual
--   day-to-day changes remain consistent in the script's
--    logic, *so long as* no additional changes are made.
---------------------------------------------------------
---------------------------------------------------------
local p = {}
local p = {}


---------------------------------------------------------
---------------------------------------------------------
-- 1. Calendar Function
-- 1. Calendar: Determine PSSC + daily seeds
--    Returns date in PSSC terms + numeric dayOfMonth, month
---------------------------------------------------------
---------------------------------------------------------
local function getCurrentDate()
local function getCurrentDate()
     local startDate = os.time({year = 1999, month = 8, day = 6})
     local startDate   = os.time({year=1999, month=8, day=6})
     local secondsInDay = 86400
     local secondsInDay= 86400
     local daysPerYear = 183
     local daysPerYear = 183


Line 26: Line 29:


     local yearFraction = totalDaysElapsed / daysPerYear
     local yearFraction = totalDaysElapsed / daysPerYear
     local psscYear = math.floor(yearFraction)
     local psscYear     = math.floor(yearFraction)
     local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1
     local dayOfYear   = math.floor((yearFraction - psscYear) * daysPerYear) + 1


    -- For demonstration, we do 3 months: 1..61, 62..122, 123..183
     local month, dayOfMonth
     local month, dayOfMonth
     if dayOfYear <= 61 then
     if dayOfYear <= 61 then
Line 42: Line 46:


     local dateString = string.format("%d/%d/%d [[PSSC]]", dayOfMonth, month, psscYear)
     local dateString = string.format("%d/%d/%d [[PSSC]]", dayOfMonth, month, psscYear)
     return dateString, dayOfMonth, month
     return dateString, psscYear, dayOfYear
end
end


---------------------------------------------------------
---------------------------------------------------------
-- 2. Investor Data
-- 2. Investor Data (Initial "Base" Stocks)
---------------------------------------------------------
---------------------------------------------------------
local investors = {
-- This table holds each investor's *base* stock count,
-- used only if we had no prior day. We'll override each
-- day using the "simulateYesterday" logic below.
---------------------------------------------------------
local baseInvestors = {
     {name = "Vaeringheim Regional Investor", stocks = 17424},
     {name = "Vaeringheim Regional Investor", stocks = 17424},
     {name = "Alpazkigz Regional Investor",  stocks = 16214},
     {name = "Alpazkigz Regional Investor",  stocks = 16214},
Line 63: Line 71:
---------------------------------------------------------
---------------------------------------------------------
local increaseReasons = {
local increaseReasons = {
     "Improved logistics at the General Port attracted new investors.",
     "Improved logistics attracted new investors.",
     "An influx of goods from Delphica boosted trade volume at the port.",
     "An influx of goods boosted trade volume.",
     "High demand for Noctic-Rabrev exports raised investor confidence in the port's performance.",
     "High demand for Noctic-Rabrev exports raised confidence.",
     "Completion of a new shipping lane connecting Nexa to the General Port increased activity.",
     "Completion of a new shipping lane increased activity.",
     "Successful negotiations with Erythros merchants expanded trade opportunities.",
     "Successful negotiations with Erythros merchants expanded trade.",
     "The recent Festival of Light in Vaeringheim drove significant port activity.",
     "A local festival drove significant port activity.",
     "High attendance at the Bassaridia Trade Symposium enhanced the port's reputation.",
     "High attendance at a major trade symposium enhanced the port's reputation.",
     "Discovery of new mineral resources near Acheron increased export potential through the port.",
     "Discovery of new mineral resources increased export potential.",
     "A surge in ceremonial wine exports from the Blood Vineyards bolstered port operations.",
     "A surge in ceremonial wine exports bolstered port operations.",
     "Efficient cargo handling innovations improved investor sentiment at the General Port.",
     "Efficient cargo handling innovations improved investor sentiment.",
     "Seasonal agricultural exports from the Plains of Jogi saw a sharp increase.",
     "Seasonal agricultural exports saw a sharp increase.",
     "High sales of pearl and abalone products boosted port activity.",
     "High sales of pearl and abalone products boosted port activity.",
     "Introduction of luxury goods from Tel-Amin attracted high-end investors.",
     "Introduction of new luxury goods attracted high-end investors.",
     "The expansion of the General Port's capacity in Suncliff enhanced its competitiveness.",
     "Expansion of the port's capacity improved its competitiveness.",
     "Improved relations with Maskmakers Guild merchants stabilized trade routes.",
     "Improved relations with local merchant guilds stabilized trade routes.",
     "An increase in fishing yields around Lake Morovia led to greater port activity.",
     "Increased fishing yields led to greater port activity.",
     "Enhanced security measures at the General Port reduced piracy concerns.",
     "Enhanced security measures reduced piracy concerns.",
     "Growing cultural appreciation for sacred Horehound exports boosted regional confidence.",
     "Growing cultural appreciation for exotic exports boosted confidence.",
     "Trade agreements with neighboring cities, including Saluria, expanded port usage.",
     "Trade agreements with neighboring cities expanded port usage.",
    "High demand for silk and luxury textiles from Seamstresses of Rouge improved port revenue.",
     "Launch of new fertilizer shipments attracted agricultural investors.",
    "The Moonlit Tide celebration increased ceremonial goods traffic through the port.",
     "Rising global interest in sacred resin exports spiked port activity.",
     "Launch of new Garganid fertilizer shipments attracted agricultural investors.",
     "Successful marketing campaigns highlighted the port's strategic importance.",
     "Rising global interest in sacred resin exports enhanced the port's activity.",
     "Completion of high-speed rail connections improved trade efficiency.",
     "Successful marketing campaigns highlighted the General Port's strategic importance.",
     "Completion of high-speed rail connections to key cities improved trade efficiency at the port."
}
}


local decreaseReasons = {
local decreaseReasons = {
     "Pirate raids in the Strait of Haifa disrupted shipments to the General Port.",
     "Pirate raids in the Strait disrupted shipments.",
     "Unfavorable weather conditions delayed agricultural exports from the Plains of Jogi.",
     "Unfavorable weather delayed agricultural exports.",
     "Labor shortages in the General Port caused delays in cargo handling.",
     "Labor shortages caused delays in cargo handling.",
     "A major fire in Salurian sacred groves reduced resin exports through the port.",
     "A major fire in supply regions reduced exports.",
     "Rising costs of Noctic-Rabrev cultivation impacted investor confidence.",
     "Rising costs impacted investor confidence.",
     "Unrest in Jangsong Province disrupted camel meat shipments to the port.",
     "Unrest in Jangsong Province disrupted shipments.",
     "Currency fluctuations in Vaeringheim affected trade stability at the General Port.",
     "Currency fluctuations affected trade stability.",
     "Economic sanctions on neighboring cities reduced export activity.",
     "Economic sanctions on neighbors reduced export activity.",
     "Decline in ceremonial artifact production reduced goods traffic through the port.",
     "Decline in ceremonial artifact production reduced goods traffic.",
     "Cultural disputes over Horehound cultivation caused delays in exports.",
     "Cultural disputes caused delays in exports.",
     "Counterfeit relics discovered in Nexa harmed the port's reputation.",
     "Counterfeit relics discovered harmed the port's reputation.",
     "High demand for fishing yields outpaced supply, reducing exports through the port.",
     "High demand outpaced supply, reducing exports.",
     "Decline in pearl quality from Anterran sources lowered investor interest.",
     "Decline in pearl quality lowered investor interest.",
     "Shortage of ceremonial wine from the Blood Vineyards hurt exports.",
     "Shortage of ceremonial wine hurt exports.",
     "Unrest among Maskmaker merchants disrupted key trade agreements.",
     "Unrest among key merchants disrupted major trade agreements.",
     "Reduced demand for silk textiles from the Seamstresses of Rouge impacted port activity.",
     "Reduced demand for local textiles impacted port activity.",
     "Collapse of a trade agreement with Tel-Amin reduced port volume.",
     "Collapse of a key trade deal reduced port volume.",
     "Seasonal labor shortages in agricultural hubs decreased port shipments.",
     "Seasonal labor shortages decreased shipments.",
     "Disruptions in Suncliff Fisheries slowed aquatic exports through the port.",
     "Alternate shipping routes bypassed the port.",
    "Discovery of alternative shipping routes bypassed the General Port.",
     "Economic downturn in a key region affected investor interest.",
     "Economic downturn in Lunalis Sancta affected investor interest.",
     "Piracy concerns in Haifan waters harmed trade confidence.",
     "Unresolved piracy concerns in Haifan waters harmed trade confidence.",
     "Failure to secure deals at a trade symposium reduced traffic.",
     "Failure to secure key trade deals during the Bassaridia Trade Symposium reduced traffic.",
     "Natural disasters destroyed key trade routes.",
     "Natural disasters near Acheron destroyed key trade routes to the port.",
     "Sabotage of port infrastructure caused significant delays.",
     "Sabotage of port infrastructure caused significant delays in shipments."
}
}


Line 124: Line 129:
     if stocksAtEnd > 12500 then
     if stocksAtEnd > 12500 then
         return "Tier I"
         return "Tier I"
     elseif stocksAtEnd >= 5000 and stocksAtEnd <= 12500 then
     elseif stocksAtEnd >= 5000 then
         return "Tier II"
         return "Tier II"
     else
     else
Line 132: Line 137:


---------------------------------------------------------
---------------------------------------------------------
-- 5. Daily Stock Changes
-- 5. Single-Day Simulation
---------------------------------------------------------
---------------------------------------------------------
local function simulateDailyActivity(investor)
local function simulateDailyActivity(stocksAtStart)
     local buyStocks = math.random(0, 500)
     local buyStocks = math.random(0, 500)
     local sellStocks = math.random(0, 500)
     local sellStocks = math.random(0, 500)


     investor.stocks = investor.stocks + buyStocks - sellStocks
     local stocksAtEnd = stocksAtStart + buyStocks - sellStocks
     return buyStocks, sellStocks
    local change = "Increased"
    if stocksAtEnd < stocksAtStart then
        change = "Decreased"
    elseif stocksAtEnd == stocksAtStart then
        change = "No Change"
    end
 
     return {
        buyStocks    = buyStocks,
        sellStocks    = sellStocks,
        stocksAtEnd  = stocksAtEnd,
        change        = change,
    }
end
end


Line 146: Line 163:
         return increaseReasons[math.random(#increaseReasons)]
         return increaseReasons[math.random(#increaseReasons)]
     elseif change == "Decreased" then
     elseif change == "Decreased" then
         return decreaseReasons[math.random(#decreaseReasons)]
         return decreaseReasons[math.random(#increaseReasons)]
     else
     else
         return "Market activity remained stable."
         return "Market activity remained stable."
Line 153: Line 170:


---------------------------------------------------------
---------------------------------------------------------
-- 6. Main Function: generateTable
-- 6. Yesterday Simulation:
--    1) We define a "yesterSeed" = dailySeed - 1
--    2) We replicate the random buy/sell from "yesterday"
--    3) We get stocksAtEndFromYesterday
--    4) That becomes today's StocksAtStart
---------------------------------------------------------
local function getYesterdaysEnd(investorBase, dailySeed)
    local yesterSeed = dailySeed - 1
    math.randomseed(yesterSeed)
 
    -- We'll simulate the same logic we use for the day,
    -- but only for "yesterday"
    local baseStart = investorBase.stocks
    local yResult  = simulateDailyActivity(baseStart)
    return yResult.stocksAtEnd  -- "yesterday's" stocks at end
end
 
---------------------------------------------------------
-- 7. Main Table Generation
---------------------------------------------------------
---------------------------------------------------------
function p.generateTable()
function p.generateTable()
     -------------------------------------------------------
     -- 7.1) Today’s date + seeds
    -- Use date-based seeding for daily stable results
     local dateString, psscYear, dayOfYear = getCurrentDate()
    -- i.e. ensures a new random scenario each day
    -------------------------------------------------------
     local dateString, dayOfMonth, month = getCurrentDate()
    -- dailySeed: year * 1000 + dayOfYear:
     local localTime = os.date("*t")
     local localTime = os.date("*t")
     local dailySeed = localTime.year * 1000 + localTime.yday
     local dailySeed = localTime.year * 1000 + localTime.yday
     math.randomseed(dailySeed)
     math.randomseed(dailySeed)


    local stockPrice = 30
     local totalMarketValueAtStart = 0
     local totalMarketValueAtStart = 0
     local totalMarketValueAtEnd = 0
     local totalMarketValueAtEnd   = 0
     local totalBoughtValue = 0
     local totalBoughtValue       = 0
     local totalSoldValue = 0
     local totalSoldValue         = 0
    local stockPrice = 30  -- Stock price in Polis


     local wikitable =  
     local out = {}
        '{| class="wikitable"\n' ..
    table.insert(out, '{| class="wikitable"\n')
         '! Date (Day/Month/Year [[PSSC]]) !! Investor !! Tier !! Stocks at Start !! Value at Start !! Stocks Bought !! Value of Stocks Bought !! Stocks Sold !! Value of Stocks Sold !! Stocks at End !! Value at End !! Change !! Reason\n'
    table.insert(out,
         "! Date (Day/Month/Year PSSC) !! Investor !! Tier !! Stocks at Start !! Value at Start !! Stocks Bought !! Value of Stocks Bought !! Stocks Sold !! Value of Stocks Sold !! Stocks at End !! Value at End !! Change !! Reason\n"
    )
 
    -- For each investor
    for _, baseObj in ipairs(baseInvestors) do
        local yEnd = getYesterdaysEnd(baseObj, dailySeed)
        local stocksAtStart = yEnd  -- "today's start" is "yesterday's end"
        if stocksAtStart < 0 then
            stocksAtStart = 0  -- safety clamp
        end


    -- Process each investor
         local valueAtStart = stocksAtStart * stockPrice
    for _, investor in ipairs(investors) do
        -- Record starting values
        local stocksAtStart = investor.stocks
         local valueAtStart = stocksAtStart * stockPrice


         -- Simulate today's random buy/sell
         -- Re-randomseed for "today"
         local buyStocks, sellStocks = simulateDailyActivity(investor)
         math.randomseed(dailySeed)
        local valueBought = buyStocks * stockPrice
        local valueSold  = sellStocks * stockPrice


         -- Record ending values
         local dayResult = simulateDailyActivity(stocksAtStart)
         local stocksAtEnd = investor.stocks
        local buyCount  = dayResult.buyStocks
        local sellCount = dayResult.sellStocks
         local stocksAtEnd = dayResult.stocksAtEnd
        if stocksAtEnd < 0 then
            stocksAtEnd = 0
        end
        local valueBought = buyCount  * stockPrice
        local valueSold  = sellCount * stockPrice
         local valueAtEnd  = stocksAtEnd * stockPrice
         local valueAtEnd  = stocksAtEnd * stockPrice
        local theChange  = dayResult.change
        local reason      = getReason(theChange)
         local tier        = determineTier(stocksAtEnd)
         local tier        = determineTier(stocksAtEnd)


        local change = "Increased"
         -- accumulate totals
        if stocksAtEnd < stocksAtStart then
            change = "Decreased"
        elseif stocksAtEnd == stocksAtStart then
            change = "No Change"
        end
 
        local reason = getReason(change)
 
         -- Update totals
         totalMarketValueAtStart = totalMarketValueAtStart + valueAtStart
         totalMarketValueAtStart = totalMarketValueAtStart + valueAtStart
         totalMarketValueAtEnd  = totalMarketValueAtEnd  + valueAtEnd
         totalMarketValueAtEnd  = totalMarketValueAtEnd  + valueAtEnd
Line 207: Line 242:
         totalSoldValue          = totalSoldValue          + valueSold
         totalSoldValue          = totalSoldValue          + valueSold


         -- Build row
         table.insert(out, "|-\n")
         wikitable = wikitable .. string.format(
         table.insert(out, string.format(
             "|-\n| %s || %s || %s || %d || %d Polis || %d || %d Polis || %d || %d Polis || %d || %d Polis || %s || %s\n",
             "| %s || %s || %s || %d || %d Polis || %d || %d Polis || %d || %d Polis || %d || %d Polis || %s || %s\n",
             dateString,
             dateString,         -- today's date
             investor.name,
             baseObj.name,
             tier,
             tier,
             stocksAtStart,
             stocksAtStart,
             valueAtStart,
             valueAtStart,
             buyStocks,
             buyCount,
             valueBought,
             valueBought,
             sellStocks,
             sellCount,
             valueSold,
             valueSold,
             stocksAtEnd,
             stocksAtEnd,
             valueAtEnd,
             valueAtEnd,
             change,
             theChange,
             reason
             reason
         )
         ))
     end
     end


     -- Totals row
     -- Totals row
     wikitable = wikitable .. string.format(
     table.insert(out, "|-\n")
         "|-\n| colspan=5 | Total Market Value at Start || %d Polis || %d Polis || %d Polis || %d Polis || ||\n",
    table.insert(out, string.format(
         "| colspan=5 | Total Market Value at Start || %d Polis || %d Polis || %d Polis || %d Polis || ||\n",
         totalMarketValueAtStart, totalBoughtValue, totalSoldValue, totalMarketValueAtEnd
         totalMarketValueAtStart, totalBoughtValue, totalSoldValue, totalMarketValueAtEnd
     )
     ))
     wikitable = wikitable .. "|}"
     table.insert(out, "|}")


     return wikitable
     return table.concat(out)
end
end


return p
return p

Revision as of 17:19, 27 December 2024

Documentation for this module may be created at Module:BassaridMarketActivity/doc

---------------------------------------------------------
-- Module: BassaridiaDailyInvestorTable
-- Updates each day with new buy/sell, but uses the
-- "previous day" simulation to set 'Stocks at Start'
-- to 'Stocks at End' from that previous day.
--
-- NOTES:
-- 1) Because we do not store data externally, we
--    re-simulate "yesterday" each day with a slightly
--    different random seed. Then we use that to set the
--    starting stocks for "today."
-- 2) Everything is still stable for the current day, but
--    tomorrow it re-simulates again. Therefore, actual
--    day-to-day changes remain consistent in the script's
--    logic, *so long as* no additional changes are made.
---------------------------------------------------------
local p = {}

---------------------------------------------------------
-- 1. Calendar: Determine PSSC + daily seeds
---------------------------------------------------------
local function getCurrentDate()
    local startDate   = os.time({year=1999, month=8, day=6})
    local secondsInDay= 86400
    local daysPerYear = 183

    local currentDate = os.time()
    local totalDaysElapsed = math.floor((currentDate - startDate) / secondsInDay)

    local yearFraction = totalDaysElapsed / daysPerYear
    local psscYear     = math.floor(yearFraction)
    local dayOfYear    = math.floor((yearFraction - psscYear) * daysPerYear) + 1

    -- For demonstration, we do 3 months: 1..61, 62..122, 123..183
    local month, dayOfMonth
    if dayOfYear <= 61 then
        month = 1
        dayOfMonth = dayOfYear
    elseif dayOfYear <= 122 then
        month = 2
        dayOfMonth = dayOfYear - 61
    else
        month = 3
        dayOfMonth = dayOfYear - 122
    end

    local dateString = string.format("%d/%d/%d [[PSSC]]", dayOfMonth, month, psscYear)
    return dateString, psscYear, dayOfYear
end

---------------------------------------------------------
-- 2. Investor Data (Initial "Base" Stocks)
---------------------------------------------------------
-- This table holds each investor's *base* stock count,
-- used only if we had no prior day. We'll override each
-- day using the "simulateYesterday" logic below.
---------------------------------------------------------
local baseInvestors = {
    {name = "Vaeringheim Regional Investor", stocks = 17424},
    {name = "Alpazkigz Regional Investor",   stocks = 16214},
    {name = "Odiferia Regional Investor",    stocks = 14718},
    {name = "Hafaan Regional Investor",      stocks =  3630},
    {name = "Jeseri Regional Investor",      stocks =  1870},
    {name = "Hatch Ministry Regional Investor", stocks = 7810},
    {name = "New South Jangsong Division",   stocks = 11085},
    {name = "Haifan Bassaridia Division",    stocks = 12500},
}

---------------------------------------------------------
-- 3. Reasons for Increase or Decrease
---------------------------------------------------------
local increaseReasons = {
    "Improved logistics attracted new investors.",
    "An influx of goods boosted trade volume.",
    "High demand for Noctic-Rabrev exports raised confidence.",
    "Completion of a new shipping lane increased activity.",
    "Successful negotiations with Erythros merchants expanded trade.",
    "A local festival drove significant port activity.",
    "High attendance at a major trade symposium enhanced the port's reputation.",
    "Discovery of new mineral resources increased export potential.",
    "A surge in ceremonial wine exports bolstered port operations.",
    "Efficient cargo handling innovations improved investor sentiment.",
    "Seasonal agricultural exports saw a sharp increase.",
    "High sales of pearl and abalone products boosted port activity.",
    "Introduction of new luxury goods attracted high-end investors.",
    "Expansion of the port's capacity improved its competitiveness.",
    "Improved relations with local merchant guilds stabilized trade routes.",
    "Increased fishing yields led to greater port activity.",
    "Enhanced security measures reduced piracy concerns.",
    "Growing cultural appreciation for exotic exports boosted confidence.",
    "Trade agreements with neighboring cities expanded port usage.",
    "Launch of new fertilizer shipments attracted agricultural investors.",
    "Rising global interest in sacred resin exports spiked port activity.",
    "Successful marketing campaigns highlighted the port's strategic importance.",
    "Completion of high-speed rail connections improved trade efficiency.",
}

local decreaseReasons = {
    "Pirate raids in the Strait disrupted shipments.",
    "Unfavorable weather delayed agricultural exports.",
    "Labor shortages caused delays in cargo handling.",
    "A major fire in supply regions reduced exports.",
    "Rising costs impacted investor confidence.",
    "Unrest in Jangsong Province disrupted shipments.",
    "Currency fluctuations affected trade stability.",
    "Economic sanctions on neighbors reduced export activity.",
    "Decline in ceremonial artifact production reduced goods traffic.",
    "Cultural disputes caused delays in exports.",
    "Counterfeit relics discovered harmed the port's reputation.",
    "High demand outpaced supply, reducing exports.",
    "Decline in pearl quality lowered investor interest.",
    "Shortage of ceremonial wine hurt exports.",
    "Unrest among key merchants disrupted major trade agreements.",
    "Reduced demand for local textiles impacted port activity.",
    "Collapse of a key trade deal reduced port volume.",
    "Seasonal labor shortages decreased shipments.",
    "Alternate shipping routes bypassed the port.",
    "Economic downturn in a key region affected investor interest.",
    "Piracy concerns in Haifan waters harmed trade confidence.",
    "Failure to secure deals at a trade symposium reduced traffic.",
    "Natural disasters destroyed key trade routes.",
    "Sabotage of port infrastructure caused significant delays.",
}

---------------------------------------------------------
-- 4. Tiers
---------------------------------------------------------
local function determineTier(stocksAtEnd)
    if stocksAtEnd > 12500 then
        return "Tier I"
    elseif stocksAtEnd >= 5000 then
        return "Tier II"
    else
        return "Tier III"
    end
end

---------------------------------------------------------
-- 5. Single-Day Simulation
---------------------------------------------------------
local function simulateDailyActivity(stocksAtStart)
    local buyStocks = math.random(0, 500)
    local sellStocks = math.random(0, 500)

    local stocksAtEnd = stocksAtStart + buyStocks - sellStocks
    local change = "Increased"
    if stocksAtEnd < stocksAtStart then
        change = "Decreased"
    elseif stocksAtEnd == stocksAtStart then
        change = "No Change"
    end

    return {
        buyStocks     = buyStocks,
        sellStocks    = sellStocks,
        stocksAtEnd   = stocksAtEnd,
        change        = change,
    }
end

local function getReason(change)
    if change == "Increased" then
        return increaseReasons[math.random(#increaseReasons)]
    elseif change == "Decreased" then
        return decreaseReasons[math.random(#increaseReasons)]
    else
        return "Market activity remained stable."
    end
end

---------------------------------------------------------
-- 6. Yesterday Simulation:
--    1) We define a "yesterSeed" = dailySeed - 1
--    2) We replicate the random buy/sell from "yesterday"
--    3) We get stocksAtEndFromYesterday
--    4) That becomes today's StocksAtStart
---------------------------------------------------------
local function getYesterdaysEnd(investorBase, dailySeed)
    local yesterSeed = dailySeed - 1
    math.randomseed(yesterSeed)

    -- We'll simulate the same logic we use for the day,
    -- but only for "yesterday"
    local baseStart = investorBase.stocks
    local yResult   = simulateDailyActivity(baseStart)
    return yResult.stocksAtEnd  -- "yesterday's" stocks at end
end

---------------------------------------------------------
-- 7. Main Table Generation
---------------------------------------------------------
function p.generateTable()
    -- 7.1) Today’s date + seeds
    local dateString, psscYear, dayOfYear = getCurrentDate()
    local localTime = os.date("*t")
    local dailySeed = localTime.year * 1000 + localTime.yday
    math.randomseed(dailySeed)

    local stockPrice = 30
    local totalMarketValueAtStart = 0
    local totalMarketValueAtEnd   = 0
    local totalBoughtValue        = 0
    local totalSoldValue          = 0

    local out = {}
    table.insert(out, '{| class="wikitable"\n')
    table.insert(out,
        "! Date (Day/Month/Year PSSC) !! Investor !! Tier !! Stocks at Start !! Value at Start !! Stocks Bought !! Value of Stocks Bought !! Stocks Sold !! Value of Stocks Sold !! Stocks at End !! Value at End !! Change !! Reason\n"
    )

    -- For each investor
    for _, baseObj in ipairs(baseInvestors) do
        local yEnd = getYesterdaysEnd(baseObj, dailySeed)
        local stocksAtStart = yEnd  -- "today's start" is "yesterday's end"
        if stocksAtStart < 0 then
            stocksAtStart = 0  -- safety clamp
        end

        local valueAtStart = stocksAtStart * stockPrice

        -- Re-randomseed for "today"
        math.randomseed(dailySeed)

        local dayResult = simulateDailyActivity(stocksAtStart)
        local buyCount  = dayResult.buyStocks
        local sellCount = dayResult.sellStocks
        local stocksAtEnd = dayResult.stocksAtEnd
        if stocksAtEnd < 0 then
            stocksAtEnd = 0
        end
        local valueBought = buyCount  * stockPrice
        local valueSold   = sellCount * stockPrice
        local valueAtEnd  = stocksAtEnd * stockPrice
        local theChange   = dayResult.change
        local reason      = getReason(theChange)
        local tier        = determineTier(stocksAtEnd)

        -- accumulate totals
        totalMarketValueAtStart = totalMarketValueAtStart + valueAtStart
        totalMarketValueAtEnd   = totalMarketValueAtEnd   + valueAtEnd
        totalBoughtValue        = totalBoughtValue        + valueBought
        totalSoldValue          = totalSoldValue          + valueSold

        table.insert(out, "|-\n")
        table.insert(out, string.format(
            "| %s || %s || %s || %d || %d Polis || %d || %d Polis || %d || %d Polis || %d || %d Polis || %s || %s\n",
            dateString,          -- today's date
            baseObj.name,
            tier,
            stocksAtStart,
            valueAtStart,
            buyCount,
            valueBought,
            sellCount,
            valueSold,
            stocksAtEnd,
            valueAtEnd,
            theChange,
            reason
        ))
    end

    -- Totals row
    table.insert(out, "|-\n")
    table.insert(out, string.format(
        "| colspan=5 | Total Market Value at Start || %d Polis || %d Polis || %d Polis || %d Polis || ||\n",
        totalMarketValueAtStart, totalBoughtValue, totalSoldValue, totalMarketValueAtEnd
    ))
    table.insert(out, "|}")

    return table.concat(out)
end

return p