Ver código fonte

ledger: output CSV for gateway bookkeeping

raylu 6 dias atrás
pai
commit
e42be993f6
5 arquivos alterados com 130 adições e 1 exclusões
  1. 1 0
      .gitignore
  2. 1 1
      package.json
  3. 96 0
      ts/ledger.ts
  4. 24 0
      www/ledger.html
  5. 8 0
      www/style.css

+ 1 - 0
.gitignore

@@ -4,6 +4,7 @@ config.toml
 node_modules/
 uv.lock
 www/buy.js*
+www/ledger.js*
 www/mat.js*
 www/roi.js*
 www/roi_*.json

+ 1 - 1
package.json

@@ -2,7 +2,7 @@
 	"name": "prun-calc",
 	"version": "0",
 	"scripts": {
-		"build": "bun build ts/buy.ts ts/mat.ts ts/roi.ts ts/shipbuilding.ts --outdir www --target browser --sourcemap=external",
+		"build": "bun build ts/buy.ts ts/ledger.ts ts/mat.ts ts/roi.ts ts/shipbuilding.ts --outdir www --target browser --sourcemap=external",
 		"typecheck": "tsgo --noEmit",
 		"serve": "python3 -m http.server -d www 8000"
 	},

+ 96 - 0
ts/ledger.ts

@@ -0,0 +1,96 @@
+const apiKey = document.querySelector('#api-key') as HTMLInputElement;
+{
+	const storedApiKey = localStorage.getItem('fio-api-key');
+	if (storedApiKey)
+		apiKey.value = storedApiKey;
+}
+document.querySelector('#fetch')!.addEventListener('click', async () => {
+	const loader = document.querySelector('#loader') as HTMLElement;
+	loader.innerHTML = '';
+	loader.style.display = 'block';
+	try {
+		await renderLedger(apiKey.value);
+		localStorage.setItem('fio-api-key', apiKey.value);
+	} catch (e) {
+		console.error(e);
+	}
+	loader.style.display = 'none';
+});
+
+async function getPrices(): Promise<Record<string, number | null>> {
+	const allPrices: Price[] = await fetchJSON('https://refined-prun.github.io/refined-prices/all.json');
+	const priceMap: Record<string, number | null> = {};
+	for (const price of allPrices)
+		if (price.ExchangeCode === 'IC1')
+			priceMap[price.MaterialTicker] = price.VWAP30D;
+console.log(priceMap);
+	return priceMap;
+}
+const pricePromise = getPrices();
+
+const ledger = document.querySelector('textarea#ledger') as HTMLTextAreaElement;
+async function renderLedger(apiKey: string): Promise<void> {
+	ledger.style.display = 'none';
+	ledger.value = 'Time,Mat,Quantity,Actual Unit Price,Discounted Unit Price,Gateway,Debit,Credit,Contract ID\n';
+
+	const prices = await pricePromise;
+	const contracts: Contract[] = await fetchJSON('https://rest.fnar.net/contract/allcontracts',
+			{headers: {'Authorization': apiKey}});
+	contracts.sort((a, b) => a.DateEpochMs - b.DateEpochMs);
+	for (const contract of contracts) {
+		if (contract.PartnerCompanyCode === null || contract.PartnerName.endsWith(' Commodity Exchange')) continue; // NPC contract
+		contract.Conditions.sort((a, b) => a.ConditionIndex - b.ConditionIndex);
+		for (let i = 0; i < contract.Conditions.length; i++) {
+			const condition = contract.Conditions[i];
+			if ((contract.Party !== condition.Party && condition.Type === 'DELIVERY') || // counterparty is delivering
+					(contract.Party === condition.Party && condition.Type === 'COMEX_PURCHASE_PICKUP')) { // we are picking up
+				const time = new Date(contract.DateEpochMs).toISOString();
+				const mat = condition.MaterialTicker;
+				const quantity = condition.MaterialAmount;
+				const totalPrice = contract.Conditions[i-1].Amount;
+				ledger.value += `${time},${mat},${quantity},${prices[mat]},${totalPrice / quantity},,${totalPrice},,${contract.ContractLocalId}\n`;
+			} else
+				continue;
+		}
+	}
+
+	ledger.style.display = 'block';
+}
+
+document.querySelector('#copy')!.addEventListener('click', () => {
+	navigator.clipboard.writeText(ledger.value);
+});
+
+async function fetchJSON(url: string, options: RequestInit = {}): Promise<any> {
+	const controller = new AbortController();
+	const timeoutId = setTimeout(() => controller.abort(), 5000);
+	const doc = await fetch(url, {...options, signal: controller.signal}).then((r) => r.json());
+	clearTimeout(timeoutId);
+	return doc;
+}
+
+interface Contract {
+	Conditions: Array<ContractCondition>;
+	ContractLocalId: string;
+	Party: string;
+	Status: string;
+	PartnerName: string;
+	PartnerCompanyCode: string;
+	DateEpochMs: number;
+}
+
+interface ContractCondition {
+	ConditionIndex: number;
+	Type: string;
+	Party: string;
+	MaterialTicker: string;
+	MaterialAmount: number;
+	Amount: number;
+	Currency: string;
+}
+
+interface Price {
+	MaterialTicker: string
+	ExchangeCode: string
+	VWAP30D: number | null
+}

+ 24 - 0
www/ledger.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="UTF-8">
+	<title>PrUn ledger</title>
+	<link rel="stylesheet" type="text/css" href="style.css">
+	<link rel="icon" href="https://www.raylu.net/hammer-man.svg" />
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<meta name="theme-color" content="#222">
+</head>
+<body>
+	<a href="/">← back</a>
+	<main class="ledger">
+		<form>
+			<label>FIO API key: <input type="password" size="30" id="api-key"></label>
+			<input type="button" value="fetch" id="fetch">
+		</form>
+		<textarea id="ledger"></textarea>
+		<section id="loader"></section>
+		<input type="button" id="copy" value="copy">
+	</main>
+	<script src="ledger.js"></script>
+</body>
+</html>

+ 8 - 0
www/style.css

@@ -149,6 +149,14 @@ main.mat {
 	}
 }
 
+main.ledger {
+	textarea#ledger {
+		margin-top: 2em;
+		width: 100%;
+		height: 70vh;
+	}
+}
+
 main.shipbuilding {
 	.analysis-node {
 		padding-left: 40px;