Price Change Mechanics

misterslack

Chieftain
Joined
Jul 21, 2007
Messages
42
This is an overview of the mechanics governing the buy/sell prices of commodities in Europe. It's a bit complex and there is some math involved. The game uses the term "yield" to refer to the commodities (horses, silver, rum, etc), so I'll use that term here.

First I'll go over the variables that determine the prices then I'll explain the actual mechanics.

1. YieldBoughtTotal
For each type of yield, this value tracks the total amount of that yield that you've bought minus the total amount you've sold throughout the game. It starts off at zero. For example, if you sell 100 silver, the YieldBoughtTotal for silver goes down by 100. If you buy 300 horses, the YieldBoughtTotal for horses goes up by 300.

2. PriceChangeThreshold
For each multiple of this value that YieldBoughtTotal exceeds (positively or negatively) the price has a higher and higher chance of changing. It's stored in Assets\XML\Terrain\CIV4YieldInfos.xml. The threshold values are:
  • 1500 for cloth, coats, rum, cigars
  • 1000 for food, lumber, cotton, fur, sugar, tobacco, ore
  • 600 for tools, guns, horses, goods
  • 100 for silver (really low!)
This value is modified by handicap percentage based on your difficulty level. The percentages are:
  • Pilgrim 200%
  • Pioneer 120%
  • Explorer 100%
  • Conquistador 90%
  • Governer 80%
  • Patriot 70%
  • Revolutionary 60%
These values are stored in Assets\XML\GameInfo\CIV4HandicapInfo.xml

3. BuyPriceLow, BuyPriceHigh, SellPriceDifference
These values determine the starting price of the yield. The price Europe pays you for the yield, called the "buy price", is simply a random number between BuyPriceLow and BuyPriceHigh, inclusive. The price you pay to Europe for the yield, called the "sell price", is the buy price plus SellPriceDifference. The values are:
  • high, low, difference
  • Food 0, 2, 8
  • Lumber 0, 1, 3
  • Silver 19, 19, 1
  • Cotton 3, 5, 2
  • Fur 4, 6, 2
  • Sugar 4, 6, 2
  • Tobacco 3, 5, 2
  • Ore 2, 4, 3
  • Cloth 8, 12, 1
  • Coats 8, 12, 1
  • Rum 8, 12, 1
  • Cigars 8, 12, 1
  • Tools 1, 2, 1
  • Muskets 3, 5, 3
  • Horses 1, 2, 1
  • Trade Goods 1, 2, 1
Note again that the values are random; start up a game and you'll see cloth, coats, rum and cigars all at different starting prices, even though they all have the same high/low/difference values. Also, these are not in any way limits on the maximum or minimum price. The maximum price for any yield is unbounded (I think), and the minimum price is always 1.

Again these values are stored in Assets\XML\Terrain\CIV4YieldInfos.xml.

4. PriceCorrectionPercent
This value isn't really a percent as it's name says, but a multiplier that helps determines the chance of the price changing when YieldBoughtTotal exceeds PriceChangeThreshold.
The value is 5 for silver, and 1 for everything else. Stored in Assets\XML\Terrain\CIV4YieldInfos.xml.

Now for the actual mechanics. Each turn the game determines a "target price" for each yield, and then does a random check to see if the actual price changes by 1 towards the target price (+1 if the target price is higher, -1 if the target price is lower). The actual price can only change by 1 each turn. The target price starts as a random number between BuyPriceLow and BuyPriceHigh. Then you add YieldBoughtTotal / PriceChangeThreshold (remember YieldBoughtTotal can be negative). The division is an integer division so decimal remainders are dropped. Next you take the absolute value of the difference between the target price and the actual price. and multiply it by PriceCorrectionPercent (5 for silver, and 1 for everything else; silver is volatile). This final value is the percent chance of a price change occurring.

If that seems complex, let's do muskets as an example. The musket starting price is between 3 and 5, so we'll say the game rolls a 4. Initially your YieldBoughtTotal is zero. PriceChangeThreshold for muskets is 600. Let's look at what happens if you don't buy or sell any muskets. Each turn the game computes the target price, which will be another random number between 3 and 5, let's say it rolls a 5. YieldBoughtTotal is zero, so the target price isn't modified. The difference between the target price (5) and the actual price (4) is 1, and the PriceCorrectionPercent for muskets is 1, so there is a 1% chance of a price change this turn. If a price change did occur, it would be +1 from 4 to 5, always towards the target price. Had you rolled a 3 for the target price, the price would have a 1% chance of going down by one this turn. The key here is that the price always has a small chance of fluctuating by a bit even if you don't trade in that yield.

