`;
if (NGPM_trackedPositionsContainer) NGPM_trackedPositionsContainer.appendChild(entryDiv);
if(positionId === 1 && document.getElementById(`ngpm-positionDesc-${positionId}`).value === "") {
document.getElementById(`ngpm-positionDesc-${positionId}`).value = "NYMEX NG Futures";
document.getElementById(`ngpm-quantity-${positionId}`).value = "1";
}
}
function NGPM_removeTrackedPosition(positionId) {
const entryDiv = document.getElementById(`ngpm-position-entry-${positionId}`);
if (entryDiv && NGPM_trackedPositionsContainer) {
NGPM_trackedPositionsContainer.removeChild(entryDiv);
}
NGPM_updatePortfolioSummary();
}
function NGPM_calculatePnLSingle(positionId, fromPortfolioUpdate = false) {
const unitTypeEl = document.getElementById(`ngpm-unitType-${positionId}`);
const quantityEl = document.getElementById(`ngpm-quantity-${positionId}`);
const entryPriceEl = document.getElementById(`ngpm-entryPrice-${positionId}`);
const currentPriceEl = document.getElementById(`ngpm-currentPrice-${positionId}`);
const pnlDetailsDiv = document.getElementById(`ngpm-pnlDetails-${positionId}`);
if (!unitTypeEl || !quantityEl || !entryPriceEl || !currentPriceEl || !pnlDetailsDiv) {
if(pnlDetailsDiv) pnlDetailsDiv.innerHTML = "
`;
NGPM_trackedPositionsSummaryTableContainer.innerHTML = tableHTML;
NGPM_totalPortfolioPnlSpan.className = overallTotalPnl >= 0 ? 'ngpm-profit' : 'ngpm-loss';
NGPM_totalPortfolioPnlSpan.textContent = NGPM_formatCurrency(overallTotalPnl);
}
function NGPM_updateAllPnLsAndNavigate(targetTabId) {
NGPM_updatePortfolioSummary();
NGPM_navigateToTab(targetTabId);
}
async function NGPM_generatePDF() {
if (!NGPM_portfolioSummaryOutput || !NGPM_pdfSpecificTitle) {
console.error("NGPM: PDF export content area not found.");
alert("Error: Could not generate PDF. Content area missing.");
return;
}
NGPM_updatePortfolioSummary();
const positionEntries = NGPM_trackedPositionsContainer ? NGPM_trackedPositionsContainer.querySelectorAll('.ngpm-tracked-position-entry') : [];
if (positionEntries.length === 0) {
alert("Please add and track at least one position to generate a report.");
return;
}
NGPM_pdfSpecificTitle.style.display = 'block';
const originalToolTitle = document.querySelector('.ngpm-tool-title');
if (originalToolTitle) originalToolTitle.style.display = 'none';
const pdfContentArea = document.getElementById('ngpm-pdfExportContent');
const originalBg = pdfContentArea.style.backgroundColor;
pdfContentArea.style.backgroundColor = '#ffffff';
try {
const canvas = await html2canvas(pdfContentArea, {
scale: 1.5,
useCORS: true,
backgroundColor: '#ffffff',
windowWidth: pdfContentArea.scrollWidth,
windowHeight: pdfContentArea.scrollHeight
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'p',
unit: 'mm',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData);
const imgRatio = imgProps.width / imgProps.height;
let newImgWidth = pdfWidth - 20;
let newImgHeight = newImgWidth / imgRatio;
let positionY = 10;
if (newImgHeight > pdfHeight - 20) {
const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width;
const A4_PX_HEIGHT = (pdfHeight - 20) * (canvas.width / newImgWidth);
pageCanvas.height = A4_PX_HEIGHT;
const pageCtx = pageCanvas.getContext('2d');
let srcY = 0;
while(srcY < canvas.height) {
const srcHeight = Math.min(A4_PX_HEIGHT, canvas.height - srcY);
pageCanvas.height = srcHeight;
pageCtx.clearRect(0, 0, pageCanvas.width, pageCanvas.height);
pageCtx.drawImage(canvas, 0, srcY, canvas.width, srcHeight, 0, 0, canvas.width, srcHeight);
const pageImgData = pageCanvas.toDataURL('image/png');
// const pageImgProps = pdf.getImageProperties(pageImgData); // Not strictly needed if dimensions are calculated
// const pageImgRatio = pageImgProps.width / pageImgProps.height;
let pageFinalWidth = pdfWidth - 20;
let pageFinalHeight = pageFinalWidth / (canvas.width / srcHeight); // Use original aspect for the slice
if (srcY > 0) pdf.addPage();
pdf.addImage(pageImgData, 'PNG', 10, positionY, pageFinalWidth, pageFinalHeight);
srcY += srcHeight;
}
} else {
const x = (pdfWidth - newImgWidth) / 2;
pdf.addImage(imgData, 'PNG', x, positionY, newImgWidth, newImgHeight);
}
pdf.save('Natural_Gas_Positions_Monitor_Summary.pdf');
} catch (error) {
console.error("Error generating PDF:", error);
alert("An error occurred while generating the PDF: " + error.message);
} finally {
NGPM_pdfSpecificTitle.style.display = 'none';
if (originalToolTitle) originalToolTitle.style.display = 'block';
pdfContentArea.style.backgroundColor = originalBg;
}
}
document.addEventListener('DOMContentLoaded', function() {
NGPM_initDOMElements();
if (!NGPM_tabs || !NGPM_tabContents) {
console.error("NGPM: Tool initialization failed.");
const container = document.getElementById('ngpmToolContainer');
if(container) container.innerHTML = "
Error: Missing input fields for this entry.
"; return 0; } const unitType = unitTypeEl.value; const quantity = parseFloat(quantityEl.value); const entryPrice = parseFloat(entryPriceEl.value); const currentPrice = parseFloat(currentPriceEl.value); if (isNaN(quantity) || isNaN(entryPrice) || isNaN(currentPrice) || quantity === 0) { pnlDetailsDiv.innerHTML = `Price Change per Unit: N/A
Total Unrealized P&L: N/A
`; if (!fromPortfolioUpdate) NGPM_updatePortfolioSummary(); return 0; } const priceChangePerUnit = currentPrice - entryPrice; let totalPnl = 0; if (unitType === "Futures Contracts") { const spec = NGPM_NATGAS_SPEC_DATA; if (quantity > 0) { // Long totalPnl = (priceChangePerUnit / spec.tickSize) * spec.tickValue * quantity; } else { // Short totalPnl = ((entryPrice - currentPrice) / spec.tickSize) * spec.tickValue * Math.abs(quantity); } } else { // MMBtu (custom), Other - simple P&L totalPnl = priceChangePerUnit * quantity; } // Determine number of decimal places for priceChange based on tickSize, if it's a future let priceChangeDecimals = 2; // Default if (unitType === "Futures Contracts") { const tickStr = NGPM_NATGAS_SPEC_DATA.tickSize.toString(); priceChangeDecimals = tickStr.includes('.') ? tickStr.split('.')[1].length : 0; } pnlDetailsDiv.innerHTML = `Price Change per Unit: ${NGPM_formatCurrency(priceChangePerUnit)}
Total Unrealized P&L: ${NGPM_formatCurrency(totalPnl)}
`; if (!fromPortfolioUpdate) NGPM_updatePortfolioSummary(); return totalPnl; } function NGPM_updatePortfolioSummary() { if (!NGPM_trackedPositionsContainer || !NGPM_totalPortfolioPnlSpan || !NGPM_trackedPositionsSummaryTableContainer) return; let overallTotalPnl = 0; const positionEntries = NGPM_trackedPositionsContainer.querySelectorAll('.ngpm-tracked-position-entry'); let tableHTML = `| Description | Entry Date | Qty | Unit | Entry Price | Current Price | Total P&L |
|---|---|---|---|---|---|---|
| ${desc} | ${entryDate} | ${quantity} | ${unitType} | ${entryPriceDisplay} | ${currentPriceDisplay} | ${NGPM_formatCurrency(currentPnlForThisPosition)} |
Error: Tool components failed to load.
"; return; } NGPM_tabs.forEach(tab => { tab.addEventListener('click', function() { if (this.dataset && this.dataset.tab) { NGPM_navigateToTab(this.dataset.tab); } }); }); NGPM_displayNatGasSpecs(); NGPM_addTrackedPositionFields(); NGPM_updatePortfolioSummary(); NGPM_navigateToTab('ngpm-tab1'); });