|
|
@@ -28,10 +28,11 @@ for (const key of Object.keys(expertise)) {
|
|
|
expertiseSelect.appendChild(option);
|
|
|
}
|
|
|
|
|
|
+const formatDecimal = new Intl.NumberFormat(undefined,
|
|
|
+ {maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: 'lessPrecision'}).format;
|
|
|
+const formatWhole = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0}).format;
|
|
|
+
|
|
|
async function render() {
|
|
|
- const formatDecimal = new Intl.NumberFormat(undefined,
|
|
|
- {maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: 'lessPrecision'}).format;
|
|
|
- const formatWhole = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0}).format;
|
|
|
const tbody = document.querySelector('tbody')!;
|
|
|
tbody.innerHTML = '';
|
|
|
|
|
|
@@ -43,13 +44,13 @@ async function render() {
|
|
|
if (expertiseSelect.value !== '' && p.expertise !== expertiseSelect.value)
|
|
|
continue;
|
|
|
const tr = document.createElement('tr');
|
|
|
- const profit_per_area = p.profit_per_day / p.area;
|
|
|
- const break_even = p.profit_per_day > 0 ? p.capex / p.profit_per_day : Infinity;
|
|
|
+ const profitPerArea = p.profit_per_day / p.area;
|
|
|
+ const breakEven = p.profit_per_day > 0 ? p.capex / p.profit_per_day : Infinity;
|
|
|
tr.innerHTML = `
|
|
|
- <td>${p.output.join(', ')}</td>
|
|
|
+ <td>${p.outputs.map(o => o.ticker).join(', ')}</td>
|
|
|
<td>${expertise[p.expertise]}</td>
|
|
|
- <td style="color: ${color(profit_per_area, 0, 250)}">${formatDecimal(profit_per_area)}</td>
|
|
|
- <td><span style="color: ${color(break_even, 30, 3)}">${formatDecimal(break_even)}</span>d</td>
|
|
|
+ <td style="color: ${color(profitPerArea, 0, 250)}">${formatDecimal(profitPerArea)}</td>
|
|
|
+ <td><span style="color: ${color(breakEven, 30, 3)}">${formatDecimal(breakEven)}</span>d</td>
|
|
|
<td style="color: ${color(p.capex, 300_000, 40_000)}">${formatWhole(p.capex)}</td>
|
|
|
<td style="color: ${color(p.cost_per_day, 40_000, 1_000)}">${formatWhole(p.cost_per_day)}</td>
|
|
|
<td style="color: ${color(p.logistics_per_area, 2, 0.2)}">${formatDecimal(p.logistics_per_area)}</td>
|
|
|
@@ -58,8 +59,20 @@ async function render() {
|
|
|
<span style="color: ${color(volumeRatio, 0.05, 0.002)}">${formatWhole(p.average_traded_7d)}</span>
|
|
|
</td>
|
|
|
`;
|
|
|
+
|
|
|
const output = tr.querySelector('td')!;
|
|
|
output.dataset.tooltip = p.recipe;
|
|
|
+
|
|
|
+ const profitCell = tr.querySelectorAll('td')[2];
|
|
|
+ const revenue = p.outputs.reduce((sum, o) => sum + o.amount * o.vwap_7d, 0);
|
|
|
+ const inputCost = p.input_costs.reduce((sum, o) => sum + o.amount * o.vwap_7d, 0);
|
|
|
+ profitCell.dataset.tooltip = formatMatPrices(p.outputs) + '\n\n' +
|
|
|
+ formatMatPrices(p.input_costs) + '\n' +
|
|
|
+ 'worker consumables: ' + formatWhole(p.worker_consumable_cost_per_day) + '\n\n' +
|
|
|
+ `(${formatWhole(revenue)} - ${formatWhole(inputCost)}) × ${formatDecimal(p.runs_per_day)} runs ` +
|
|
|
+ `- ${formatWhole(p.worker_consumable_cost_per_day)} = ${formatDecimal(p.profit_per_day)}\n` +
|
|
|
+ `${formatDecimal(p.profit_per_day)} / ${formatWhole(p.area)} area = ${formatDecimal(profitPerArea)}`;
|
|
|
+
|
|
|
tbody.appendChild(tr);
|
|
|
}
|
|
|
document.getElementById('last-updated')!.textContent =
|
|
|
@@ -72,20 +85,34 @@ function color(n: number, low: number, high: number): string {
|
|
|
return `color-mix(in xyz, #0aa ${scale * 100}%, #f80)`;
|
|
|
}
|
|
|
|
|
|
+function formatMatPrices(matPrices: MatPrice[]): string {
|
|
|
+ return matPrices.map(({ticker, amount, vwap_7d}) =>
|
|
|
+ `${ticker}: ${amount} × ${formatDecimal(vwap_7d)} = ${formatWhole(amount * vwap_7d)}`).join('\n');
|
|
|
+}
|
|
|
+
|
|
|
setupPopover();
|
|
|
lowVolume.addEventListener('change', render);
|
|
|
expertiseSelect.addEventListener('change', render);
|
|
|
render();
|
|
|
|
|
|
interface Profit {
|
|
|
- output: string[]
|
|
|
+ outputs: MatPrice[]
|
|
|
recipe: string
|
|
|
expertise: keyof typeof expertise
|
|
|
profit_per_day: number
|
|
|
area: number
|
|
|
capex: number
|
|
|
cost_per_day: number
|
|
|
+ input_costs: MatPrice[]
|
|
|
+ worker_consumable_cost_per_day: number
|
|
|
+ runs_per_day: number
|
|
|
logistics_per_area: number
|
|
|
output_per_day: number
|
|
|
average_traded_7d: number
|
|
|
}
|
|
|
+
|
|
|
+interface MatPrice {
|
|
|
+ ticker: string
|
|
|
+ amount: number
|
|
|
+ vwap_7d: number
|
|
|
+}
|