Now let's say you go and buy 1000 muskets to fight the Spanish. Your YieldBoughtTotal is now 1000. The target price will now be computed as random[3, 5] + 1000/600, or random[3, 5] + 1 since decimals are truncated. Let's say the actual price is still 4, and you roll a 5 on the random, then the target price would be 6. The difference is 6-4 = 2, so there would be a 2% chance of the price going up.

The spanish are really pressing you and you've had to buy 3000 more muskets. Your YieldBoughtTotal is now 1000+3000 = 4000. You've gotten lucky however, and the price is hasn't gone up from 4. Now your target price is going to be random[3, 5] + 4000/600 = random[3, 5] + 6 = 9 to 11. Your chance of the price going up is between 5% and 7% each turn.

Let's see how volatile silver is. The high and low are both 19, so the price doesn't have any random variance. The PriceChangeThreshold is only 100(!!), and the PriceCorrectionPercent is 5 instead of the usual one, so your chance of change will be five times higher than it normally would be. Let's say you sell 300 silver so your YieldBoughtTotal is -300 (remember it goes down when you sell). Then the target price is 19 - 300/100 = 16. The starting price is always 19, so the difference is 19-16 = 3%, but we multiply by PriceCorrectionPercent to get 5 * 3% = 15% of the price dropping! Silver is very volatile indeed.

Finally, the Mercantile trait of the Dutch: it simply cuts your trading volume in half. So if you sell 300 tobacco, it only counts as you having sold 150, so your YieldBoughtTotal goes up or down half as quickly. This means you can sell twice as many goods as the competition before you get the same price increase.

Here is C++ pseudo-code for process (the actual function is CvPlayer::doPrices() in CvGameCoreDLL\CvPlayer.cpp).

Code:
for each yield
{
	int newPrice = random(BuyPriceLow, BuyPriceHigh) + (YieldBoughtTotal / PriceChangeThreshold);

	if (random(100) < PriceCorrectionPercent * abs(newPrice - currentPrice))
	{
		newPrice = clamp(currentPrice - 1, newPrice, currentPrice + 1);
		setYieldBuyPrice(newPrice);
	}
}
 
Nice work.

Couple of points that I want to add.

The actions of other Europeans seem to have no effect on your prices. Does not matter how expensive silver is in Madrid compared to London.

The prices will lag behind the target price. The target price moves linearly as you trade, (and could go negative) but the actual price moves proportionally to the difference between the the target and actual, which will delay the change.

It is worth noting the the price of goods will not recover over time, outside of the random variation between buylow and buyhigh. Silver will only increase in price if you start buying it. Other goods will only recover in price after they reach their target value, as the difference between buylow and buyhigh allows it to oscillate around the target.

If you buy a galleon full of silver, the target price will change by 6, and so the price will move by 6 (hopefully quite soon), allowing you to sell it all at the higher price and pocket the difference. Not really a feasible tactic because you pay tax on the total, not the difference, and you need a lot of gold, and I have not bothered to calculate the time scale for the price change.
 
The actions of other Europeans seem to have no effect on your prices. Does not matter how expensive silver is in Madrid compared to London.

Great change compared to original, were prices of muskets inflated and prices of silver deflated like mad on higher difficulties due to AI actions.
 
The actions of other Europeans seem to have no effect on your prices. Does not matter how expensive silver is in Madrid compared to London.

This is correct and I should have added this. The code is actually present: buying/selling in other European markets would add a percentage of the volume to your totals, so if you're the English, and the Spanish buy 100 guns, and the market correlation constant was 10%, you'd get 100 * 10% = 10 to your totals. However this constant, called EUROPE_MARKET_CORRELATION_PERCENT in the XML, is currently zero.
 
Is there an adjustment for Gamespeed? I can't find it.
More Turns -> More Produktion -> Lower Price at the end of the Epic/Marathon Game.
 
I guess it got set to zero at some point during development. Shame, as I quite liked selling muskets in Europe in the original, even without using the tax-free soldiers exploit.

I would also have liked to see a units/turn threshold, rather than total to date, and prices updated after each transaction, rather than each turn.
 
In the original colonization was the price of the 4 finish cash crop goods(coats, cloth, cigars, rum) linked, if the price of one of those would drop, would the price of another of them rise. The overage price of all those 4 always stayed around 10. It seems this is no longer the case in col2.
 
