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 2: Line 2:
-- Module: BassaridiaDailyInvestorTable
-- Module: BassaridiaDailyInvestorTable
-- Generates a daily table of investor stock activity
-- Generates a daily table of investor stock activity
-- and associated reasons for increases/decreases.
-- with half increasing, half decreasing. No investor
--
-- can gain or lose more than 30% from their start.
-- Each new real-life day:
-- Reasons are unique per investor per day.
--  1) We use the current date to set a random seed,
--    so the simulation is stable for that day,
--    but refreshes on the next day.
--  2) About half the investors have net share "Inc"
--    (buy > sell), half "Dec" (sell > buy).
--  3) No investor's net change in shares (stocksAtEnd
--     - stocksAtStart) may exceed 30% of stocksAtStart.
-- 4) Each investor draws a unique reason from the
--    appropriate pool (increase vs. decrease).
---------------------------------------------------------
---------------------------------------------------------


Line 19: Line 10:


---------------------------------------------------------
---------------------------------------------------------
-- 1. getCurrentDate
-- 1. getCurrentDate: returns "DD/MM/YYYY PSSC" + dayOfYear
--    Returns "DD/MM/YYYY PSSC" plus dayOfYear
---------------------------------------------------------
---------------------------------------------------------
local function getCurrentDate()
local function getCurrentDate()
Line 34: Line 24:
     local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1
     local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1


     -- simplistic 3-month model
     -- simplistic approach: 3 "months" in the 183-day year
     local month, dayOfMonth
     local month, dayOfMonth
     if dayOfYear <= 61 then
     if dayOfYear <= 61 then
