Skip to content

Commit 3f46004

Browse files
committed
Add buying price section
1 parent 4906731 commit 3f46004

File tree

7 files changed

+252
-0
lines changed

7 files changed

+252
-0
lines changed

src/SUMMARY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
- [Towns](./towns.md)
2020
- [Ticks](./towns/ticks.md)
2121
- [Population](./towns/population.md)
22+
- [Ware Prices](./towns/ware-prices.md)
23+
- [Base Price](./towns/ware-prices/base-price.md)
24+
- [Thresholds](./towns/ware-prices/thresholds.md)
25+
- [Buying Price](./towns/ware-prices/buying-price.md)
2226
- [Shipyard](./towns/shipyard.md)
2327
- [Taxes](./towns/taxes.md)
2428
- [Tavern](./towns/tavern.md)

src/towns/ware-prices.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Ware Prices
2+
3+
The buying price depends the ratio of *ware price thresholds* and the *remaining amount*.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Base Price
2+
The `ware_base_prices` table at `0x00673A18` defines the following *base prices*:
3+
4+
|Ware|Base Price|
5+
|-|-|
6+
|Grain|0.055000003|
7+
|Meat|0.47855002|
8+
|Fish|0.22005001|
9+
|Beer|0.17399999|
10+
|Salt|0.1425|
11+
|Honey|0.55000001|
12+
|Spices|1.4|
13+
|Wine|1.1|
14+
|Cloth|1.034|
15+
|Skins|3.3824999|
16+
|WhaleOil|0.41249999|
17+
|Timber|0.027500002|
18+
|IronGoods|1.278|
19+
|Leather|1.12|
20+
|Wool|0.44000003|
21+
|Pitch|0.278|
22+
|PigIron|0.44000003|
23+
|Hemp|0.22000001|
24+
|Pottery|0.85499996|
25+
|Bricks|0.039900005|
44.8 KB
Loading
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Buy Price
2+
The `get_buy_price` function at `0x0052E430` takes a ware, town, and buy amount and returns the transaction price.
3+
4+
## Formula
5+
The price formula operates on 5 intervals, and the 4 price thresholds specify the bounds.
6+
7+
|Interval|Bounds|
8+
|-|-|
9+
|0|[0; \\(t\_0\\)]|
10+
|1|[\\(t\_0\\); \\(t\_1\\)]|
11+
|2|[\\(t\_1\\); \\(t\_2\\)]|
12+
|3|[\\(t\_2\\); \\(t\_3\\)]|
13+
|4|[\\(t\_3\\); \\(\inf\\)]|
14+
15+
Within every interval \\(i\\) the price \\(p\_i\\) is defined as:
16+
\\[
17+
\begin{aligned}
18+
p\_{i} &= p\_{base} * w\_{i} * f\_{i}
19+
\end{aligned}
20+
\\]
21+
22+
where \\(w\_b\\) is the amount being bought from \\(i\\) and \\(f\\) is defined as:
23+
\\[
24+
\begin{aligned}
25+
f\_4 &= 0.6\\\\
26+
f_{i} &= m_i - v_i \underbrace{\frac{w\_{relative\\_stock} - w\_{relative\\_remain}}{2 * \text{interval_width}}}\_{\in [0; 1]}
27+
\end{aligned}
28+
\\]
29+
30+
where \\(w\_{relative\\_stock}\\) and \\(w\_{relative\\_remain}\\) are the stock's and remainder's offsets in the interval
31+
and \\(m\_i\\) and \\(v\_i\\) are defined as:
32+
33+
|Bracket|\\(m\_i\\)|\\(v\_i\\)|
34+
|-|-|-|
35+
|0|4|2.5|
36+
|1|1.5|0.5|
37+
|2|1.0|0.2|
38+
|3|0.8|0.2|
39+
40+
## Example
41+
Let's assume we buy pig iron from a town with the following stock and thresholds:
42+
43+
|Threshold|Value|
44+
|-|-|
45+
|Stock|110000|
46+
|t0|20000|
47+
|t1|60000|
48+
|t2|70000|
49+
|t3|80000|
50+
51+
If we buy one bundle (2000), the resulting prices at different stock levels would be:
52+
![image](buying-price-pigiron.png)
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import logging
2+
from matplotlib import pyplot as plt
3+
import numpy as np
4+
import math
5+
import pytest
6+
7+
LOGGER = logging.getLogger()
8+
F_MAX = [4, 1.5, 1.0, 0.8]
9+
F_VAR = [2.5, 0.5, 0.2, 0.2]
10+
SAMPLE_THRESHOLDS = [20_000, 60_000, 70_000, 80_000]
11+
PIG_IRON_BASE_PRICE = 0.44000003
12+
13+
14+
def get_price(
15+
stock: int, buy_amount: int, thresholds: list[int], base_price: float
16+
) -> float:
17+
if buy_amount > stock:
18+
raise Exception(f"buy_amount {buy_amount} > stock {stock}")
19+
price = 0
20+
interval = 0
21+
remaining = stock - buy_amount
22+
LOGGER.debug(f"get_price for {buy_amount} of {stock} ({remaining} remaining)")
23+
while interval < 4:
24+
if remaining < thresholds[interval]:
25+
# Remaining is smaller than the end of the threshold bracket, therefore we are buying from it
26+
interval_start = 0 if interval == 0 else thresholds[interval - 1]
27+
interval_end = thresholds[interval]
28+
interval_size = interval_end - interval_start
29+
w_interval_stock = min(stock, interval_end) # Smaller of stock and interval
30+
w_relative_stock = (
31+
w_interval_stock - interval_start
32+
) # Distance of stock from start of interval
33+
34+
# Get the amount in the current bracket (i.e. everything until the next threshold or stock)
35+
w_b = w_interval_stock - max(remaining, interval_start)
36+
w_relative_remain = w_relative_stock - w_b
37+
38+
new_bracket_stock = w_interval_stock - w_b
39+
# f = get_factor2(interval, interval_stock, new_bracket_stock, thresholds)
40+
f = get_factor(interval, w_relative_stock, w_relative_remain, interval_size)
41+
LOGGER.debug(f"{w_interval_stock} {new_bracket_stock} {f}")
42+
bracket_price = base_price * w_b * f
43+
LOGGER.debug(
44+
f"Buying {w_b} below in {interval} with f={f} for {bracket_price} ({w_relative_remain} remaining)"
45+
)
46+
price += bracket_price
47+
48+
if stock <= interval_end:
49+
break
50+
interval += 1
51+
52+
if remaining + buy_amount > thresholds[3]:
53+
interval_start = thresholds[3]
54+
interval_end = stock
55+
w_b = interval_end - max(remaining, interval_start)
56+
f = 0.6
57+
LOGGER.debug(f"Buying {w_b} above t3 with f={f}")
58+
price += base_price * w_b * f
59+
60+
return price
61+
62+
63+
def get_factor(
64+
interval: int, w_relative_stock: int, w_relative_remain: int, interval_width: int
65+
) -> int:
66+
LOGGER.debug(
67+
f"get_factor(interval={interval}, w_interval_stock={w_relative_stock}, w_relative_remain={w_relative_remain})"
68+
)
69+
if interval == 4:
70+
return 0.6
71+
else:
72+
middle = (w_relative_stock + w_relative_remain) / 2
73+
return F_MAX[interval] - F_VAR[interval] * (middle) / (interval_width)
74+
75+
76+
def plot_example1():
77+
plt.clf()
78+
params = {"mathtext.default": "regular"}
79+
plt.rcParams.update(params)
80+
fig, (ax1) = plt.subplots(1, 1)
81+
x = []
82+
t0_f_y = []
83+
t0_pigiron_y = []
84+
for stock in range(2000, SAMPLE_THRESHOLDS[-1] + 10_000):
85+
# for stock in range(17000, 23000):
86+
x.append(stock)
87+
t0_pigiron_y.append(
88+
get_price(stock, 2000, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
89+
)
90+
if len(t0_pigiron_y) > 2 and t0_pigiron_y[-1] > t0_pigiron_y[-2]:
91+
raise Exception(f"At {stock} stock, we have an increase in price")
92+
93+
ax1.plot(x, t0_pigiron_y)
94+
# """
95+
ax1.axvline(SAMPLE_THRESHOLDS[0])
96+
ax1.text(SAMPLE_THRESHOLDS[0], 0, "$t_0$", va="bottom")
97+
ax1.axvline(SAMPLE_THRESHOLDS[1])
98+
ax1.text(SAMPLE_THRESHOLDS[1], 0, "$t_1$", va="bottom")
99+
ax1.axvline(SAMPLE_THRESHOLDS[2])
100+
ax1.text(SAMPLE_THRESHOLDS[2], 0, "$t_2$", va="bottom")
101+
ax1.axvline(SAMPLE_THRESHOLDS[3])
102+
ax1.text(SAMPLE_THRESHOLDS[3], 0, "$t_3$", va="bottom")
103+
# """
104+
105+
ax1.set_title("Buying Price of 1 Pigiron Bundle")
106+
ax1.set_xlabel("Stock")
107+
ax1.set_ylabel("Price")
108+
109+
plt.tight_layout()
110+
plt.savefig("buying-price-pigiron.png", dpi=150)
111+
112+
113+
def tests():
114+
stock = 2_000
115+
buy_amount = 1 * 2000
116+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
117+
assert price == pytest.approx(3410.000212490559)
118+
119+
stock = 4_000
120+
buy_amount = 1 * 2000
121+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
122+
assert price == pytest.approx(3190.00019878149)
123+
124+
stock = 18_000
125+
buy_amount = 1 * 2000
126+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
127+
assert price == pytest.approx(1649.999995396131)
128+
129+
stock = 23_000
130+
buy_amount = 1 * 2000
131+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
132+
assert price == pytest.approx(1298.000080883503)
133+
134+
stock = 23_000
135+
buy_amount = 5 * 2000
136+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
137+
assert price == pytest.approx(7922.750493697822)
138+
139+
stock = 55_000
140+
buy_amount = 1 * 2000
141+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
142+
assert price == pytest.approx(946.0000589489937)
143+
144+
stock = 65_000
145+
buy_amount = 1 * 2000
146+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
147+
assert price == pytest.approx(809.6001041603122)
148+
149+
stock = 75_000
150+
buy_amount = 1 * 2000
151+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
152+
assert price == pytest.approx(633.6000931930575)
153+
154+
stock = 110_000
155+
buy_amount = 50 * 2000
156+
price = get_price(stock, buy_amount, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE)
157+
assert price == pytest.approx(46310.00288575888)
158+
159+
160+
logging.basicConfig()
161+
tests()
162+
logging.getLogger().setLevel(logging.INFO)
163+
plot_example1()
164+
logging.getLogger().setLevel(logging.DEBUG)
165+
LOGGER.info("success!")
166+
167+
LOGGER.debug(get_price(22_000, 2_000, SAMPLE_THRESHOLDS, PIG_IRON_BASE_PRICE))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Thresholds

0 commit comments

Comments
 (0)