Awesomeness. Great thread. I have already revamped my econ system since I play on super huge maps with marathon settings and max civs I doubled all the thresholds and boosted silver 10x.
 
Great post. Does anyone else feel that the price changes for processed goods are far too low? If I understand the math correctly, if you have sold 3000 coats (a rdidicolous amount) the price would only have a 2% chance on average to go down.
 
2% per turn

10 turns= 20% chance of price change, and the more you sell the higher the chance. And yes 10 turn does not quite = 20%, but I don't want to do the math right now.

How hard would it be to increase the number of good allowed before the price changes by, x times the game turn number?
 
So if i read this correctly, changing PriceCorrectionPercent to 0 will fix the prices for the entire game ?
 
I think the game needs a certain base supply and demand for goods in Europe (i.e. the YieldBoughtTotal should be normalized towards 0)
 
Yes, Exactly ^^

As it is now only one side of the market is present. Nothing happens in Europe, so basicly all the good you ever sold there stick around at the Docks forever, making the price never recover. I wonder why the Europeans buy all that booze from me, if they are notgoing to drink it.

There need to be some decay factor in the YieldBoughtTotal.

I think it might be good to make it decay quite fast in fact.
With a bit of randomnes to it.
But correlate all 4 euro-markets and make all goods more volatile.

So if the pesky Dutch delivered a Merchantman full of booze just the very turn before your ship arrived, the prices would be very low. But it would recover eventually.
Over not too long - maybe a few turns - so Selling right now or making your Ship wait in europe for a while would become a viable decision.
 
Should be easy to write in the end of turn script something like:

yeildboughttotal= yeildboughttotal + othernationsyieldboughtthisturn

if yieldboughttotal > 0 then
____ yieldboughttotal=yieldboughttotal - x

else if yieldboughttotal < 0 then
____ yieldboughttotal=yieldboughttotal + x
 
If you implement a "constant" decay, you should think about Europe's population growing during the game and so demands for finished goods will also grow.

Another method would be a decay by a certain percentage, simulating an invisible market which will slowly balance the prices again ... if you sell too much, prices will drop and more people can afford to buy this good until prices recover ... The same with buying a lot of pieces of a certain good ... prices will go up, manufacturers in homeland will increase production ... prices will drop

You could use similar mechanics as the Rebel Sentiment Decay in cities :

yieldboughttotal = ( (iTurnfactor -1) * yieldboughttotal ) / iTurnfactor;​

iTurnfactor for Rebel Sentiment is 10 (normal) and 30 (marathon), so on normal the stack would decay by 10% per turn and on marathon by 3.33% ...

If the decay is to strong, you can use smaller values like 1% and 0.33% ... (iTurnfactor = 100 and 300.)
If you do not sell too many goods, the decay will give you stable market prices ... if prices drop, you can stop selling and prices will recover ...

However I have the feeling that people might try to exploit this by massing goods and then selling everything in one turn at high prices ... To counter this, the prices for a good should not only be calculated each turn but also be calculated new after each traded stack of 100 / 300 pieces, including equipment of pioneers, soldiers, dragoons, scouts with tools, guns or horses.
 
What about this option: Add an unlimited size warehouse in Europe where all your sold goods are stored (the number of goods there should be displayed). Each turn, supply and demand in Europe are chosen randomly (could be influenced by any number of factors, for example the price evaluated relative to a certain base value) and the price of the goods would solely depend on supply and demand. Whenever you sell a good, the price is re-checked which will make the good cheaper eventually; when you buy one, it will similarly become more expensive.
Also at the start of turn, the demand should be deducted and the supply should be added to the pool. This is basically the same as a decay but adds a lot of flavor. Furthermore, you can run out of goods if you buy too much of a certain item. Demand should obviously be high for colonial goods while supply should be high for tools, horses and stuff.
 
In case anybody is interested...
it is very easy to correlate the markets of the European colonizing powers.

In GlobalDefines.xml there is an entry for that

<DefineName>EUROPE_MARKET_CORRELATION_PERCENT</DefineName>
<iDefineIntVal>0</iDefineIntVal>

Change it if you want prices in the various countries to influence each other.
Thus if you buy muskets or tools like mad you would also make them more expensive for your rivals.
 
Indeed it is pretty weird some goods consumption algorithm was written for Natives and not for Europeans.
 
Top Bottom