Total Unrealized P&L: N/A
`;
if (!fromPortfolioUpdate) SPT_updatePortfolioSummary();
return 0;
}
const priceChangePerUnit = currentPrice - entryPrice;
let totalPnl = 0;
if (unitType === "Futures Contracts") {
const spec = SPT_SILVER_SPEC_DATA; // Standard COMEX SI
// P&L = (Price Change / Tick Size) * Tick Value * Number of Contracts
// Handle short positions correctly (quantity < 0 means profit if price goes down)
if (quantity > 0) { // Long
totalPnl = (priceChangePerUnit / spec.tickSize) * spec.tickValue * quantity;
} else { // Short
totalPnl = ((entryPrice - currentPrice) / spec.tickSize) * spec.tickValue * Math.abs(quantity);
}
} else { // Troy Ounces, ETF Shares, Other - simple P&L
totalPnl = priceChangePerUnit * quantity;
}
pnlDetailsDiv.innerHTML = `
Price Change per Unit: ${SPT_formatCurrency(priceChangePerUnit)}
Total Unrealized P&L: ${SPT_formatCurrency(totalPnl)}
`;
if (!fromPortfolioUpdate) SPT_updatePortfolioSummary();
return totalPnl;
}
function SPT_updatePortfolioSummary() {
if (!SPT_trackedPositionsContainer || !SPT_totalPortfolioPnlSpan || !SPT_trackedPositionsSummaryTableContainer) return;
let overallTotalPnl = 0;
const positionEntries = SPT_trackedPositionsContainer.querySelectorAll('.spt-tracked-position-entry');
let tableHTML = `
| Description |
Entry Date |
Qty |
Unit |
Entry Price |
Current Price |
Total P&L |
`;
if (positionEntries.length === 0) {
SPT_trackedPositionsSummaryTableContainer.innerHTML = "No positions currently being tracked.
";
SPT_totalPortfolioPnlSpan.textContent = SPT_formatCurrency(0);
return;
}
positionEntries.forEach(entry => {
const positionId = entry.id.split('-').pop();
const currentPnlForThisPosition = SPT_calculatePnLSingle(positionId, true);
overallTotalPnl += currentPnlForThisPosition;
const desc = document.getElementById(`spt-positionDesc-${positionId}`)?.value || "N/A";
const entryDate = document.getElementById(`spt-entryDate-${positionId}`)?.value || "N/A";
const quantity = parseFloat(document.getElementById(`spt-quantity-${positionId}`)?.value) || 0;
const unitType = document.getElementById(`spt-unitType-${positionId}`)?.value || "N/A";
const entryPrice = parseFloat(document.getElementById(`spt-entryPrice-${positionId}`)?.value) || 0;
const currentPrice = parseFloat(document.getElementById(`spt-currentPrice-${positionId}`)?.value) || 0;
const pnlClass = currentPnlForThisPosition >= 0 ? 'spt-profit' : 'spt-loss';
tableHTML += `
| ${desc} |
${entryDate} |
${quantity} |
${unitType} |
${SPT_formatCurrency(entryPrice)} |
${SPT_formatCurrency(currentPrice)} |
${SPT_formatCurrency(currentPnlForThisPosition)} |
`;
});
tableHTML += `
`;
SPT_trackedPositionsSummaryTableContainer.innerHTML = tableHTML;
SPT_totalPortfolioPnlSpan.className = overallTotalPnl >= 0 ? 'spt-profit' : 'spt-loss';
SPT_totalPortfolioPnlSpan.textContent = SPT_formatCurrency(overallTotalPnl);
}
function SPT_updateAllPnLsAndNavigate(targetTabId) {
SPT_updatePortfolioSummary();
SPT_navigateToTab(targetTabId);
}
async function SPT_generatePDF() {
if (!SPT_portfolioSummaryOutput || !SPT_pdfSpecificTitle) {
console.error("SPT: PDF export content area not found.");
alert("Error: Could not generate PDF. Content area missing.");
return;
}
SPT_updatePortfolioSummary(); // Ensure data is fresh
const positionEntries = SPT_trackedPositionsContainer ? SPT_trackedPositionsContainer.querySelectorAll('.spt-tracked-position-entry') : [];
if (positionEntries.length === 0) {
alert("Please add and track at least one position to generate a report.");
return;
}
SPT_pdfSpecificTitle.style.display = 'block';
const originalToolTitle = document.querySelector('.spt-tool-title');
if (originalToolTitle) originalToolTitle.style.display = 'none';
const pdfContentArea = document.getElementById('spt-pdfExportContent');
const originalBg = pdfContentArea.style.backgroundColor;
pdfContentArea.style.backgroundColor = '#ffffff'; // Ensure white for capture
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);
const pageImgRatio = pageImgProps.width / pageImgProps.height;
let pageFinalWidth = pdfWidth - 20;
let pageFinalHeight = pageFinalWidth / pageImgRatio;
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('Silver_Positions_Tracker_Summary.pdf');
} catch (error) {
console.error("Error generating PDF:", error);
alert("An error occurred while generating the PDF: " + error.message);
} finally {
SPT_pdfSpecificTitle.style.display = 'none';
if (originalToolTitle) originalToolTitle.style.display = 'block';
pdfContentArea.style.backgroundColor = originalBg;
}
}
document.addEventListener('DOMContentLoaded', function() {
SPT_initDOMElements();
if (!SPT_tabs || !SPT_tabContents) {
console.error("SPT: Tool initialization failed.");
const container = document.getElementById('sptToolContainer');
if(container) container.innerHTML = "
Error: Tool components failed to load.
";
return;
}
SPT_tabs.forEach(tab => {
tab.addEventListener('click', function() {
if (this.dataset && this.dataset.tab) {
SPT_navigateToTab(this.dataset.tab);
}
});
});
SPT_displaySilverSpecs();
SPT_addTrackedPositionFields();
SPT_updatePortfolioSummary();
SPT_navigateToTab('spt-tab1');
});