View on Exchange »
`;
} else {
CMFT_commoditySpecsDiv.innerHTML = "
Select a commodity to view its contract specifications.
";
}
}
function CMFT_formatCurrency(value) {
if (isNaN(parseFloat(value))) return "$0.00";
const absValue = Math.abs(parseFloat(value));
const formatted = `$${absValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
return parseFloat(value) < 0 ? `-${formatted}` : formatted;
}
function CMFT_addTrackedContractFields() {
if (CMFT_trackedContractCount >= CMFT_MAX_TRACKED_CONTRACTS) {
alert(`Maximum of ${CMFT_MAX_TRACKED_CONTRACTS} contracts can be tracked.`);
return;
}
CMFT_trackedContractCount++;
const contractId = CMFT_trackedContractCount;
const entryDiv = document.createElement('div');
entryDiv.classList.add('cmft-tracked-contract-entry');
entryDiv.id = `cmft-contract-entry-${contractId}`;
const today = new Date().toISOString().split('T')[0];
const commodityOptions = Object.keys(CMFT_COMMODITY_DATA).map(key => `
${key} (${CMFT_COMMODITY_DATA[key].symbol}) `).join('');
const selectedCommodityFromTab1 = CMFT_selectCommodity ? CMFT_selectCommodity.value : "";
entryDiv.innerHTML = `
Tracking Entry #${contractId} Remove
Price Change: N/A
Unrealized P&L/Contract: N/A
Total Unrealized P&L: N/A
`;
if (CMFT_trackedContractsContainer) CMFT_trackedContractsContainer.appendChild(entryDiv);
// Pre-select commodity if one was chosen in Tab 1 and this is the first added contract
const commodityNameSelect = document.getElementById(`cmft-commodityName-${contractId}`);
const commodityNameCustomInput = document.getElementById(`cmft-commodityNameCustom-${contractId}`);
if (commodityNameSelect && selectedCommodityFromTab1 && CMFT_COMMODITY_DATA[selectedCommodityFromTab1]) {
commodityNameSelect.value = selectedCommodityFromTab1;
if(commodityNameCustomInput) commodityNameCustomInput.style.display = 'none';
} else if (commodityNameSelect) {
commodityNameSelect.value = ""; // Ensure it's on "-- Select --"
if(commodityNameCustomInput) commodityNameCustomInput.style.display = 'block';
}
if(commodityNameSelect && commodityNameCustomInput) {
commodityNameSelect.addEventListener('change', function() {
commodityNameCustomInput.style.display = this.value === "" ? 'block' : 'none';
});
}
}
function CMFT_removeTrackedContract(contractId) {
const entryDiv = document.getElementById(`cmft-contract-entry-${contractId}`);
if (entryDiv && CMFT_trackedContractsContainer) {
CMFT_trackedContractsContainer.removeChild(entryDiv);
// Note: Does not decrement CMFT_trackedContractCount to keep IDs unique.
// If many are added/removed, this could hit limit sooner. Simpler for now.
}
CMFT_updatePortfolioSummary(); // Recalculate overall P&L
}
function CMFT_calculatePnLSingle(contractId, fromPortfolioUpdate = false) {
const commodityNameEl = document.getElementById(`cmft-commodityName-${contractId}`);
let commodityName = commodityNameEl ? commodityNameEl.value : "";
if (commodityName === "" && document.getElementById(`cmft-commodityNameCustom-${contractId}`)) { // Check custom input if select is empty
commodityName = document.getElementById(`cmft-commodityNameCustom-${contractId}`).value;
}
const entryPriceEl = document.getElementById(`cmft-entryPrice-${contractId}`);
const numContractsEl = document.getElementById(`cmft-numContracts-${contractId}`);
const currentPriceEl = document.getElementById(`cmft-currentPrice-${contractId}`);
const pnlDetailsDiv = document.getElementById(`cmft-pnlDetails-${contractId}`);
if (!commodityName || !entryPriceEl || !numContractsEl || !currentPriceEl || !pnlDetailsDiv) {
if(pnlDetailsDiv) pnlDetailsDiv.innerHTML = "
Error: Missing input fields for this entry.
";
return 0; // Return 0 PNL if error
}
const entryPrice = parseFloat(entryPriceEl.value);
const numContracts = parseInt(numContractsEl.value);
const currentPrice = parseFloat(currentPriceEl.value);
if (isNaN(entryPrice) || isNaN(numContracts) || isNaN(currentPrice) || numContracts === 0) {
pnlDetailsDiv.innerHTML = `
Price Change: N/A
Unrealized P&L/Contract: N/A
Total Unrealized P&L: N/A
`;
if (!fromPortfolioUpdate) CMFT_updatePortfolioSummary(); // Update summary even if this one is N/A
return 0;
}
const commodityInfo = CMFT_COMMODITY_DATA[commodityName]; // Use the selected/typed name
if (!commodityInfo) {
pnlDetailsDiv.innerHTML = `
Warning: Commodity '${commodityName}' not in database. P&L cannot be calculated accurately without tick size/value. Assuming 1 point = $1.
Total Unrealized P&L (Approx): ${CMFT_formatCurrency((currentPrice - entryPrice) * numContracts)}
`;
if (!fromPortfolioUpdate) CMFT_updatePortfolioSummary();
return (currentPrice - entryPrice) * numContracts; // Basic P&L if commodity unknown
}
const priceChange = currentPrice - entryPrice;
// For P&L calc, use tickValue and tickSize
// P&L = (Price Change / Tick Size) * Tick Value * Number of Contracts
// If short, price change is effectively (Entry - Current)
let pnlPerContract;
if (numContracts > 0) { // Long
pnlPerContract = (priceChange / commodityInfo.tickSize) * commodityInfo.tickValue;
} else { // Short
pnlPerContract = ((entryPrice - currentPrice) / commodityInfo.tickSize) * commodityInfo.tickValue;
}
const totalPnl = pnlPerContract * Math.abs(numContracts);
pnlDetailsDiv.innerHTML = `
Price Change: ${priceChange.toFixed(commodityName.startsWith("Corn") ? 2: (commodityInfo.tickSize.toString().split('.')[1] || '').length)}
Unrealized P&L/Contract: ${CMFT_formatCurrency(pnlPerContract)}
Total Unrealized P&L: ${CMFT_formatCurrency(totalPnl)}
`;
if (!fromPortfolioUpdate) CMFT_updatePortfolioSummary(); // Update overall P&L from single calc
return totalPnl;
}
function CMFT_updatePortfolioSummary() {
if (!CMFT_trackedContractsContainer || !CMFT_totalPortfolioPnlSpan || !CMFT_trackedContractsSummaryTableContainer) return;
let overallTotalPnl = 0;
const contractEntries = CMFT_trackedContractsContainer.querySelectorAll('.cmft-tracked-contract-entry');
let tableHTML = `
Commodity
Contract M/Y
Pos.
Entry
Current
Last Upd.
Total P&L
`;
if (contractEntries.length === 0) {
CMFT_trackedContractsSummaryTableContainer.innerHTML = "No contracts currently being tracked.
";
CMFT_totalPortfolioPnlSpan.textContent = CMFT_formatCurrency(0);
return;
}
contractEntries.forEach(entry => {
const contractId = entry.id.split('-').pop();
const currentPnlForThisContract = CMFT_calculatePnLSingle(contractId, true); // true to prevent recursion
overallTotalPnl += currentPnlForThisContract;
const commodityNameEl = document.getElementById(`cmft-commodityName-${contractId}`);
let commodityName = commodityNameEl ? commodityNameEl.value : "";
if (commodityName === "" && document.getElementById(`cmft-commodityNameCustom-${contractId}`)) {
commodityName = document.getElementById(`cmft-commodityNameCustom-${contractId}`).value || "Custom";
} else if (!commodityName) {
commodityName = "N/A";
}
const contractMY = document.getElementById(`cmft-contractMonthYear-${contractId}`)?.value || "N/A";
const numContracts = parseInt(document.getElementById(`cmft-numContracts-${contractId}`)?.value) || 0;
const entryPrice = parseFloat(document.getElementById(`cmft-entryPrice-${contractId}`)?.value) || 0;
const currentPrice = parseFloat(document.getElementById(`cmft-currentPrice-${contractId}`)?.value) || 0;
const lastUpdated = document.getElementById(`cmft-lastUpdated-${contractId}`)?.value || "N/A";
const pnlClass = currentPnlForThisContract >= 0 ? 'cmft-profit' : 'cmft-loss';
tableHTML += `
${commodityName}
${contractMY}
${numContracts}
${entryPrice.toFixed(2)}
${currentPrice.toFixed(2)}
${lastUpdated}
${CMFT_formatCurrency(currentPnlForThisContract)}
`;
});
tableHTML += `
`;
CMFT_trackedContractsSummaryTableContainer.innerHTML = tableHTML;
CMFT_totalPortfolioPnlSpan.className = overallTotalPnl >= 0 ? 'cmft-profit' : 'cmft-loss';
CMFT_totalPortfolioPnlSpan.textContent = CMFT_formatCurrency(overallTotalPnl);
}
function CMFT_updateAllPnLsAndNavigate(targetTabId) {
CMFT_updatePortfolioSummary(); // This will call CMFT_calculatePnLSingle for each
CMFT_navigateToTab(targetTabId);
}
async function CMFT_generatePDF() {
if (!CMFT_portfolioSummaryOutput || !CMFT_pdfSpecificTitle) {
console.error("CMFT: PDF export content area not found.");
alert("Error: Could not generate PDF. Content area missing.");
return;
}
// Ensure summary is up-to-date before PDF generation
CMFT_updatePortfolioSummary();
const portfolioContent = document.getElementById('cmft-portfolio-summary-output');
if (!portfolioContent || portfolioContent.innerHTML.includes("No contracts tracked yet")) {
// Do not generate PDF if no content or only default message
const contractEntries = CMFT_trackedContractsContainer ? CMFT_trackedContractsContainer.querySelectorAll('.cmft-tracked-contract-entry') : [];
if (contractEntries.length === 0) {
alert("Please add and track at least one contract to generate a report.");
return;
}
}
CMFT_pdfSpecificTitle.style.display = 'block';
const originalToolTitle = document.querySelector('.cmft-tool-title');
if (originalToolTitle) originalToolTitle.style.display = 'none';
const pdfContentArea = document.getElementById('cmft-pdfExportContent');
const originalBg = pdfContentArea.style.backgroundColor;
pdfContentArea.style.backgroundColor = '#ffffff';
try {
const canvas = await html2canvas(pdfContentArea, {
scale: 1.5, // Use 1.5 for better quality on potentially dense tables
useCORS: true,
backgroundColor: '#ffffff',
windowWidth: pdfContentArea.scrollWidth,
windowHeight: pdfContentArea.scrollHeight
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'p', // Portrait
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 position = 10; // Initial y position
if (newImgHeight > pdfHeight - 20) { // Content is taller than one page
const pageCanvas = document.createElement('canvas');
pageCanvas.width = canvas.width; // Use original canvas dimensions for slicing
const A4_PX_HEIGHT = (pdfHeight - 20) * (canvas.width / newImgWidth); // Effective pixel height of content area on one PDF page
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; // Important to set this for each slice
pageCtx.clearRect(0, 0, pageCanvas.width, pageCanvas.height); // Clear for new slice
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, position, pageFinalWidth, pageFinalHeight);
srcY += srcHeight;
}
} else { // Fits on one page
const x = (pdfWidth - newImgWidth) / 2;
pdf.addImage(imgData, 'PNG', x, position, newImgWidth, newImgHeight);
}
pdf.save('Commodities_Futures_Tracker_Summary.pdf');
} catch (error) {
console.error("Error generating PDF:", error);
alert("An error occurred while generating the PDF: " + error.message);
} finally {
CMFT_pdfSpecificTitle.style.display = 'none';
if (originalToolTitle) originalToolTitle.style.display = 'block';
pdfContentArea.style.backgroundColor = originalBg;
}
}
document.addEventListener('DOMContentLoaded', function() {
CMFT_initDOMElements();
if (!CMFT_tabs || !CMFT_tabContents) {
console.error("CMFT: Tool initialization failed. Essential tab elements missing.");
const container = document.getElementById('cmftToolContainer');
if(container) container.innerHTML = "
Error: Tool components failed to load.
";
return;
}
CMFT_tabs.forEach(tab => {
tab.addEventListener('click', function() {
if (this.dataset && this.dataset.tab) {
CMFT_navigateToTab(this.dataset.tab);
}
});
});
CMFT_displayCommoditySpecs(); // Initial call for default/empty state
CMFT_addTrackedContractFields(); // Add one entry by default
CMFT_updatePortfolioSummary(); // Initialize summary display
CMFT_navigateToTab('cmft-tab1');
});