|
@@ -12,8 +12,8 @@ def main() -> None:
|
|
|
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] = {
|
|
|
- p['MaterialTicker']: Price(p['VWAP7D'], p['AverageTraded7D']) for p in raw_prices # pyright: ignore[reportArgumentType]
|
|
|
|
|
- if p['ExchangeCode'] == 'IC1' and p['VWAP7D'] is not None
|
|
|
|
|
|
|
+ p['MaterialTicker']: Price(p['VWAP7D'], p['AverageTraded7D'], p['VWAP30D']) for p in raw_prices # pyright: ignore[reportArgumentType]
|
|
|
|
|
+ if p['ExchangeCode'] == 'IC1'
|
|
|
}
|
|
}
|
|
|
habitation: typing.Mapping[Worker, str] = {
|
|
habitation: typing.Mapping[Worker, str] = {
|
|
|
'Pioneers': 'HB1',
|
|
'Pioneers': 'HB1',
|
|
@@ -43,12 +43,15 @@ def calc_profit(recipe: Recipe, buildings: typing.Mapping[str, Building], hab_ar
|
|
|
(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
|
|
|
return
|
|
return
|
|
|
- try:
|
|
|
|
|
- output_price = prices[output['Ticker']]
|
|
|
|
|
- cost = sum(prices[input['Ticker']].vwap * input['Amount'] for input in recipe['Inputs'])
|
|
|
|
|
- except KeyError: # skip recipes with thinly traded materials
|
|
|
|
|
|
|
+ output_price = prices[output['Ticker']]
|
|
|
|
|
+ if output_price.vwap_7d is None or output_price.average_traded_7d is None: # skip recipes with thinly traded output
|
|
|
return
|
|
return
|
|
|
- revenue = output_price.vwap * output['Amount']
|
|
|
|
|
|
|
+ cost = 0
|
|
|
|
|
+ for input in recipe['Inputs']:
|
|
|
|
|
+ if (input_cost := prices[input['Ticker']].vwap_7d) is None:
|
|
|
|
|
+ return # skip recipes with thinly traded inputs
|
|
|
|
|
+ cost += input_cost * input['Amount']
|
|
|
|
|
+ revenue = output_price.vwap_7d * output['Amount']
|
|
|
building = buildings[recipe['BuildingTicker']]
|
|
building = buildings[recipe['BuildingTicker']]
|
|
|
area = building['AreaCost'] + sum(hab_area_cost[worker] * building[worker] for worker in hab_area_cost)
|
|
area = building['AreaCost'] + sum(hab_area_cost[worker] * building[worker] for worker in hab_area_cost)
|
|
|
capex = building_construction_cost(building, prices) + \
|
|
capex = building_construction_cost(building, prices) + \
|
|
@@ -77,7 +80,7 @@ def calc_profit(recipe: Recipe, buildings: typing.Mapping[str, Building], hab_ar
|
|
|
average_traded_7d=output_price.average_traded_7d)
|
|
average_traded_7d=output_price.average_traded_7d)
|
|
|
|
|
|
|
|
def building_construction_cost(building: Building, prices: typing.Mapping[str, Price]) -> float:
|
|
def building_construction_cost(building: Building, prices: typing.Mapping[str, Price]) -> float:
|
|
|
- return sum(bc['Amount'] * prices[bc['CommodityTicker']].vwap for bc in building['BuildingCosts'])
|
|
|
|
|
|
|
+ return sum(bc['Amount'] * prices[bc['CommodityTicker']].vwap_7d for bc in building['BuildingCosts'])
|
|
|
|
|
|
|
|
def building_daily_cost(building: Building, prices: typing.Mapping[str, Price]) -> float:
|
|
def building_daily_cost(building: Building, prices: typing.Mapping[str, Price]) -> float:
|
|
|
consumption = {
|
|
consumption = {
|
|
@@ -91,7 +94,8 @@ def building_daily_cost(building: Building, prices: typing.Mapping[str, Price])
|
|
|
for worker, mats in consumption.items():
|
|
for worker, mats in consumption.items():
|
|
|
workers = building[worker]
|
|
workers = building[worker]
|
|
|
for mat, per_100 in mats:
|
|
for mat, per_100 in mats:
|
|
|
- cost += prices[mat].vwap * workers * per_100 / 100
|
|
|
|
|
|
|
+ mat_price = prices[mat]
|
|
|
|
|
+ cost += (mat_price.vwap_7d or mat_price.vwap_30d) * workers * per_100 / 100
|
|
|
return cost
|
|
return cost
|
|
|
|
|
|
|
|
Worker = typing.Literal['Pioneers', 'Settlers', 'Technicians', 'Engineers', 'Scientists']
|
|
Worker = typing.Literal['Pioneers', 'Settlers', 'Technicians', 'Engineers', 'Scientists']
|
|
@@ -133,11 +137,13 @@ class RawPrice(typing.TypedDict):
|
|
|
PriceAverage: int
|
|
PriceAverage: int
|
|
|
VWAP7D: float | None # volume-weighted average price over last 7 days
|
|
VWAP7D: float | None # volume-weighted average price over last 7 days
|
|
|
AverageTraded7D: float | None # averaged daily traded volume over last 7 days
|
|
AverageTraded7D: float | None # averaged daily traded volume over last 7 days
|
|
|
|
|
+ VWAP30D: float | None
|
|
|
|
|
|
|
|
@dataclasses.dataclass(eq=False, frozen=True, slots=True)
|
|
@dataclasses.dataclass(eq=False, frozen=True, slots=True)
|
|
|
class Price:
|
|
class Price:
|
|
|
- vwap: float
|
|
|
|
|
- average_traded_7d: float
|
|
|
|
|
|
|
+ vwap_7d: float | None
|
|
|
|
|
+ average_traded_7d: float | None
|
|
|
|
|
+ vwap_30d: float | None
|
|
|
|
|
|
|
|
@dataclasses.dataclass(eq=False, frozen=True, slots=True)
|
|
@dataclasses.dataclass(eq=False, frozen=True, slots=True)
|
|
|
class Profit:
|
|
class Profit:
|