|
@@ -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
|
|
|
|
|
+}
|