Skip to content

Commit 83c93ae

Browse files
authored
Merge pull request #52 from r-brown/feature/product-review-roadmap
Product Trust & Usability Update
2 parents a36a724 + cc58fa0 commit 83c93ae

22 files changed

Lines changed: 1911 additions & 214 deletions

index.html

Lines changed: 216 additions & 191 deletions
Large diffs are not rendered by default.

src/calculations/stats.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,27 @@ export function calculateAdvancedStats(this: StatsContext) {
404404
return sum + pl - adjustment;
405405
}, 0);
406406

407+
// Quote coverage over the positions feeding unrealizedPL. A position is
408+
// "marked" when its value uses a real price (live quote or user snapshot);
409+
// everything else is valued at raw cashflow — open short options count at
410+
// full credit — which the UI must disclose next to the headline number.
411+
const unrealizedTrades = [...openTrades, ...awaitingCoverageTrades];
412+
const markedTrades = unrealizedTrades.filter(trade => {
413+
const source = (trade as unknown as Record<string, unknown>).marketPriceSource;
414+
return source === 'live' || source === 'snapshot';
415+
});
416+
const unmarkedTickers = Array.from(new Set(
417+
unrealizedTrades
418+
.filter(trade => !markedTrades.includes(trade))
419+
.map(trade => String(trade.ticker ?? '').trim().toUpperCase())
420+
.filter(Boolean)
421+
)).sort();
422+
const unrealizedQuoteCoverage = {
423+
marked: markedTrades.length,
424+
total: unrealizedTrades.length,
425+
unmarkedTickers
426+
};
427+
407428
// Calculate average win and average loss
408429
const avgWin: DollarAmount = winningTrades.length > 0
409430
? winningTrades.reduce((sum, trade) => sum + realizedPLOf(trade), 0) / winningTrades.length
@@ -459,6 +480,7 @@ export function calculateAdvancedStats(this: StatsContext) {
459480
collateralByTicker,
460481
realizedPL,
461482
unrealizedPL,
483+
unrealizedQuoteCoverage,
462484
pendingPremium: parseFloat(pendingPremium.toFixed(2)),
463485
realizationAnomalies,
464486
avgWin,

src/core/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ interface AppConfigShape {
3838
readonly FINNHUB_CONFIG: string
3939
readonly FINNHUB_SECRET: string
4040
readonly GEMINI_MAX_TOKENS: string
41+
readonly ANNOUNCEMENT_DISMISSED: string
42+
readonly LAST_STRATEGY: string
4143
readonly LEGACY_KEYS: readonly string[]
4244
}
4345
SHARE_CARD: {
@@ -74,6 +76,8 @@ export const APP_CONFIG: AppConfigShape = Object.freeze({
7476
FINNHUB_CONFIG: 'GammaLedgerFinnhubConfig',
7577
FINNHUB_SECRET: 'GammaLedgerFinnhubSecret',
7678
GEMINI_MAX_TOKENS: 'GammaLedgerGeminiMaxTokens',
79+
ANNOUNCEMENT_DISMISSED: 'GammaLedgerAnnouncementDismissedId',
80+
LAST_STRATEGY: 'GammaLedgerLastStrategy',
7781
LEGACY_KEYS: Object.freeze([
7882
'GammaLedgerTrades',
7983
'GammaLedgerDatabase',

src/imports/controls.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ export function setupImportControls(this: any) {
6666
});
6767
}
6868

69+
const schwabButton = document.getElementById('import-schwab-btn');
70+
const schwabInput = document.getElementById('import-schwab-input') as HTMLInputElement | null;
71+
72+
if (schwabButton && schwabInput) {
73+
schwabButton.addEventListener('click', (event) => {
74+
event.preventDefault();
75+
schwabInput.value = '';
76+
schwabInput.click();
77+
});
78+
79+
schwabInput.addEventListener('change', (event) => {
80+
this.handleSchwabCsvFileSelection(event);
81+
});
82+
}
83+
6984
const mergeButton = document.getElementById('import-merge-btn');
7085
if (mergeButton) {
7186
mergeButton.addEventListener('click', (event) => {
@@ -131,13 +146,31 @@ export function setupImportControls(this: any) {
131146
ofxInput.dispatchEvent(new Event('change', { bubbles: true }));
132147
}
133148
} else if (name.endsWith('.csv')) {
134-
// Trigger the Robinhood CSV import path
135-
const dt = new DataTransfer();
136-
dt.items.add(file);
137-
if (robinhoodInput) {
138-
robinhoodInput.files = dt.files;
139-
robinhoodInput.dispatchEvent(new Event('change', { bubbles: true }));
140-
}
149+
// Sniff the CSV format and route to the right importer;
150+
// unknown layouts open the column mapper.
151+
file.text().then((text: string) => {
152+
const format = this.detectCsvFormat(text);
153+
const fileName = file.name || 'CSV import';
154+
if (format === 'robinhood') {
155+
return this.importRobinhoodCsvContent(text, { fileName });
156+
}
157+
if (format === 'schwab') {
158+
return this.importSchwabCsvContent(text, { fileName });
159+
}
160+
this.openCsvColumnMapper(text, { fileName });
161+
return undefined;
162+
}).catch((error: unknown) => {
163+
console.error('CSV import error:', error);
164+
const message = error instanceof Error ? error.message : 'Unknown error';
165+
this.showNotification(`Failed to import CSV: ${message}`, 'error');
166+
this.appendImportLog({
167+
type: 'error',
168+
message: `Failed to import ${file.name || 'CSV file'}: ${message}`,
169+
timestamp: new Date()
170+
});
171+
}).finally(() => {
172+
this.hideLoadingIndicator();
173+
});
141174
} else {
142175
this.showNotification('Unsupported file type. Please use OFX, QFX, or CSV files.', 'error');
143176
}

0 commit comments

Comments
 (0)