Line 53: Line 43:
---------------------------------------------------------
---------------------------------------------------------
-- 2. Investor Data
-- 2. Investor Data
--    (Each investor has a 'stocks' starting point.)
---------------------------------------------------------
---------------------------------------------------------
local investors = {
local investors = {
Line 68: Line 57:
---------------------------------------------------------
---------------------------------------------------------
-- 3. Reasons for Increase / Decrease
-- 3. Reasons for Increase / Decrease
---------------------------------------------------------
-- Enough reasons so that each investor can have a unique one.
---------------------------------------------------------
---------------------------------------------------------
local increaseReasons = {
local increaseReasons = {
Line 78: Line 65:
     "Successful negotiations with Erythros merchants expanded trade.",
     "Successful negotiations with Erythros merchants expanded trade.",
     "Festival of Light in Vaeringheim drove significant port activity.",
     "Festival of Light in Vaeringheim drove significant port activity.",
     "High attendance at the Bassaridia Trade Symposium enhanced the port's reputation.",
     "High attendance at the Bassaridia Trade Symposium enhanced reputation.",
     "Discovery of new mineral resources near Acheron increased export potential.",
     "Discovery of new mineral resources near Acheron increased export potential.",
     "A surge in ceremonial wine exports from the Blood Vineyards bolstered port ops.",
     "A surge in ceremonial wine exports from the Blood Vineyards bolstered ops.",
     "Efficient cargo handling improvements boosted investor sentiment.",
     "Efficient cargo handling improvements boosted investor sentiment.",
     "Seasonal agricultural exports from Plains of Jogi saw a sharp increase.",
     "Seasonal agricultural exports from Jogi saw a sharp increase.",
     "High sales of pearl and abalone products boosted port traffic.",
     "High sales of pearl and abalone products boosted port traffic.",
     "Luxury goods from Tel-Amin attracted new high-end investors.",
     "Luxury goods from Tel-Amin attracted new high-end investors.",
Line 89: Line 76:
     "Increased fishing yields around Lake Morovia led to more port activity.",
     "Increased fishing yields around Lake Morovia led to more port activity.",
     "Enhanced security measures at the Port reduced piracy concerns.",
     "Enhanced security measures at the Port reduced piracy concerns.",
     "Growing cultural appreciation for sacred Horehound exports boosted confidence.",
     "Cultural appreciation for Horehound exports boosted confidence.",
     "Trade deals with Saluria expanded port usage significantly.",
     "Trade deals with Saluria expanded port usage significantly.",
     "Demand for silk from the Seamstresses of Rouge improved port revenue.",
     "Demand for silk from the Seamstresses of Rouge improved port revenue.",
     "Moonlit Tide celebrations increased ceremonial goods traffic.",
     "Moonlit Tide celebrations increased ceremonial goods traffic.",
     "Launch of new Garganid fertilizer shipments attracted agriculture investors.",
     "Launch of new Garganid fertilizer shipments attracted agriculture investors.",
     "Global interest in sacred resin exports enhanced port shipments.",
     "Global interest in sacred resin exports enhanced shipments.",
     "Successful marketing campaign highlighted the Port's location.",
     "Marketing campaign highlighted the Port's strategic location.",
     "Completion of high-speed rail connections boosted trade efficiency."
     "Completion of high-speed rail connections boosted trade efficiency."
}
}
Line 112: Line 99:
     "Counterfeit relics in Nexa harmed the port's reputation.",
     "Counterfeit relics in Nexa harmed the port's reputation.",
     "Fishing yields were outpaced by demand, reducing shipments.",
     "Fishing yields were outpaced by demand, reducing shipments.",
     "Decline in pearl quality from Anterran sources lowered investor interest.",
     "Decline in pearl quality from Anterran sources lowered interest.",
     "Shortage of ceremonial wine from Blood Vineyards cut exports.",
     "Shortage of ceremonial wine from Blood Vineyards cut exports.",
     "Unrest among Maskmaker merchants disrupted trade agreements.",
     "Unrest among Maskmaker merchants disrupted trade agreements.",
     "Less demand for silk from the Seamstresses of Rouge impacted port flows.",
     "Less demand for silk from Seamstresses of Rouge impacted flows.",
     "Collapse of trade agreement with Tel-Amin lowered volume.",
     "Collapse of trade agreement with Tel-Amin lowered volume.",
     "Seasonal labor shortages shrank port shipments significantly.",
     "Seasonal labor shortages shrank port shipments significantly.",
Line 128: Line 115:


---------------------------------------------------------
---------------------------------------------------------
-- 4. Utility: pick a random reason, ensuring no duplicates
-- 4. Utility for Unique Reasons
---------------------------------------------------------
---------------------------------------------------------
local function popUniqueReason(list)
local function popUniqueReason(list)
Line 141: Line 128:


---------------------------------------------------------
---------------------------------------------------------
-- 5. Force half to "inc" half to "dec"
-- 5. Force half Inc, half Dec
---------------------------------------------------------
---------------------------------------------------------
local function assignUpDown(count)
local function assignUpDown(count)
    -- If count=8, we want 4 inc, 4 dec
     local half = math.floor(count / 2)
     local half = math.floor(count / 2)
     local arr = {}
     local arr = {}
     for i=1, half do
     for i = 1, half do
        arr[i] = "Inc"
      arr[i] = "Inc"
     end
     end
     for i=half+1, count do
     for i = half+1, count do
        arr[i] = "Dec"
      arr[i] = "Dec"
     end
     end
     -- shuffle
     -- shuffle
     for i=#arr,2,-1 do
     for i = #arr, 2, -1 do
       local j=math.random(i)
       local j = math.random(i)
       arr[i], arr[j] = arr[j], arr[i]
       arr[i], arr[j] = arr[j], arr[i]
     end
     end
Line 162: Line 148:


---------------------------------------------------------
---------------------------------------------------------
-- 6. generateTable function
-- 6. Main generator
---------------------------------------------------------
---------------------------------------------------------
function p.generateTable(frame)
function p.generateTable(frame)
 
    -- Date-based seed
     local dateString, dayOfYear = getCurrentDate()
     local dateString, dayOfYear = getCurrentDate()
    -- daily random seed
     local localTime = os.date("*t")
     local localTime = os.date("*t")
     local dailySeed = localTime.year * 1000 + localTime.yday
     local dailySeed = localTime.year * 1000 + localTime.yday
Line 173: Line 158:


     local stockPrice = 30
     local stockPrice = 30
    -- HEADERS
    local out = '{| class="wikitable"\n'
    out = out .. "! Date "
    out = out .. "! Investor "
    out = out .. "! Stocks at Start "
    out = out .. "! Stocks Bought "
    out = out .. "! Stocks Sold "
    out = out .. "! Stocks at End "
    out = out .. "! Value at Start "
    out = out .. "! Value at End "
    out = out .. "! Change "
    out = out .. "! Reason\n"
     local n = #investors
     local n = #investors
     local updown = assignUpDown(n)
     local updown = assignUpDown(n)


    local totalStartValue = 0
     -- Copy reason arrays so we can remove used ones
    local totalEndValue  = 0
 
     -- Copy the reason arrays so we don't inadvertently remove them from the original
     local incPool = {}
     local incPool = {}
     for _, r in ipairs(increaseReasons) do incPool[#incPool+1] = r end
     for _, r in ipairs(increaseReasons) do incPool[#incPool+1] = r end
Line 199: Line 167:
     for _, r in ipairs(decreaseReasons) do decPool[#decPool+1] = r end
     for _, r in ipairs(decreaseReasons) do decPool[#decPool+1] = r end


    -- Table header
    local out = {}
    table.insert(out, '{| class="wikitable" style="text-align:center; width:100%;"')
    table.insert(out, "|-")
    table.insert(out, "! Date")
    table.insert(out, "! Investor")
    table.insert(out, "! Stocks at Start")
    table.insert(out, "! Stocks Bought")
    table.insert(out, "! Stocks Sold")
    table.insert(out, "! Stocks at End")
    table.insert(out, "! Value at Start (Polis)")
    table.insert(out, "! Value at End (Polis)")
    table.insert(out, "! Change")
    table.insert(out, "! Reason")
    local totalStartValue = 0
    local totalEndValue  = 0
    -- For each investor
     for i, inv in ipairs(investors) do
     for i, inv in ipairs(investors) do
         local stocksStart = inv.stocks
         local stocksStart = inv.stocks
         local label = updown[i] -- "Inc" or "Dec"
         local label = updown[i] -- "Inc" or "Dec"


         -- We want to ensure final change won't exceed 30%.
         -- ensure final net won't exceed 30% from start
         local maxDelta = math.floor(stocksStart * 0.3) -- e.g. 30% of start
         local maxDelta = math.floor(stocksStart * 0.3)
         if maxDelta < 1 then maxDelta=1 end
         if maxDelta < 1 then maxDelta = 1 end


         local buy=0
         local buy=0
         local sell=0
         local sell=0
         if label == "Inc" then
         if label == "Inc" then
            -- net up => buy - sell > 0
             buy = math.random(1, maxDelta)
            -- pick buy up to 30% (1..maxDelta), sell up to (buy-1)
             buy = math.random(1, maxDelta)
             sell = math.random(0, buy-1)
             sell = math.random(0, buy-1)
         else
         else
            -- net down => sell - buy > 0
            -- pick sell up to 30% (1..maxDelta), buy up to (sell-1)
             sell = math.random(1, maxDelta)
             sell = math.random(1, maxDelta)
             buy = math.random(0, sell-1)
             buy = math.random(0, sell-1)
         end
         end


Line 237: Line 219:
         end
         end


        -- pick reason from incPool or decPool or incPool if no change
         local reason
         local reason
         if changeWord == "Increased" then
         if changeWord == "Increased" then
Line 244: Line 225:
             reason = popUniqueReason(decPool)
             reason = popUniqueReason(decPool)
         else
         else
             reason = popUniqueReason(incPool) -- or decPool, up to you
            -- If No Change, we can draw from either pool
            -- to avoid duplication, let's pick from incPool
             reason = popUniqueReason(incPool)
         end
         end


         out = out .. string.format(
         table.insert(out, "|-")
[[
        table.insert(out, string.format("| %s", dateString))
|-
        table.insert(out, string.format("| %s", inv.name))
| %s
        table.insert(out, string.format("| %d", stocksStart))
| %s
        table.insert(out, string.format("| %d", buy))
| %d
        table.insert(out, string.format("| %d", sell))
| %d
        table.insert(out, string.format("| %d", stocksEnd))
| %d
        table.insert(out, string.format("| %d", valueStart))
| %d
        table.insert(out, string.format("| %d", valueEnd))
| %d Polis
        table.insert(out, string.format("| %s", changeWord))
| %d Polis
        table.insert(out, string.format("| %s", reason))
| %s
| %s
]],
            dateString,
            inv.name,
            stocksStart,
            buy,
            sell,
            stocksEnd,
            valueStart,
            valueEnd,
            changeWord,
            reason
        )
     end
     end


     -- Totals row
     -- Totals row
     out = out .. string.format(
     table.insert(out, "|-")
[[
    table.insert(out, string.format("| colspan=\"6\" | Totals"))
|-
    table.insert(out, string.format("| %d", totalStartValue))
| colspan="2" | Totals
    table.insert(out, string.format("| %d", totalEndValue))
|
     table.insert(out, "| ")
|
     table.insert(out, "| ")
|
|
| %d Polis
| %d Polis
|
|
]],
        totalStartValue,
        totalEndValue
     )
 
     out = out .. "\n|}"


     return out
    table.insert(out, "|}")
     return table.concat(out, "\n")
end
end


return p
return p

Revision as of 17:27, 27 December 2024

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

---------------------------------------------------------
-- Module: BassaridiaDailyInvestorTable
-- Generates a daily table of investor stock activity
-- with half increasing, half decreasing. No investor
-- can gain or lose more than 30% from their start.
-- Reasons are unique per investor per day.
---------------------------------------------------------

local p = {}

---------------------------------------------------------
-- 1. getCurrentDate: returns "DD/MM/YYYY PSSC" + dayOfYear
---------------------------------------------------------
local function getCurrentDate()
    local startDate = os.time({year = 1999, month = 8, day = 6})
    local secondsInDay = 86400
    local daysPerYear = 183

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

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

    -- simplistic approach: 3 "months" in the 183-day year
    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, dayOfYear
end

---------------------------------------------------------
-- 2. Investor Data
---------------------------------------------------------
local investors = {
    { 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 / Decrease
---------------------------------------------------------
local increaseReasons = {
    "Improved logistics at the General Port attracted new investors.",
    "An influx of goods from Delphica boosted trade volume at the port.",
    "High demand for Noctic-Rabrev exports raised investor confidence.",
    "Completion of a new shipping lane connecting Nexa increased activity.",
    "Successful negotiations with Erythros merchants expanded trade.",
    "Festival of Light in Vaeringheim drove significant port activity.",
    "High attendance at the Bassaridia Trade Symposium enhanced reputation.",
    "Discovery of new mineral resources near Acheron increased export potential.",
    "A surge in ceremonial wine exports from the Blood Vineyards bolstered ops.",
    "Efficient cargo handling improvements boosted investor sentiment.",
    "Seasonal agricultural exports from Jogi saw a sharp increase.",
    "High sales of pearl and abalone products boosted port traffic.",
    "Luxury goods from Tel-Amin attracted new high-end investors.",
    "Port capacity expansion in Suncliff improved competitiveness.",
    "Improved relations with Maskmakers Guild stabilized trade routes.",
    "Increased fishing yields around Lake Morovia led to more port activity.",
    "Enhanced security measures at the Port reduced piracy concerns.",
    "Cultural appreciation for Horehound exports boosted confidence.",
    "Trade deals with Saluria expanded port usage significantly.",
    "Demand for silk from the Seamstresses of Rouge improved port revenue.",
    "Moonlit Tide celebrations increased ceremonial goods traffic.",
    "Launch of new Garganid fertilizer shipments attracted agriculture investors.",
    "Global interest in sacred resin exports enhanced shipments.",
    "Marketing campaign highlighted the Port's strategic location.",
    "Completion of high-speed rail connections boosted trade efficiency."
}

local decreaseReasons = {
    "Pirate raids in the Strait of Haifa disrupted shipments to the Port.",
    "Unfavorable weather delayed agriculture exports from Plains of Jogi.",
    "Labor shortages in the Port caused cargo-handling delays.",
    "A major fire in Salurian sacred groves reduced resin exports.",
    "Rising costs of Noctic-Rabrev cultivation hurt confidence.",
    "Unrest in Jangsong Province disrupted camel meat shipments.",
    "Currency fluctuations in Vaeringheim affected stability.",
    "Economic sanctions on neighbors reduced export activity.",
    "Decline in ceremonial artifact production lowered traffic.",
    "Cultural disputes over Horehound caused export delays.",
    "Counterfeit relics in Nexa harmed the port's reputation.",
    "Fishing yields were outpaced by demand, reducing shipments.",
    "Decline in pearl quality from Anterran sources lowered interest.",
    "Shortage of ceremonial wine from Blood Vineyards cut exports.",
    "Unrest among Maskmaker merchants disrupted trade agreements.",
    "Less demand for silk from Seamstresses of Rouge impacted flows.",
    "Collapse of trade agreement with Tel-Amin lowered volume.",
    "Seasonal labor shortages shrank port shipments significantly.",
    "Disruptions in Suncliff Fisheries slowed aquatic exports.",
    "Alternative shipping routes bypassed the Port altogether.",
    "Economic downturn in Lunalis Sancta reduced investor interest.",
    "Lingering piracy concerns in Haifan waters harmed confidence.",
    "Failure at Bassaridia Trade Symposium lowered traffic.",
    "Natural disasters near Acheron destroyed key routes.",
    "Sabotage of port infrastructure caused shipping delays."
}

---------------------------------------------------------
-- 4. Utility for Unique Reasons
---------------------------------------------------------
local function popUniqueReason(list)
    if #list == 0 then
        return "No reason available (out of reasons)."
    end
    local rnd = math.random(#list)
    local chosen = list[rnd]
    table.remove(list, rnd)
    return chosen
end

---------------------------------------------------------
-- 5. Force half Inc, half Dec
---------------------------------------------------------
local function assignUpDown(count)
    local half = math.floor(count / 2)
    local arr = {}
    for i = 1, half do
       arr[i] = "Inc"
    end
    for i = half+1, count do
       arr[i] = "Dec"
    end
    -- shuffle
    for i = #arr, 2, -1 do
       local j = math.random(i)
       arr[i], arr[j] = arr[j], arr[i]
    end
    return arr
end

---------------------------------------------------------
-- 6. Main generator
---------------------------------------------------------
function p.generateTable(frame)
    -- Date-based seed
    local dateString, dayOfYear = getCurrentDate()
    local localTime = os.date("*t")
    local dailySeed = localTime.year * 1000 + localTime.yday
    math.randomseed(dailySeed)

    local stockPrice = 30
    local n = #investors
    local updown = assignUpDown(n)

    -- Copy reason arrays so we can remove used ones
    local incPool = {}
    for _, r in ipairs(increaseReasons) do incPool[#incPool+1] = r end
    local decPool = {}
    for _, r in ipairs(decreaseReasons) do decPool[#decPool+1] = r end

    -- Table header
    local out = {}
    table.insert(out, '{| class="wikitable" style="text-align:center; width:100%;"')
    table.insert(out, "|-")
    table.insert(out, "! Date")
    table.insert(out, "! Investor")
    table.insert(out, "! Stocks at Start")
    table.insert(out, "! Stocks Bought")
    table.insert(out, "! Stocks Sold")
    table.insert(out, "! Stocks at End")
    table.insert(out, "! Value at Start (Polis)")
    table.insert(out, "! Value at End (Polis)")
    table.insert(out, "! Change")
    table.insert(out, "! Reason")

    local totalStartValue = 0
    local totalEndValue   = 0

    -- For each investor
    for i, inv in ipairs(investors) do
        local stocksStart = inv.stocks
        local label = updown[i] -- "Inc" or "Dec"

        -- ensure final net won't exceed 30% from start
        local maxDelta = math.floor(stocksStart * 0.3)
        if maxDelta < 1 then maxDelta = 1 end

        local buy=0
        local sell=0
        if label == "Inc" then
            buy  = math.random(1, maxDelta)
            sell = math.random(0, buy-1)
        else
            sell = math.random(1, maxDelta)
            buy  = math.random(0, sell-1)
        end

        local stocksEnd = stocksStart + buy - sell
        local valueStart = stocksStart * stockPrice
        local valueEnd   = stocksEnd   * stockPrice
        totalStartValue = totalStartValue + valueStart
        totalEndValue   = totalEndValue   + valueEnd

        local changeWord
        if stocksEnd > stocksStart then
            changeWord = "Increased"
        elseif stocksEnd < stocksStart then
            changeWord = "Decreased"
        else
            changeWord = "No Change"
        end

        local reason
        if changeWord == "Increased" then
            reason = popUniqueReason(incPool)
        elseif changeWord == "Decreased" then
            reason = popUniqueReason(decPool)
        else
            -- If No Change, we can draw from either pool
            -- to avoid duplication, let's pick from incPool
            reason = popUniqueReason(incPool)
        end

        table.insert(out, "|-")
        table.insert(out, string.format("| %s", dateString))
        table.insert(out, string.format("| %s", inv.name))
        table.insert(out, string.format("| %d", stocksStart))
        table.insert(out, string.format("| %d", buy))
        table.insert(out, string.format("| %d", sell))
        table.insert(out, string.format("| %d", stocksEnd))
        table.insert(out, string.format("| %d", valueStart))
        table.insert(out, string.format("| %d", valueEnd))
        table.insert(out, string.format("| %s", changeWord))
        table.insert(out, string.format("| %s", reason))
    end

    -- Totals row
    table.insert(out, "|-")
    table.insert(out, string.format("| colspan=\"6\" | Totals"))
    table.insert(out, string.format("| %d", totalStartValue))
    table.insert(out, string.format("| %d", totalEndValue))
    table.insert(out, "| ")
    table.insert(out, "| ")

    table.insert(out, "|}")
    return table.concat(out, "\n")
end

return p