|
@@ -7,6 +7,7 @@ import cache
|
|
|
|
|
|
|
|
def main() -> None:
|
|
def main() -> None:
|
|
|
recipes: list[Recipe] = cache.get('https://api.prunplanner.org/data/recipes')
|
|
recipes: list[Recipe] = cache.get('https://api.prunplanner.org/data/recipes')
|
|
|
|
|
+ buildings: dict[str, Building] = {m['Ticker']: m for m in cache.get('https://api.prunplanner.org/data/buildings')}
|
|
|
materials: dict[str, Material] = {m['Ticker']: m for m in cache.get('https://api.prunplanner.org/data/materials')}
|
|
materials: dict[str, Material] = {m['Ticker']: m for m in cache.get('https://api.prunplanner.org/data/materials')}
|
|
|
raw_prices: list[RawPrice] = cache.get('https://refined-prun.github.io/refined-prices/all.json')
|
|
raw_prices: list[RawPrice] = cache.get('https://refined-prun.github.io/refined-prices/all.json')
|
|
|
prices: dict[str, Price] = {
|
|
prices: dict[str, Price] = {
|
|
@@ -16,20 +17,21 @@ def main() -> None:
|
|
|
|
|
|
|
|
profits = []
|
|
profits = []
|
|
|
for recipe in recipes:
|
|
for recipe in recipes:
|
|
|
- if profit := calc_profit(recipe, materials, prices):
|
|
|
|
|
|
|
+ if profit := calc_profit(recipe, buildings, materials, prices):
|
|
|
profits.append(profit)
|
|
profits.append(profit)
|
|
|
profits.sort(reverse=True)
|
|
profits.sort(reverse=True)
|
|
|
for p in profits:
|
|
for p in profits:
|
|
|
- print(f'{p.recipe:40} {p.profit:10.2f}\033[31m', end='')
|
|
|
|
|
|
|
+ print(f'{p.output:5} \033[32m{p.profit: 10,.0f}\033[31m', end='')
|
|
|
if p.low_volume:
|
|
if p.low_volume:
|
|
|
print(' low volume', end='')
|
|
print(' low volume', end='')
|
|
|
- if p.high_opex:
|
|
|
|
|
- print(' high opex', end='')
|
|
|
|
|
if p.heavy_logistics:
|
|
if p.heavy_logistics:
|
|
|
print(' heavy logistics', end='')
|
|
print(' heavy logistics', end='')
|
|
|
- print('\033[0m')
|
|
|
|
|
|
|
+ if p.high_opex:
|
|
|
|
|
+ print(' high opex', end='')
|
|
|
|
|
+ print(f'\n\033[30m{p.recipe:30} \033[33m{p.capex: 6,.0f} \033[35m{p.cost_per_day: 6,.0f}\033[0m')
|
|
|
|
|
|
|
|
-def calc_profit(recipe: Recipe, materials: typing.Mapping[str, Material], prices: typing.Mapping[str, Price]) -> Profit | None:
|
|
|
|
|
|
|
+def calc_profit(recipe: Recipe, buildings: typing.Mapping[str, Building], materials: typing.Mapping[str, Material],
|
|
|
|
|
+ prices: typing.Mapping[str, Price]) -> Profit | None:
|
|
|
try:
|
|
try:
|
|
|
(output,) = recipe['Outputs']
|
|
(output,) = recipe['Outputs']
|
|
|
except ValueError: # skip recipes that don't have exactly 1 output
|
|
except ValueError: # skip recipes that don't have exactly 1 output
|
|
@@ -40,6 +42,8 @@ def calc_profit(recipe: Recipe, materials: typing.Mapping[str, Material], prices
|
|
|
except KeyError: # skip recipes with thinly traded materials
|
|
except KeyError: # skip recipes with thinly traded materials
|
|
|
return
|
|
return
|
|
|
revenue = output_price.vwap * output['Amount']
|
|
revenue = output_price.vwap * output['Amount']
|
|
|
|
|
+ capex = sum(bm['Amount'] * prices[bm['CommodityTicker']].vwap
|
|
|
|
|
+ for bm in buildings[recipe['BuildingTicker']]['BuildingCosts'])
|
|
|
profit_per_run = revenue - cost
|
|
profit_per_run = revenue - cost
|
|
|
runs_per_day = 24 * 60 * 60 * 1000 / recipe['TimeMs']
|
|
runs_per_day = 24 * 60 * 60 * 1000 / recipe['TimeMs']
|
|
|
cost_per_day = cost * runs_per_day
|
|
cost_per_day = cost * runs_per_day
|
|
@@ -50,9 +54,10 @@ def calc_profit(recipe: Recipe, materials: typing.Mapping[str, Material], prices
|
|
|
materials[output['Ticker']]['Weight'] * output['Amount'],
|
|
materials[output['Ticker']]['Weight'] * output['Amount'],
|
|
|
materials[output['Ticker']]['Volume'] * output['Amount'],
|
|
materials[output['Ticker']]['Volume'] * output['Amount'],
|
|
|
) * runs_per_day
|
|
) * runs_per_day
|
|
|
- return Profit(recipe['RecipeName'], profit=profit_per_run * runs_per_day,
|
|
|
|
|
|
|
+ return Profit(output['Ticker'], recipe['RecipeName'], profit=profit_per_run * runs_per_day,
|
|
|
|
|
+ capex=capex,
|
|
|
|
|
+ cost_per_day=cost_per_day,
|
|
|
low_volume=output_price.average_traded < output_per_day * 20,
|
|
low_volume=output_price.average_traded < output_per_day * 20,
|
|
|
- high_opex=cost_per_day > 100_000,
|
|
|
|
|
heavy_logistics=logistics_per_day > 100)
|
|
heavy_logistics=logistics_per_day > 100)
|
|
|
|
|
|
|
|
class Recipe(typing.TypedDict):
|
|
class Recipe(typing.TypedDict):
|
|
@@ -66,6 +71,15 @@ class RecipeMat(typing.TypedDict):
|
|
|
Ticker: str
|
|
Ticker: str
|
|
|
Amount: int
|
|
Amount: int
|
|
|
|
|
|
|
|
|
|
+class Building(typing.TypedDict):
|
|
|
|
|
+ Ticker: str
|
|
|
|
|
+ AreaCost: int
|
|
|
|
|
+ BuildingCosts: list[BuildingMat]
|
|
|
|
|
+
|
|
|
|
|
+class BuildingMat(typing.TypedDict):
|
|
|
|
|
+ CommodityTicker: str
|
|
|
|
|
+ Amount: int
|
|
|
|
|
+
|
|
|
class Material(typing.TypedDict):
|
|
class Material(typing.TypedDict):
|
|
|
Ticker: str
|
|
Ticker: str
|
|
|
Weight: float
|
|
Weight: float
|
|
@@ -85,21 +99,25 @@ class Price:
|
|
|
|
|
|
|
|
@dataclasses.dataclass(eq=False, slots=True)
|
|
@dataclasses.dataclass(eq=False, slots=True)
|
|
|
class Profit:
|
|
class Profit:
|
|
|
|
|
+ output: str
|
|
|
recipe: str
|
|
recipe: str
|
|
|
profit: float
|
|
profit: float
|
|
|
|
|
+ capex: float
|
|
|
|
|
+ cost_per_day: float
|
|
|
low_volume: bool
|
|
low_volume: bool
|
|
|
- high_opex: bool
|
|
|
|
|
heavy_logistics: bool
|
|
heavy_logistics: bool
|
|
|
|
|
+ high_opex: bool = dataclasses.field(init=False)
|
|
|
score: float = dataclasses.field(init=False)
|
|
score: float = dataclasses.field(init=False)
|
|
|
|
|
|
|
|
def __post_init__(self) -> None:
|
|
def __post_init__(self) -> None:
|
|
|
|
|
+ self.high_opex = self.cost_per_day > 50_000
|
|
|
self.score = self.profit
|
|
self.score = self.profit
|
|
|
if self.low_volume:
|
|
if self.low_volume:
|
|
|
self.score *= 0.2
|
|
self.score *= 0.2
|
|
|
- if self.high_opex:
|
|
|
|
|
- self.score *= 0.8
|
|
|
|
|
if self.heavy_logistics:
|
|
if self.heavy_logistics:
|
|
|
self.score *= 0.5
|
|
self.score *= 0.5
|
|
|
|
|
+ if self.high_opex:
|
|
|
|
|
+ self.score *= 0.8
|
|
|
|
|
|
|
|
def __lt__(self, other: Profit) -> bool:
|
|
def __lt__(self, other: Profit) -> bool:
|
|
|
return self.score < other.score
|
|
return self.score < other.score
|