|
@@ -74,27 +74,27 @@ async function render() {
|
|
|
const controls = document.createElement('div');
|
|
const controls = document.createElement('div');
|
|
|
controls.style.marginBottom = '15px';
|
|
controls.style.marginBottom = '15px';
|
|
|
controls.innerHTML = `
|
|
controls.innerHTML = `
|
|
|
- <label style="margin-right: 15px;">CapEx Price:
|
|
|
|
|
|
|
+ <label style="margin-right: 15px;">CapEx Price:
|
|
|
<select id="capex-metric"><option value="vwap">VWAP</option><option value="bid">Bid</option><option value="ask">Ask</option></select>
|
|
<select id="capex-metric"><option value="vwap">VWAP</option><option value="bid">Bid</option><option value="ask">Ask</option></select>
|
|
|
</label>
|
|
</label>
|
|
|
- <label style="margin-right: 15px;">OpEx Price:
|
|
|
|
|
|
|
+ <label style="margin-right: 15px;">OpEx Price:
|
|
|
<select id="opex-metric"><option value="vwap">VWAP</option><option value="bid">Bid</option><option value="ask">Ask</option></select>
|
|
<select id="opex-metric"><option value="vwap">VWAP</option><option value="bid">Bid</option><option value="ask">Ask</option></select>
|
|
|
</label>
|
|
</label>
|
|
|
- <label style="margin-right: 15px;">Revenue Price:
|
|
|
|
|
|
|
+ <label style="margin-right: 15px;">Revenue Price:
|
|
|
<select id="revenue-metric"><option value="vwap">VWAP</option><option value="bid">Bid</option><option value="ask">Ask</option></select>
|
|
<select id="revenue-metric"><option value="vwap">VWAP</option><option value="bid">Bid</option><option value="ask">Ask</option></select>
|
|
|
</label>
|
|
</label>
|
|
|
<label style="margin-right: 15px;">
|
|
<label style="margin-right: 15px;">
|
|
|
<input type="checkbox" id="show-negative"> Show Negative Profit
|
|
<input type="checkbox" id="show-negative"> Show Negative Profit
|
|
|
</label>
|
|
</label>
|
|
|
<label style="margin-right: 15px;">
|
|
<label style="margin-right: 15px;">
|
|
|
- Round Trip (hrs):
|
|
|
|
|
|
|
+ Round Trip (hrs):
|
|
|
<select id="round-trip">
|
|
<select id="round-trip">
|
|
|
<option value="omit">Omit From Calculation</option>
|
|
<option value="omit">Omit From Calculation</option>
|
|
|
${Array.from({length: 25}, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join('')}
|
|
${Array.from({length: 25}, (_, i) => `<option value="${i + 1}">${i + 1}</option>`).join('')}
|
|
|
</select>
|
|
</select>
|
|
|
</label>
|
|
</label>
|
|
|
<label style="margin-right: 15px;">
|
|
<label style="margin-right: 15px;">
|
|
|
- Days OpEx:
|
|
|
|
|
|
|
+ Days OpEx:
|
|
|
<select id="working-capital">
|
|
<select id="working-capital">
|
|
|
<option value="omit">Omit From Calculation</option>
|
|
<option value="omit">Omit From Calculation</option>
|
|
|
<option value="dynamic">Max for Shipment (dynamic)</option>
|
|
<option value="dynamic">Max for Shipment (dynamic)</option>
|
|
@@ -110,7 +110,7 @@ async function render() {
|
|
|
</select>
|
|
</select>
|
|
|
</label>
|
|
</label>
|
|
|
<label>
|
|
<label>
|
|
|
- Permit Number:
|
|
|
|
|
|
|
+ Permit Number:
|
|
|
<select id="target-permit">
|
|
<select id="target-permit">
|
|
|
<option value="omit">Omit From Calculation</option>
|
|
<option value="omit">Omit From Calculation</option>
|
|
|
${Array.from({length: 49}, (_, i) => `<option value="${i + 2}">${i + 2}</option>`).join('')}
|
|
${Array.from({length: 49}, (_, i) => `<option value="${i + 2}">${i + 2}</option>`).join('')}
|
|
@@ -162,7 +162,7 @@ async function render() {
|
|
|
if (!headersInitialized) {
|
|
if (!headersInitialized) {
|
|
|
const ths = document.querySelectorAll('th');
|
|
const ths = document.querySelectorAll('th');
|
|
|
const keys: (keyof ProfitWithMetrics | 'outputs')[] = [
|
|
const keys: (keyof ProfitWithMetrics | 'outputs')[] = [
|
|
|
- 'outputs', 'expertise', 'profit_per_base', 'break_even',
|
|
|
|
|
|
|
+ 'outputs', 'expertise', 'profit_per_base', 'break_even',
|
|
|
'capex_val', 'opex_val', 'normalized_logistics_per_base', 'market_capacity_base'
|
|
'capex_val', 'opex_val', 'normalized_logistics_per_base', 'market_capacity_base'
|
|
|
];
|
|
];
|
|
|
|
|
|
|
@@ -171,7 +171,7 @@ async function render() {
|
|
|
ths.forEach((th, i) => {
|
|
ths.forEach((th, i) => {
|
|
|
if (keys[i]) {
|
|
if (keys[i]) {
|
|
|
th.style.cursor = 'pointer';
|
|
th.style.cursor = 'pointer';
|
|
|
- th.title = '';
|
|
|
|
|
|
|
+ th.title = '';
|
|
|
|
|
|
|
|
if (keys[i] === 'profit_per_base') {
|
|
if (keys[i] === 'profit_per_base') {
|
|
|
th.textContent = 'Profit/Base';
|
|
th.textContent = 'Profit/Base';
|
|
@@ -230,11 +230,7 @@ async function render() {
|
|
|
if (!buildingFound)
|
|
if (!buildingFound)
|
|
|
selectedBuilding = '';
|
|
selectedBuilding = '';
|
|
|
|
|
|
|
|
- savedBuilding = '';
|
|
|
|
|
-
|
|
|
|
|
- const filteredProfits = profits.filter(p => {
|
|
|
|
|
-
|
|
|
|
|
- savedBuilding = '';
|
|
|
|
|
|
|
+ savedBuilding = '';
|
|
|
|
|
|
|
|
const filteredProfits = profits.filter(p => {
|
|
const filteredProfits = profits.filter(p => {
|
|
|
const volumeRatio = p.output_per_day / p.average_traded_7d;
|
|
const volumeRatio = p.output_per_day / p.average_traded_7d;
|
|
@@ -254,8 +250,8 @@ async function render() {
|
|
|
let activeWorkingCapitalDays = 0;
|
|
let activeWorkingCapitalDays = 0;
|
|
|
if (workingCapitalOption !== 'omit') {
|
|
if (workingCapitalOption !== 'omit') {
|
|
|
if (workingCapitalOption === 'dynamic') {
|
|
if (workingCapitalOption === 'dynamic') {
|
|
|
- activeWorkingCapitalDays = p.normalized_logistics_per_base > 0
|
|
|
|
|
- ? 1 / p.normalized_logistics_per_base
|
|
|
|
|
|
|
+ activeWorkingCapitalDays = p.normalized_logistics_per_base > 0
|
|
|
|
|
+ ? 1 / p.normalized_logistics_per_base
|
|
|
: 0;
|
|
: 0;
|
|
|
} else {
|
|
} else {
|
|
|
activeWorkingCapitalDays = parseInt(workingCapitalOption, 10);
|
|
activeWorkingCapitalDays = parseInt(workingCapitalOption, 10);
|
|
@@ -292,13 +288,13 @@ async function render() {
|
|
|
// yields the total cash flow value of all available trades in the global FIO market for this bottleneck.
|
|
// yields the total cash flow value of all available trades in the global FIO market for this bottleneck.
|
|
|
const market_cash_flow = revenue_val * p.market_capacity_base;
|
|
const market_cash_flow = revenue_val * p.market_capacity_base;
|
|
|
|
|
|
|
|
- return {
|
|
|
|
|
- ...p,
|
|
|
|
|
- capex_val,
|
|
|
|
|
- opex_val,
|
|
|
|
|
- revenue_val,
|
|
|
|
|
- profit_per_base,
|
|
|
|
|
- break_even,
|
|
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...p,
|
|
|
|
|
+ capex_val,
|
|
|
|
|
+ opex_val,
|
|
|
|
|
+ revenue_val,
|
|
|
|
|
+ profit_per_base,
|
|
|
|
|
+ break_even,
|
|
|
hq_capex,
|
|
hq_capex,
|
|
|
activeWorkingCapitalDays,
|
|
activeWorkingCapitalDays,
|
|
|
activeShipCapex,
|
|
activeShipCapex,
|
|
@@ -383,30 +379,6 @@ async function render() {
|
|
|
capexCell.dataset.tooltip += `\nShip CapEx: ${formatSigFig(p.activeShipCapex)} (${formatSigFig(p.shipsNeeded)} ships)`;
|
|
capexCell.dataset.tooltip += `\nShip CapEx: ${formatSigFig(p.activeShipCapex)} (${formatSigFig(p.shipsNeeded)} ships)`;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const marketCell = tr.querySelectorAll('td')[7];
|
|
|
|
|
- marketCell.dataset.tooltip = `Market Capacity: ${formatSigFig(p.average_traded_7d)} traded/day ÷ ${formatSigFig(p.output_per_day / (p.area / 500))} produced/day/base = ${formatSigFig(p.market_capacity_base)} equivalent bases`;
|
|
|
|
|
- const runs_per_base = p.runs_per_day / (p.area / 500);
|
|
|
|
|
-
|
|
|
|
|
- profitCell.dataset.tooltip = formatMatPrices(p.outputs, revenueMetric, runs_per_base) + '\n\n' +
|
|
|
|
|
- formatMatPrices(p.input_costs, opexMetric, runs_per_base) + '\n' +
|
|
|
|
|
- '+ worker consumables\n\n' +
|
|
|
|
|
- `(${formatSigFig(p.revenue_val)} - ${formatSigFig(p.opex_val)}) = ${formatSigFig(p.profit_per_base)}`;
|
|
|
|
|
-
|
|
|
|
|
- const capexCell = tr.querySelectorAll('td')[4];
|
|
|
|
|
- capexCell.dataset.tooltip = `Base Construction: ${formatSigFig(p.capex[capexMetric] / (p.area / 500))}`;
|
|
|
|
|
-
|
|
|
|
|
- if (workingCapitalOption !== 'omit') {
|
|
|
|
|
- capexCell.dataset.tooltip += `\nWorking Capital (${formatSigFig(p.activeWorkingCapitalDays)} days): ${formatSigFig(p.opex_val * p.activeWorkingCapitalDays)}`;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (targetPermitOption !== 'omit' && p.hq_capex > 0) {
|
|
|
|
|
- capexCell.dataset.tooltip += `\nHQ Upgrade (Permit ${targetPermitOption}): ${formatSigFig(p.hq_capex)}`;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (roundTripOption !== 'omit') {
|
|
|
|
|
- capexCell.dataset.tooltip += `\nShip CapEx: ${formatSigFig(p.activeShipCapex)} (${formatSigFig(p.shipsNeeded)} ships)`;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
const marketCell = tr.querySelectorAll('td')[7];
|
|
const marketCell = tr.querySelectorAll('td')[7];
|
|
|
marketCell.dataset.tooltip = `Market Capacity: ${formatSigFig(p.average_traded_7d)} traded/day ÷ ${formatSigFig(p.output_per_day / (p.area / 500))} produced/day/base = ${formatSigFig(p.market_capacity_base)} equivalent bases`;
|
|
marketCell.dataset.tooltip = `Market Capacity: ${formatSigFig(p.average_traded_7d)} traded/day ÷ ${formatSigFig(p.output_per_day / (p.area / 500))} produced/day/base = ${formatSigFig(p.market_capacity_base)} equivalent bases`;
|
|
|
|
|
|
|
@@ -419,7 +391,7 @@ async function render() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// EXTREME DETAIL: Overhauled function to calculate both Absolute and Relative Volume-Weighted Percentiles.
|
|
// EXTREME DETAIL: Overhauled function to calculate both Absolute and Relative Volume-Weighted Percentiles.
|
|
|
-// To ensure the mathematical max bounds cleanly hit 100.0%, the denominator is extracted dynamically
|
|
|
|
|
|
|
+// To ensure the mathematical max bounds cleanly hit 100.0%, the denominator is extracted dynamically
|
|
|
// relative to the highest data point present in the array.
|
|
// relative to the highest data point present in the array.
|
|
|
function getPercentiles(val: number, sortedArr: {val: number, weight: number}[], invert: boolean = false): {abs: string, rel: string} {
|
|
function getPercentiles(val: number, sortedArr: {val: number, weight: number}[], invert: boolean = false): {abs: string, rel: string} {
|
|
|
if (sortedArr.length < 2) return {abs: "100.0%", rel: "100.0%"};
|
|
if (sortedArr.length < 2) return {abs: "100.0%", rel: "100.0%"};
|
|
@@ -432,7 +404,7 @@ function getPercentiles(val: number, sortedArr: {val: number, weight: number}[],
|
|
|
lessCount++;
|
|
lessCount++;
|
|
|
lessWeight += sortedArr[i].weight;
|
|
lessWeight += sortedArr[i].weight;
|
|
|
} else {
|
|
} else {
|
|
|
- break;
|
|
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -484,12 +456,6 @@ function color(n: number, low: number, high: number): string {
|
|
|
return `color-mix(in xyz, #0aa ${scale * 100}%, #f80)`;
|
|
return `color-mix(in xyz, #0aa ${scale * 100}%, #f80)`;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function formatMatPrices(matPrices: MatPrice[], metric: MetricType, runs_per_day: number): string {
|
|
|
|
|
- return matPrices.map(({ticker, amount, vwap_7d, bid, ask}) => {
|
|
|
|
|
- const val = metric === 'vwap' ? vwap_7d : metric === 'bid' ? (bid ?? vwap_7d) : (ask ?? vwap_7d);
|
|
|
|
|
- const daily_amount = amount * runs_per_day;
|
|
|
|
|
- return `${ticker}: ${formatSigFig(daily_amount)} × ${formatSigFig(val)} = ${formatSigFig(daily_amount * val)}`;
|
|
|
|
|
- }).join('\n');
|
|
|
|
|
function formatMatPrices(matPrices: MatPrice[], metric: MetricType, runs_per_day: number): string {
|
|
function formatMatPrices(matPrices: MatPrice[], metric: MetricType, runs_per_day: number): string {
|
|
|
return matPrices.map(({ticker, amount, vwap_7d, bid, ask}) => {
|
|
return matPrices.map(({ticker, amount, vwap_7d, bid, ask}) => {
|
|
|
const val = metric === 'vwap' ? vwap_7d : metric === 'bid' ? (bid ?? vwap_7d) : (ask ?? vwap_7d);
|
|
const val = metric === 'vwap' ? vwap_7d : metric === 'bid' ? (bid ?? vwap_7d) : (ask ?? vwap_7d);
|
|
@@ -511,12 +477,6 @@ interface Metrics {
|
|
|
ask: number;
|
|
ask: number;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-interface Metrics {
|
|
|
|
|
- vwap: number;
|
|
|
|
|
- bid: number;
|
|
|
|
|
- ask: number;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
interface Profit {
|
|
interface Profit {
|
|
|
outputs: MatPrice[]
|
|
outputs: MatPrice[]
|
|
|
recipe: string
|
|
recipe: string
|
|
@@ -531,9 +491,6 @@ interface Profit {
|
|
|
logistics_per_base: number
|
|
logistics_per_base: number
|
|
|
normalized_logistics_per_base: number
|
|
normalized_logistics_per_base: number
|
|
|
logistics_bottleneck: string
|
|
logistics_bottleneck: string
|
|
|
- logistics_per_base: number
|
|
|
|
|
- normalized_logistics_per_base: number
|
|
|
|
|
- logistics_bottleneck: string
|
|
|
|
|
output_per_day: number
|
|
output_per_day: number
|
|
|
average_traded_7d: number
|
|
average_traded_7d: number
|
|
|
market_capacity_base: number
|
|
market_capacity_base: number
|
|
@@ -560,8 +517,6 @@ interface MatPrice {
|
|
|
vwap_7d: number
|
|
vwap_7d: number
|
|
|
bid: number | null
|
|
bid: number | null
|
|
|
ask: number | null
|
|
ask: number | null
|
|
|
- bid: number | null
|
|
|
|
|
- ask: number | null
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
interface Building {
|
|
interface Building {
|