Bannerlord mechanics

Prices and profit markers

Bannerlord v1.4.8 · checked 2026-11-10 · updated 2026-11-12

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:

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

Buy and sell price

buy  price = max(1, ceil (value · base · (1 + penalty)))
sell price = max(1, floor(value · base / (1 + penalty)))

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:

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.

  1. Start: penalty = 0.06.
  2. Selling equipment, not a caravan: + (1.5 + 0.25·max(0, 3 − 1)) = + (1.5 + 0.5) = + 2.0, so penalty = 2.06.
  3. No other row applies; the client-party row divides by 1 + 0 = 1.
  4. 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):

  1. Buying: Demand / (0.1·50 + 0.04·300 + 2) = 20 / (5 + 12 + 2) = 20 / 19 = 1.0526.
  2. base = 1.0526 ^ 0.6 = 1.0313, inside the trade-good clamp [0.1, 10].
  3. Buy price: ceil(40 · 1.0313 · 1.06) = ceil(43.73) = 44.
  4. Selling: InStoreValue becomes 300 + 40 = 340, so 20 / (5 + 13.6 + 2) = 20 / 20.6 = 0.9709.
  5. base = 0.9709 ^ 0.6 = 0.9824.
  6. 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)

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)

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":

  1. a = 40, b = 44. a is not 0.
  2. 0.80·b = 35.2; 40 is not below it, so not HighProfit.
  3. 0.95·b = 41.8; 40 is below it, so Profit (green).

Step by step for "Paid 60":

  1. a = 60, b = 44.
  2. 0.80·b = 35.2 and 0.95·b = 41.8; 60 is below neither.
  3. 1.05·b = 46.2; 60 is above it, so Loss (red). The 1.20·b = 52.8 check that would give HighLoss is never reached.

Game code

IL for every method on this page, decoded from game v1.4.8:

Reproducing

The IL was extracted with the steps in How the findings were reversed:

  1. Snapshot the DLLs into dll/v<ver>/: uv run python -m scripts.reversing.sync_dlls.
  2. 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.
  3. 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