ledger.ts 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. const apiKey = document.querySelector('#api-key') as HTMLInputElement;
  2. {
  3. const storedApiKey = localStorage.getItem('fio-api-key');
  4. if (storedApiKey)
  5. apiKey.value = storedApiKey;
  6. }
  7. document.querySelector('#fetch')!.addEventListener('click', async () => {
  8. const loader = document.querySelector('#loader') as HTMLElement;
  9. loader.style.display = 'block';
  10. try {
  11. await renderLedger(apiKey.value);
  12. localStorage.setItem('fio-api-key', apiKey.value);
  13. } catch (e) {
  14. console.error(e);
  15. }
  16. loader.style.display = 'none';
  17. });
  18. async function getPrices(): Promise<Record<string, number | null>> {
  19. const allPrices: Price[] = await fetchJSON('https://refined-prun.github.io/refined-prices/all.json');
  20. const priceMap: Record<string, number | null> = {};
  21. for (const price of allPrices)
  22. if (price.ExchangeCode === 'IC1')
  23. priceMap[price.MaterialTicker] = price.VWAP30D;
  24. return priceMap;
  25. }
  26. const pricePromise = getPrices();
  27. const ledger = document.querySelector('textarea#ledger') as HTMLTextAreaElement;
  28. async function renderLedger(apiKey: string): Promise<void> {
  29. ledger.style.display = 'none';
  30. ledger.value = 'Time,Mat,Quantity,Actual Unit Price,Discounted Unit Price,Gateway,Debit,Credit,Contract ID\n';
  31. const prices = await pricePromise;
  32. const contracts: Contract[] = await fetchJSON('https://rest.fnar.net/contract/allcontracts',
  33. {headers: {'Authorization': apiKey}});
  34. contracts.sort((a, b) => a.DateEpochMs - b.DateEpochMs);
  35. for (const contract of contracts) {
  36. if (contract.PartnerCompanyCode === null || contract.PartnerName.endsWith(' Commodity Exchange')) continue; // NPC contract
  37. contract.Conditions.sort((a, b) => a.ConditionIndex - b.ConditionIndex);
  38. for (let i = 0; i < contract.Conditions.length; i++) {
  39. const condition = contract.Conditions[i];
  40. if ((contract.Party !== condition.Party && condition.Type === 'DELIVERY') || // counterparty is delivering
  41. (contract.Party === condition.Party && condition.Type === 'COMEX_PURCHASE_PICKUP')) { // we are picking up
  42. const time = new Date(contract.DateEpochMs).toISOString();
  43. const mat = condition.MaterialTicker;
  44. const quantity = condition.MaterialAmount;
  45. const totalPrice = contract.Conditions[i-1].Amount;
  46. ledger.value += `${time},${mat},${quantity},${prices[mat]},${totalPrice / quantity},,${totalPrice},,${contract.ContractLocalId}\n`;
  47. } else
  48. continue;
  49. }
  50. }
  51. ledger.style.display = 'block';
  52. }
  53. document.querySelector('#copy')!.addEventListener('click', () => {
  54. navigator.clipboard.writeText(ledger.value);
  55. });
  56. async function fetchJSON(url: string, options: RequestInit = {}): Promise<any> {
  57. const controller = new AbortController();
  58. const timeoutId = setTimeout(() => controller.abort(), 5000);
  59. const doc = await fetch(url, {...options, signal: controller.signal}).then((r) => r.json());
  60. clearTimeout(timeoutId);
  61. return doc;
  62. }
  63. interface Contract {
  64. Conditions: Array<ContractCondition>;
  65. ContractLocalId: string;
  66. Party: string;
  67. Status: string;
  68. PartnerName: string;
  69. PartnerCompanyCode: string;
  70. DateEpochMs: number;
  71. }
  72. interface ContractCondition {
  73. ConditionIndex: number;
  74. Type: string;
  75. Party: string;
  76. MaterialTicker: string;
  77. MaterialAmount: number;
  78. Amount: number;
  79. Currency: string;
  80. }
  81. interface Price {
  82. MaterialTicker: string
  83. ExchangeCode: string
  84. VWAP30D: number | null
  85. }