Prices and profit markers
How a town prices an item, how the trade penalty is built, and how the green and red profit markers on the trade screen are chosen, at full precision for game v1.4.8. For a plain-language version, see the trading guide. Trade rumors have their own page: Trade rumors.
No official module (SandBox, StoryMode, NavalDLC, BirthAndDeath) overrides any of the models or behaviors on this page. The numbers in the examples and tables are computed by the Python model in src/bannerlord/trade/.
At a glance
| What | Needs | Compares |
|---|---|---|
| Markers on your items | Appraiser or Whole Seller (Trade 25) | what you paid on average vs. the sell price here |
| Markers on the merchant's items | Caravan Master or Market Dealer (Trade 50) | this town's price factor vs. the average over all other towns |
Markers only appear in trade mode (InventoryMode.Trade), not when looting or using a stash.
Market state
Prices are computed by DefaultTradeItemPriceFactorModel. The market state is kept per town and per item category, in an ItemData record with four fields:
Supply: the town's supply value for the category.Demand: the town's demand value for the category.InStore: how many items of the category the town holds.InStoreValue: the total value of those items, Σ count·value over every item of the category in stock.
A village has no market of its own; it uses its bound town's market.
Base price factor
The base price factor base(cat) is the multiplier a town applies to the value of every item in category cat:
base(cat) = ( Demand / (0.1·Supply + 0.04·InStoreValue + 2) ) ^ p
Demand,Supply,InStoreValue: the town's market state forcat, defined above.p: the exponent.p = 0.3for animals,p = 0.6for everything else. The smaller exponent makes animal prices react less to the market.- Selling: when the player sells, the item's own value is added first,
InStoreValue += item.Value, and then the formula is applied. The item being sold already counts as stock. - Clamp: the result is clamped to
[0.1, 10]for trade goods and to[0.8, 1.3]for everything else.
Buy and sell price
buy price = max(1, ceil (value · base · (1 + penalty)))
sell price = max(1, floor(value · base / (1 + penalty)))
value: the item's value.base: the base price factor of the item's category in this town (with the selling adjustment when selling).penalty: the trade penalty below.- The buy price is rounded up and the sell price rounded down; neither goes below 1.
- When the merchant is a caravan and the client has Silver Tongue, the buy price is multiplied by
(1 − SilverTongue.SecondaryBonus).
Trade penalty
penalty comes from GetTradePenalty and is built in this order:
| Step | Effect |
|---|---|
| start | 0.06 |
| client at war with merchant's faction | +0.5 |
| client is not a caravan, selling equipment (not goods/animal/horse) | +(1.5 + 0.25·max(0, tier − 1)), ×(1 + Artisan Smith) for your own crafted weapons |
| client is not a caravan, selling a pack animal / a mount | +0.8 each |
| merchant is a village | +0.1 buying, +1.0 selling |
| merchant is a caravan | +2.0 buying a pack animal; then +0.1 buying, +1.0 selling |
| client is a caravan | ×0.5 |
| no client party | ×0.2 |
| client party | ×1 / (1 + Trade skill TradePenaltyReduction) |
| perks, as factors on the result | Village Network / Rumor Network (own faction village/town), Whole Seller (selling goods), Granary Accountant (selling food), Appraiser (selling equipment), Aserai trader feat −10%, Arms Dealer (selling weapons), Insurance Plans (buying food), Arenicos Mules (pack animals), Deeper Sacks, Arenicos Horses (mounts), Smuggler Connections (main party, crime rating > 0), Distributed Goods (buying in villages), Local Connection (selling horses), Tradeyard Foreman (selling pottery/tools/jewelry/cotton), Rapid Development (buying clay/iron/silver/cotton) |
Reading the table:
- The client is the party trading with the merchant; for the player this is the main party. The merchant is the town, village or caravan on the other side.
- The additive rows (+) come first, then the multiplicative rows (×) apply to the running total.
tieris the equipment's tier. A tier-1 item adds 1.5, each tier above 1 adds 0.25 more: tier 3 adds 2.0, tier 6 adds 2.75.TradePenaltyReductionis the reduction the client's Trade skill gives; it divides the penalty by1 + TradePenaltyReduction.×(1 + Artisan Smith)scales only the equipment term(1.5 + 0.25·max(0, tier − 1)), not the running total.- With no client party and no item-type terms the penalty is
0.06·0.2 = 0.012. For other items the item-type terms are added to 0.06 first, and the whole sum is then ×0.2. This is the penalty trade rumors are recorded with (see Trade rumors).
Example: the plain penalty
The main party buys or sells trade goods in a town, at peace, with no Trade skill reduction and no perks. Only the start row applies, so penalty = 0.06. This is the penalty used for the price curve below.
Example: selling tier-3 equipment
The main party sells a tier-3 item of value 1000 in a town, at peace, with no Trade skill reduction and no perks, where base = 1.0.
- Start:
penalty = 0.06. - Selling equipment, not a caravan:
+ (1.5 + 0.25·max(0, 3 − 1)) = + (1.5 + 0.5) = + 2.0, sopenalty = 2.06. - No other row applies; the client-party row divides by
1 + 0 = 1. - Sell price:
max(1, floor(1000 · 1.0 / (1 + 2.06))) = floor(1000 / 3.06) = floor(326.80) = 326.
The item sells for 326, about a third of its value.
Example: a caravan client at war
A caravan client at war with the merchant's faction, trading goods in a town, with no Trade skill reduction and no perks: (0.06 + 0.5) · 0.5 = 0.28. The equipment and pack-animal rows do not apply to a caravan client.
Price curve example
A trade good of value 40 in a town with Supply = 50 and Demand = 20, traded by the main party with penalty = 0.06, for stock values from 0 to 2000.
Line chart of buy price and sell price for an item worth 40 with supply 50 and demand 20, against the value of that item in the shop from 0 to 2000. Buy price falls from 80 to 18 and sell price from 62 to 15. Both cross the base value of 40 at a few hundred of stock.
A trade good worth 40 with supply 50 and demand 20.
| Goods in the town's stock (value) | Buy for | Sells here for |
|---|---|---|
| 0 | 80 | 62 |
| 100 | 61 | 49 |
| 200 | 51 | 42 |
| 300 | 44 | 37 |
| 400 | 39 | 33 |
| 500 | 36 | 30 |
| 600 | 33 | 28 |
| 700 | 31 | 26 |
| 800 | 29 | 24 |
| 900 | 27 | 23 |
| 1000 | 26 | 22 |
| 1100 | 25 | 21 |
| 1200 | 24 | 20 |
| 1300 | 23 | 19 |
| 1400 | 22 | 18 |
| 1500 | 21 | 18 |
| 1600 | 20 | 17 |
| 1700 | 20 | 16 |
| 1800 | 19 | 16 |
| 1900 | 19 | 15 |
| 2000 | 18 | 15 |
Step by step for the row with InStoreValue = 300, p = 0.6 (a trade good):
- Buying:
Demand / (0.1·50 + 0.04·300 + 2) = 20 / (5 + 12 + 2) = 20 / 19 = 1.0526. base = 1.0526 ^ 0.6 = 1.0313, inside the trade-good clamp[0.1, 10].- Buy price:
ceil(40 · 1.0313 · 1.06) = ceil(43.73) = 44. - Selling:
InStoreValuebecomes300 + 40 = 340, so20 / (5 + 13.6 + 2) = 20 / 20.6 = 0.9709. base = 0.9709 ^ 0.6 = 0.9824.- Sell price:
floor(40 · 0.9824 / 1.06) = floor(37.07) = 37.
The same steps for InStoreValue = 0 give base = (20 / 7) ^ 0.6 = 1.8774 and a buy price of ceil(79.60) = 80; selling, base = (20 / 8.6) ^ 0.6 = 1.6593 and a sell price of floor(62.61) = 62.
Profit markers
The marker is chosen by SPItemVM.UpdateProfitType, which calls GetProfitTypeFromDiff(a, b). There are five states, ProfitTypes:
| State | Value |
|---|---|
HighLoss |
−2 |
Loss |
−1 |
Default |
0 |
Profit |
1 |
HighProfit |
2 |
a and b are two prices whose meaning depends on the marker mode (below). The checks run in this order and the first match wins:
GetProfitTypeFromDiff(a, b):
a == 0 -> Default
a < 0.80·b -> HighProfit
a < 0.95·b -> Profit
a > 1.05·b -> Loss
a > 1.20·b -> HighLoss (unreachable: the 1.05 check returns first)
else -> Default
The HighLoss bug
HighLoss is never shown: any loss beyond 5% displays as plain Loss. Any a > 1.20·b also satisfies a > 1.05·b, and that check comes first and returns Loss. In the example table below, a paid price of 60 against a sell price of 44 (ratio 1.36) shows the same red as 50 (ratio 1.14).
Your inventory (Appraiser or Whole Seller)
a= your average purchase price for the item.b= the sell price in this town (ItemCost).
TradeSkillCampaignBehavior keeps one running average per ItemObject, for items without a modifier only. avg is the average price paid and count the number of items it covers:
buy n at total cost C: avg = (avg·count + C) / (count + n); count += n
sell k: count -= min(count, k); entry removed at count 0
outside trade mode: count is capped at what the party still holds; entry removed if none left
Selling lowers count but leaves avg unchanged.
Items never bought (looted, crafted, rewards) and items with a modifier (e.g. "Fine", "Rusty") have a = 0 or a = b and always show Default.
So: green = selling here earns ≥ 5% (≥ 20% for the strong marker) over what you paid; red = selling here loses > 5%.
Merchant inventory (Caravan Master or Market Dealer)
a= this town'sbase(cat).b= the meanbase(cat)over every other town in the world, computed when the trade screen opens (InventoryLogic.InitializeCategoryAverages).
Both sides use base only: no trade penalty, no perks, no stock change from the item itself. Green = this category is ≥ 5% (≥ 20%) cheaper here than the world average; red = > 5% dearer. Per category, not per item, and it includes towns you have never visited.
Marker examples
Your-inventory rows compare a paid price with a sell price of 44; merchant rows compare a local base with a world average of 1.
Number line from 0.6 to 1.4 split at 0.8, 0.95, 1.05 and 1.2 into strong green, green, no marker and red zones. Your goods: paid 30, 40, 50 and 60 against a sell price of 44 give ratios 0.68, 0.91, 1.14 and 1.36, marked strong green, green, red and red. Merchant: price levels 0.75, 1 and 1.15 against a world average of 1 give strong green, no marker and red.
Marker colors for goods you bought (compared with the price here) and for towns (compared with the world average).
| Case | Compared with | Marker |
|---|---|---|
| Paid 30 | sells here for 44 | Strong green (big profit) |
| Paid 40 | sells here for 44 | Green (profit) |
| Paid 50 | sells here for 44 | Red (loss) |
| Paid 60 | sells here for 44 | Red (loss) |
| Local price level 0.75 | world average 1 | Strong green (much cheaper than elsewhere) |
| Local price level 1 | world average 1 | None |
| Local price level 1.15 | world average 1 | Red (dearer than elsewhere) |
Step by step for "Paid 40":
a = 40,b = 44.ais not 0.0.80·b = 35.2; 40 is not below it, so notHighProfit.0.95·b = 41.8; 40 is below it, soProfit(green).
Step by step for "Paid 60":
a = 60,b = 44.0.80·b = 35.2and0.95·b = 41.8; 60 is below neither.1.05·b = 46.2; 60 is above it, soLoss(red). The1.20·b = 52.8check that would giveHighLossis never reached.
Game code
IL for every method on this page, decoded from game v1.4.8:
- DefaultTradeItemPriceFactorModel.il:
GetBasePriceFactor,GetPriceFactor,GetPrice,GetTradePenalty. - DefaultPartyTradeModel.il:
GetTradePenaltyFactor(Trade skill reduction). - TownMarketData.il and ItemData.il: market state per town and category.
- Town.il:
GetItemPrice,GetItemCategoryPriceIndex. - TradeCampaignBehavior.il: market initialisation and daily market updates.
- InventoryLogic.il and InventoryLogic.averages.il: item prices on the trade screen,
InitializeCategoryAverages. - InventoryTradeVM.avg.il:
GetAveragePrice. - TradeSkillCampaignBehavior.il and ItemTradeData.il: the running purchase average.
- DefaultPerks.InitializeAll.il: perk skill thresholds and bonuses.
Reproducing
The IL was extracted with the steps in How the findings were reversed:
- Snapshot the DLLs into
dll/v<ver>/:uv run python -m scripts.reversing.sync_dlls. - Dump a type's members:
rea inspect-managed-members dll/v1.4.8/<asm>.dll --type <Full.Type.Name> --format json --full-output > ev/<T>.json. - Decode the method bodies:
python3 scripts/reversing/ildump.py ev/<T>.json dll/v1.4.8/<asm>.dll '<regex on Type::method>'.
| Type | Assembly |
|---|---|
SPItemVM, ItemMenuVM (namespace …ViewModelCollection.Inventory), ProfitTypes |
TaleWorlds.CampaignSystem.ViewModelCollection |
TradeSkillCampaignBehavior, TradeRumorsCampaignBehavior, CaravansCampaignBehavior (…CampaignBehaviors), TradeRumor, InventoryLogic, TownMarketData, ItemData, DefaultTradeItemPriceFactorModel, DefaultPartyTradeModel, DefaultPerks |
TaleWorlds.CampaignSystem |
Changelog
- v1.4.8 (2026-11-10): First version from IL.