ledger.ts 3.3 KB

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