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 | ||
-- | -- and associated reasons for increases/decreases. | ||
-- | -- | ||
-- | -- Each new real-life day: | ||
-- 1) | -- 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). | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local p = {} | local p = {} | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 1. | -- 1. getCurrentDate | ||
-- Returns "DD/MM/YYYY PSSC" plus dayOfYear | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function getCurrentDate() | local function getCurrentDate() | ||
local startDate | local startDate = os.time({year = 1999, month = 8, day = 6}) | ||
local secondsInDay= 86400 | local secondsInDay = 86400 | ||
local daysPerYear = 183 | local daysPerYear = 183 | ||
local | local now = os.time() | ||
local totalDaysElapsed = math.floor(( | local totalDaysElapsed = math.floor((now - startDate) / secondsInDay) | ||
local yearFraction = totalDaysElapsed / daysPerYear | local yearFraction = totalDaysElapsed / daysPerYear | ||
local psscYear | local psscYear = math.floor(yearFraction) | ||
local dayOfYear | local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1 | ||
-- | -- simplistic 3-month model | ||
local month, dayOfMonth | local month, dayOfMonth | ||
if dayOfYear <= 61 then | if dayOfYear <= 61 then | ||
| Line 45: | Line 47: | ||
end | end | ||
local dateString = string.format("%d/%d/%d | local dateString = string.format("%d/%d/%d PSSC", dayOfMonth, month, psscYear) | ||
return dateString | return dateString, dayOfYear | ||
end | end | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 2. Investor Data | -- 2. Investor Data | ||
-- | -- (Each investor has a 'stocks' starting point.) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local | local investors = { | ||
{name = "Vaeringheim Regional Investor", stocks = 17424}, | { name = "Vaeringheim Regional Investor", stocks = 17424 }, | ||
{name = "Alpazkigz Regional Investor", | { name = "Alpazkigz Regional Investor", stocks = 16214 }, | ||
{name = "Odiferia Regional Investor", | { name = "Odiferia Regional Investor", stocks = 14718 }, | ||
{name = "Hafaan Regional Investor", | { name = "Hafaan Regional Investor", stocks = 3630 }, | ||
{name = "Jeseri Regional Investor", | { name = "Jeseri Regional Investor", stocks = 1870 }, | ||
{name = "Hatch Ministry Regional Investor", stocks = 7810}, | { name = "Hatch Ministry Regional Investor", stocks = 7810 }, | ||
{name = "New South Jangsong Division", | { name = "New South Jangsong Division", stocks = 11085 }, | ||
{name = "Haifan Bassaridia Division", | { name = "Haifan Bassaridia Division", stocks = 12500 }, | ||
} | } | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 3. Reasons for Increase | -- 3. Reasons for Increase / Decrease | ||
--------------------------------------------------------- | |||
-- Enough reasons so that each investor can have a unique one. | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local increaseReasons = { | local increaseReasons = { | ||
"Improved logistics attracted new investors.", | "Improved logistics at the General Port attracted new investors.", | ||
"An influx of goods boosted trade volume.", | "An influx of goods from Delphica boosted trade volume at the port.", | ||
"High demand for Noctic-Rabrev exports raised confidence.", | "High demand for Noctic-Rabrev exports raised investor confidence.", | ||
"Completion of a new shipping lane increased activity.", | "Completion of a new shipping lane connecting Nexa increased activity.", | ||
"Successful negotiations with Erythros merchants expanded trade.", | "Successful negotiations with Erythros merchants expanded trade.", | ||
" | "Festival of Light in Vaeringheim drove significant port activity.", | ||
"High attendance at | "High attendance at the Bassaridia Trade Symposium enhanced the port's reputation.", | ||
"Discovery of new mineral resources increased export potential.", | "Discovery of new mineral resources near Acheron increased export potential.", | ||
"A surge in ceremonial wine exports bolstered port | "A surge in ceremonial wine exports from the Blood Vineyards bolstered port ops.", | ||
"Efficient cargo handling | "Efficient cargo handling improvements boosted investor sentiment.", | ||
"Seasonal agricultural exports saw a sharp increase.", | "Seasonal agricultural exports from Plains of Jogi saw a sharp increase.", | ||
"High sales of pearl and abalone products boosted port | "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 | "Improved relations with Maskmakers Guild stabilized trade routes.", | ||
"Increased fishing yields led to | "Increased fishing yields around Lake Morovia led to more port activity.", | ||
"Enhanced security measures reduced piracy concerns.", | "Enhanced security measures at the Port reduced piracy concerns.", | ||
"Growing cultural appreciation for | "Growing cultural appreciation for sacred Horehound exports boosted confidence.", | ||
"Trade | "Trade deals with Saluria expanded port usage significantly.", | ||
"Launch of new fertilizer shipments attracted | "Demand for silk from the Seamstresses of Rouge improved port revenue.", | ||
" | "Moonlit Tide celebrations increased ceremonial goods traffic.", | ||
"Successful marketing | "Launch of new Garganid fertilizer shipments attracted agriculture investors.", | ||
"Completion of high-speed rail connections | "Global interest in sacred resin exports enhanced port shipments.", | ||
"Successful marketing campaign highlighted the Port's location.", | |||
"Completion of high-speed rail connections boosted trade efficiency." | |||
} | } | ||
local decreaseReasons = { | local decreaseReasons = { | ||
"Pirate raids in the Strait disrupted shipments.", | "Pirate raids in the Strait of Haifa disrupted shipments to the Port.", | ||
"Unfavorable weather delayed | "Unfavorable weather delayed agriculture exports from Plains of Jogi.", | ||
"Labor shortages caused | "Labor shortages in the Port caused cargo-handling delays.", | ||
"A major fire in | "A major fire in Salurian sacred groves reduced resin exports.", | ||
"Rising costs | "Rising costs of Noctic-Rabrev cultivation hurt confidence.", | ||
"Unrest in Jangsong Province disrupted shipments.", | "Unrest in Jangsong Province disrupted camel meat shipments.", | ||
"Currency fluctuations affected | "Currency fluctuations in Vaeringheim affected stability.", | ||
"Economic sanctions on neighbors reduced export activity.", | "Economic sanctions on neighbors reduced export activity.", | ||
"Decline in ceremonial artifact production | "Decline in ceremonial artifact production lowered traffic.", | ||
"Cultural disputes caused delays | "Cultural disputes over Horehound caused export delays.", | ||
"Counterfeit relics | "Counterfeit relics in Nexa harmed the port's reputation.", | ||
" | "Fishing yields were outpaced by demand, reducing shipments.", | ||
"Decline in pearl quality lowered investor interest.", | "Decline in pearl quality from Anterran sources lowered investor interest.", | ||
"Shortage of ceremonial wine | "Shortage of ceremonial wine from Blood Vineyards cut exports.", | ||
"Unrest among | "Unrest among Maskmaker merchants disrupted trade agreements.", | ||
" | "Less demand for silk from the Seamstresses of Rouge impacted port flows.", | ||
"Collapse of | "Collapse of trade agreement with Tel-Amin lowered volume.", | ||
"Seasonal labor shortages | "Seasonal labor shortages shrank port shipments significantly.", | ||
" | "Disruptions in Suncliff Fisheries slowed aquatic exports.", | ||
"Economic downturn in | "Alternative shipping routes bypassed the Port altogether.", | ||
" | "Economic downturn in Lunalis Sancta reduced investor interest.", | ||
"Failure | "Lingering piracy concerns in Haifan waters harmed confidence.", | ||
"Natural disasters destroyed key | "Failure at Bassaridia Trade Symposium lowered traffic.", | ||
"Sabotage of port infrastructure caused | "Natural disasters near Acheron destroyed key routes.", | ||
"Sabotage of port infrastructure caused shipping delays." | |||
} | } | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 4. | -- 4. Utility: pick a random reason, ensuring no duplicates | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function | local function popUniqueReason(list) | ||
if | if #list == 0 then | ||
return "No reason available (out of reasons)." | |||
return " | |||
end | end | ||
local rnd = math.random(#list) | |||
local chosen = list[rnd] | |||
table.remove(list, rnd) | |||
return chosen | |||
end | end | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 5. | -- 5. Force half to "inc" half to "dec" | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function | local function assignUpDown(count) | ||
-- If count=8, we want 4 inc, 4 dec | |||
local | local half = math.floor(count / 2) | ||
local arr = {} | |||
local | for i=1, half do | ||
arr[i] = "Inc" | |||
end | |||
for i=half+1, count do | |||
arr[i] = "Dec" | |||
end | end | ||
-- shuffle | |||
for i=#arr,2,-1 do | |||
local j=math.random(i) | |||
arr[i], arr[j] = arr[j], arr[i] | |||
local | |||
end | end | ||
return arr | |||
end | end | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 6. | -- 6. generateTable function | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
function p.generateTable(frame) | |||
local dateString, dayOfYear = getCurrentDate() | |||
-- daily random seed | |||
local dateString | |||
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 198: | Line 173: | ||
local stockPrice = 30 | local stockPrice = 30 | ||
local out = | -- 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 updown = assignUpDown(n) | |||
local totalStartValue = 0 | |||
local totalEndValue = 0 | |||
-- Copy the reason arrays so we don't inadvertently remove them from the original | |||
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 | |||
for i, inv in ipairs(investors) do | |||
local stocksStart = inv.stocks | |||
local label = updown[i] -- "Inc" or "Dec" | |||
-- We want to ensure final change won't exceed 30%. | |||
local maxDelta = math.floor(stocksStart * 0.3) -- e.g. 30% of start | |||
if maxDelta < 1 then maxDelta=1 end | |||
local buy=0 | |||
local sell=0 | |||
if label == "Inc" then | |||
-- net up => buy - sell > 0 | |||
-- pick buy up to 30% (1..maxDelta), sell up to (buy-1) | |||
buy = math.random(1, maxDelta) | |||
sell = math.random(0, buy-1) | |||
else | |||
-- net down => sell - buy > 0 | |||
-- pick sell up to 30% (1..maxDelta), buy up to (sell-1) | |||
sell = math.random(1, maxDelta) | |||
buy = math.random(0, sell-1) | |||
end | end | ||
local | 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 | -- pick reason from incPool or decPool or incPool if no change | ||
local reason | |||
if changeWord == "Increased" then | |||
reason = popUniqueReason(incPool) | |||
elseif changeWord == "Decreased" then | |||
reason = popUniqueReason(decPool) | |||
else | |||
reason = popUniqueReason(incPool) -- or decPool, up to you | |||
end | end | ||
out = out .. string.format( | |||
[[ | |||
|- | |||
dateString, | | %s | ||
| %s | |||
| %d | |||
| %d | |||
| %d | |||
| %d | |||
| %d Polis | |||
| %d Polis | |||
| %s | |||
| %s | |||
]], | |||
dateString, | |||
inv.name, | |||
stocksStart, | |||
buy, | |||
sell, | |||
stocksEnd, | |||
valueStart, | |||
valueEnd, | |||
changeWord, | |||
reason | reason | ||
) | |||
end | end | ||
-- Totals row | -- Totals row | ||
out = out .. string.format( | |||
[[ | |||
|- | |||
| colspan="2" | Totals | |||
) | | | ||
| | |||
| | |||
| | |||
| %d Polis | |||
| %d Polis | |||
| | |||
| | |||
]], | |||
totalStartValue, | |||
totalEndValue | |||
) | |||
out = out .. "\n|}" | |||
return | return out | ||
end | end | ||
return p | return p | ||
Revision as of 17:25, 27 December 2024
Documentation for this module may be created at Module:BassaridMarketActivity/doc
---------------------------------------------------------
-- Module: BassaridiaDailyInvestorTable
-- Generates a daily table of investor stock activity
-- and associated reasons for increases/decreases.
--
-- Each new real-life 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).
---------------------------------------------------------
local p = {}
---------------------------------------------------------
-- 1. getCurrentDate
-- Returns "DD/MM/YYYY PSSC" plus 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 3-month model
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
-- (Each investor has a 'stocks' starting point.)
---------------------------------------------------------
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
---------------------------------------------------------
-- Enough reasons so that each investor can have a unique one.
---------------------------------------------------------
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 the port's reputation.",
"Discovery of new mineral resources near Acheron increased export potential.",
"A surge in ceremonial wine exports from the Blood Vineyards bolstered port ops.",
"Efficient cargo handling improvements boosted investor sentiment.",
"Seasonal agricultural exports from Plains of 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.",
"Growing cultural appreciation for sacred 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 port shipments.",
"Successful marketing campaign highlighted the Port's 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 investor interest.",
"Shortage of ceremonial wine from Blood Vineyards cut exports.",
"Unrest among Maskmaker merchants disrupted trade agreements.",
"Less demand for silk from the Seamstresses of Rouge impacted port 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: pick a random reason, ensuring no duplicates
---------------------------------------------------------
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 to "inc" half to "dec"
---------------------------------------------------------
local function assignUpDown(count)
-- If count=8, we want 4 inc, 4 dec
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. generateTable function
---------------------------------------------------------
function p.generateTable(frame)
local dateString, dayOfYear = getCurrentDate()
-- daily random seed
local localTime = os.date("*t")
local dailySeed = localTime.year * 1000 + localTime.yday
math.randomseed(dailySeed)
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 updown = assignUpDown(n)
local totalStartValue = 0
local totalEndValue = 0
-- Copy the reason arrays so we don't inadvertently remove them from the original
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
for i, inv in ipairs(investors) do
local stocksStart = inv.stocks
local label = updown[i] -- "Inc" or "Dec"
-- We want to ensure final change won't exceed 30%.
local maxDelta = math.floor(stocksStart * 0.3) -- e.g. 30% of start
if maxDelta < 1 then maxDelta=1 end
local buy=0
local sell=0
if label == "Inc" then
-- net up => buy - sell > 0
-- pick buy up to 30% (1..maxDelta), sell up to (buy-1)
buy = math.random(1, maxDelta)
sell = math.random(0, buy-1)
else
-- net down => sell - buy > 0
-- pick sell up to 30% (1..maxDelta), buy up to (sell-1)
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
-- pick reason from incPool or decPool or incPool if no change
local reason
if changeWord == "Increased" then
reason = popUniqueReason(incPool)
elseif changeWord == "Decreased" then
reason = popUniqueReason(decPool)
else
reason = popUniqueReason(incPool) -- or decPool, up to you
end
out = out .. string.format(
[[
|-
| %s
| %s
| %d
| %d
| %d
| %d
| %d Polis
| %d Polis
| %s
| %s
]],
dateString,
inv.name,
stocksStart,
buy,
sell,
stocksEnd,
valueStart,
valueEnd,
changeWord,
reason
)
end
-- Totals row
out = out .. string.format(
[[
|-
| colspan="2" | Totals
|
|
|
|
| %d Polis
| %d Polis
|
|
]],
totalStartValue,
totalEndValue
)
out = out .. "\n|}"
return out
end
return p