diff --git a/data/recommendations/performance_database.json b/data/recommendations/performance_database.json index 1565c9cc..fe63e7b4 100644 --- a/data/recommendations/performance_database.json +++ b/data/recommendations/performance_database.json @@ -1,7 +1,234 @@ { - "last_updated": "2026-02-10 08:41:33", - "total_recommendations": 170, + "last_updated": "2026-02-10 22:26:47", + "total_recommendations": 185, "recommendations_by_date": { + "2026-02-10": [ + { + "ticker": "GME", + "rank": 1, + "strategy_match": "momentum", + "final_score": 45, + "confidence": 10, + "reason": "GameStop leads the list with a high Quantitative Score of 45 and a robust ML Win Probability of 52.6%. The setup is primed for a squeeze with 16.1% short interest and significant recent insider buying, notably a $21 million purchase by CEO Ryan Cohen. Options sentiment is strongly bullish with a Put/Call ratio of 0.248, suggesting traders are positioning for upside. Technically, the stock is in an uptrend above its 200 SMA, and the convergence of retail momentum and insider conviction offers an asymmetric risk/reward profile.", + "entry_price": 24.889999389648438, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 24.81999969482422, + "return_pct": -0.28, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "AVR", + "rank": 2, + "strategy_match": "momentum", + "final_score": 40, + "confidence": 8, + "reason": "Anteris Technologies aligns with the high-win-rate 'Insider Buying' strategy, featuring $28.75 million in recent insider purchases. The stock carries a high Quantitative Score of 40 and shows highly unusual bullish options activity with a Put/Call ratio of just 0.007. Trading above its 50 SMA, the stock displays technical resilience despite recent volatility. The combination of heavy institutional accumulation and aggressive options betting supports a strong short-term bounce thesis.", + "entry_price": 5.829999923706055, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 5.789999961853027, + "return_pct": -0.69, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "PEGA", + "rank": 3, + "strategy_match": "momentum", + "final_score": 30, + "confidence": 9, + "reason": "Pegasystems is a compelling earnings play with an ML Win Probability of 48.4% and an exceptionally strong ADX trend reading of 67.1. With earnings scheduled for today, the stock's oversold Stochastic reading suggests potential for a sharp mean-reversion rally if results exceed depressed expectations. While the longer-term trend is down, the immediate catalyst and high win probability make this a viable tactical trade. Volatility is elevated, allowing for the targeted >5% return within the 7-day window.", + "entry_price": 42.525001525878906, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 43.029998779296875, + "return_pct": 1.19, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "BLKB", + "rank": 4, + "strategy_match": "momentum", + "final_score": 30, + "confidence": 9, + "reason": "Blackbaud presents a deep value/reversion opportunity heading into earnings today, backed by an ML Win Probability of 48.4%. The stock is significantly oversold with an RSI of 29.5, yet shows bullish divergence in On-Balance Volume, indicating underlying accumulation. Options volume heavily favors calls (P/C 0.446), suggesting market participants anticipate a post-earnings recovery. The trade targets a snapback from extreme oversold conditions triggered by the earnings event.", + "entry_price": 49.790000915527344, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 47.68000030517578, + "return_pct": -4.24, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "INMD", + "rank": 5, + "strategy_match": "momentum", + "final_score": 15, + "confidence": 8, + "reason": "InMode enters its earnings release today with a supportive technical structure, including a Golden Cross (50 SMA above 200 SMA) and a price holding above the 20 EMA. The ML model predicts a win with 47.2% probability, and the company maintains a healthy financial position with a Current Ratio of 9.75. Despite a recent pullback, the alignment of earnings volatility and a confirmed uptrend creates a favorable setup for a momentum breakout. Risk is managed by the stock's strong balance sheet.", + "entry_price": 15.300000190734863, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 14.619999885559082, + "return_pct": -4.44, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "TMC", + "rank": 6, + "strategy_match": "momentum", + "final_score": 35, + "confidence": 8, + "reason": "TMC is a high-volatility momentum play driven by retail sentiment and Reddit due diligence, supported by a 45.5% ML Win Probability. The stock has a high short interest of 10.7% and bullish options flow (P/C ratio 0.208), creating the conditions for a potential squeeze. Technicals show a bullish Stochastic crossover, and the high ATR of 12.3% ensures sufficient volatility to hit profit targets quickly. The catalyst is continued speculative interest and short covering.", + "entry_price": 6.460000038146973, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 6.400000095367432, + "return_pct": -0.93, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "DDOG", + "rank": 7, + "strategy_match": "volume_accumulation", + "final_score": 30, + "confidence": 8, + "reason": "Datadog is flagged for Volume Accumulation, a strategy with a historical 100% win rate, coinciding with its earnings release today. The ML Win Probability is solid at 43.6%, and the stock exhibits a bullish divergence in On-Balance Volume despite recent price weakness. Options sentiment is constructive with more call volume than puts. This setup suggests 'smart money' positioning ahead of the binary earnings event, offering a strong risk/reward for a reversal.", + "entry_price": 131.63499450683594, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 129.6699981689453, + "return_pct": -1.49, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "PATH", + "rank": 8, + "strategy_match": "momentum", + "final_score": 35, + "confidence": 7, + "reason": "UiPath is a prime short squeeze candidate with 15.6% short interest and highly unusual bullish options activity (Put/Call ratio of 0.111). The ML model predicts a 43.9% win probability, and Reddit due diligence is fueling retail interest. While the stock is in a downtrend, the rising On-Balance Volume indicates accumulation. The trade thesis relies on a rapid repricing event driven by options gamma exposure and short covering.", + "entry_price": 13.074999809265137, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 12.949999809265137, + "return_pct": -0.96, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "PMN", + "rank": 9, + "strategy_match": "momentum", + "final_score": 20, + "confidence": 7, + "reason": "ProMIS Neurosciences benefits from significant recent insider buying totaling over $11 million, a strong signal of internal confidence. The ML model assigns a 43.8% probability of a win, and the stock is trading above its 20 EMA, indicating short-term strength. With extremely high volatility (ATR >11%), the stock is capable of making the required >5% move rapidly. The investment case is built on following insider conviction in a high-beta biotech asset.", + "entry_price": 13.550000190734863, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 14.289999961853027, + "return_pct": 5.46, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "IGV", + "rank": 10, + "strategy_match": "momentum", + "final_score": 25, + "confidence": 7, + "reason": "IGV offers a diversified way to play the potential rebound in the software sector, backed by a 42.3% ML Win Probability. The ETF is currently oversold with a Stochastic reading below 20, often a precursor to a technical bounce. Options flows are bullish, and the strong trend strength (ADX 63.9) suggests the sector remains active. This trade captures the broader momentum recovery thesis with lower single-stock risk.", + "entry_price": 86.29499816894531, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 85.41000366210938, + "return_pct": -1.03, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "POET", + "rank": 11, + "strategy_match": "momentum", + "final_score": 35, + "confidence": 7, + "reason": "POET Technologies is a high-volatility momentum play (ATR >10%) with a 41.1% ML Win Probability. The stock is the subject of Reddit due diligence and shows a bullish options Put/Call ratio of 0.224. Despite a downtrend, a recent bullish stochastic crossover signals potential for a relief rally. The thesis rests on speculative retail interest and high volatility driving a short-term price spike.", + "entry_price": 6.114999771118164, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 5.840000152587891, + "return_pct": -4.5, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "ASTS", + "rank": 12, + "strategy_match": "momentum", + "final_score": 35, + "confidence": 7, + "reason": "AST SpaceMobile remains a favorite among momentum traders with 18.5% short interest and a strong long-term uptrend. The stock is trading well above major moving averages, and the ML model gives it a 40.5% chance of success. Options volume is call-heavy, supporting a bullish outlook. The trade targets a continuation of the volatility-driven uptrend, aided by potential short covering.", + "entry_price": 99.80999755859375, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 96.2699966430664, + "return_pct": -3.55, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "APH", + "rank": 13, + "strategy_match": "momentum", + "final_score": 20, + "confidence": 7, + "reason": "Amphenol represents a high-quality momentum play, confirmed by insider buying and a strong technical uptrend. The stock is trading above both its 50 and 200 SMAs and recently flashed a Golden Cross signal. With an ML Win Probability of 41.1%, it offers a favorable balance of probability and trend stability. The catalyst is the continued institutional support indicated by price action relative to VWAP.", + "entry_price": 145.18910217285156, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 144.13999938964844, + "return_pct": -0.72, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "META", + "rank": 14, + "strategy_match": "momentum", + "final_score": 10, + "confidence": 6, + "reason": "Meta Platforms is predicted to WIN by the ML model (39.6%) and maintains a solid technical uptrend, holding above its 20 EMA and VWAP. Despite some insider selling, the fundamental backdrop of revenue growth and profitability remains a tailwind. Options positioning is constructive, and the stock is not overbought. This trade is a bet on large-cap tech resilience and continued momentum.", + "entry_price": 671.3660278320312, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 670.719970703125, + "return_pct": -0.1, + "days_held": 0, + "last_updated": "2026-02-10" + }, + { + "ticker": "WRB", + "rank": 15, + "strategy_match": "momentum", + "final_score": 25, + "confidence": 6, + "reason": "W. R. Berkley makes the list primarily due to massive insider buying totaling over $308 million, aligning with a historically high-win-rate strategy. The stock is in a technical uptrend and trading above its 20 EMA. While the ML prediction is neutral, the sheer scale of insider conviction provides a strong floor and potential upside catalyst. The trade follows the 'smart money' signal in a stable insurance play.", + "entry_price": 69.02999877929688, + "discovery_date": "2026-02-10", + "status": "open", + "current_price": 69.91999816894531, + "return_pct": 1.29, + "days_held": 0, + "last_updated": "2026-02-10" + } + ], "2026-02-06": [ { "ticker": "GME", @@ -13,11 +240,11 @@ "entry_price": 24.93000030517578, "discovery_date": "2026-02-06", "status": "open", - "current_price": 24.924999237060547, - "return_pct": -0.02, + "current_price": 24.81999969482422, + "return_pct": -0.44, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": -0.02, + "return_1d": -0.44, "win_1d": false }, { @@ -30,11 +257,11 @@ "entry_price": 12.289999961853027, "discovery_date": "2026-02-06", "status": "open", - "current_price": 12.0, - "return_pct": -2.36, + "current_price": 10.5, + "return_pct": -14.56, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": -2.36, + "return_1d": -14.56, "win_1d": false }, { @@ -47,11 +274,11 @@ "entry_price": 394.7300109863281, "discovery_date": "2026-02-06", "status": "open", - "current_price": 420.3800048828125, - "return_pct": 6.5, + "current_price": 413.2699890136719, + "return_pct": 4.7, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 6.5, + "return_1d": 4.7, "win_1d": true }, { @@ -64,11 +291,11 @@ "entry_price": 410.4100036621094, "discovery_date": "2026-02-06", "status": "open", - "current_price": 423.2450866699219, - "return_pct": 3.13, + "current_price": 425.2099914550781, + "return_pct": 3.61, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 3.13, + "return_1d": 3.61, "win_1d": true }, { @@ -81,11 +308,11 @@ "entry_price": 279.0199890136719, "discovery_date": "2026-02-06", "status": "open", - "current_price": 273.3949890136719, - "return_pct": -2.02, + "current_price": 273.67999267578125, + "return_pct": -1.91, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": -2.02, + "return_1d": -1.91, "win_1d": false }, { @@ -98,11 +325,11 @@ "entry_price": 5.569900035858154, "discovery_date": "2026-02-06", "status": "open", - "current_price": 6.114999771118164, - "return_pct": 9.79, + "current_price": 5.840000152587891, + "return_pct": 4.85, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 9.79, + "return_1d": 4.85, "win_1d": true }, { @@ -115,11 +342,11 @@ "entry_price": 35.709999084472656, "discovery_date": "2026-02-06", "status": "open", - "current_price": 37.689998626708984, - "return_pct": 5.54, + "current_price": 40.45000076293945, + "return_pct": 13.27, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 5.54, + "return_1d": 13.27, "win_1d": true }, { @@ -132,11 +359,11 @@ "entry_price": 109.41999816894531, "discovery_date": "2026-02-06", "status": "open", - "current_price": 112.30000305175781, - "return_pct": 2.63, + "current_price": 112.27999877929688, + "return_pct": 2.61, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 2.63, + "return_1d": 2.61, "win_1d": true }, { @@ -149,11 +376,11 @@ "entry_price": 220.76499938964844, "discovery_date": "2026-02-06", "status": "open", - "current_price": 208.31500244140625, - "return_pct": -5.64, + "current_price": 206.5800018310547, + "return_pct": -6.43, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": -5.64, + "return_1d": -6.43, "win_1d": false }, { @@ -166,11 +393,11 @@ "entry_price": 271.44000244140625, "discovery_date": "2026-02-06", "status": "open", - "current_price": 276.0299987792969, - "return_pct": 1.69, + "current_price": 274.07000732421875, + "return_pct": 0.97, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 1.69, + "return_1d": 0.97, "win_1d": true }, { @@ -183,11 +410,11 @@ "entry_price": 185.42999267578125, "discovery_date": "2026-02-06", "status": "open", - "current_price": 181.48500061035156, - "return_pct": -2.13, + "current_price": 182.69000244140625, + "return_pct": -1.48, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": -2.13, + "return_1d": -1.48, "win_1d": false }, { @@ -200,11 +427,11 @@ "entry_price": 182.40069580078125, "discovery_date": "2026-02-06", "status": "open", - "current_price": 189.24000549316406, - "return_pct": 3.75, + "current_price": 188.5399932861328, + "return_pct": 3.37, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 3.75, + "return_1d": 3.37, "win_1d": true }, { @@ -217,12 +444,12 @@ "entry_price": 150.97999572753906, "discovery_date": "2026-02-06", "status": "open", - "current_price": 152.57000732421875, - "return_pct": 1.05, + "current_price": 148.94000244140625, + "return_pct": -1.35, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 1.05, - "win_1d": true + "return_1d": -1.35, + "win_1d": false }, { "ticker": "PATH", @@ -234,11 +461,11 @@ "entry_price": 12.345000267028809, "discovery_date": "2026-02-06", "status": "open", - "current_price": 13.055000305175781, - "return_pct": 5.75, + "current_price": 12.949999809265137, + "return_pct": 4.9, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": 5.75, + "return_1d": 4.9, "win_1d": true }, { @@ -251,11 +478,11 @@ "entry_price": 322.0899963378906, "discovery_date": "2026-02-06", "status": "open", - "current_price": 317.9800109863281, - "return_pct": -1.28, + "current_price": 318.5799865722656, + "return_pct": -1.09, "days_held": 4, "last_updated": "2026-02-10", - "return_1d": -1.28, + "return_1d": -1.09, "win_1d": false } ], @@ -270,13 +497,13 @@ "entry_price": 718.7100219726562, "discovery_date": "2026-01-30", "status": "open", - "current_price": 670.8400268554688, - "return_pct": -6.66, + "current_price": 670.719970703125, + "return_pct": -6.68, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": -6.66, + "return_1d": -6.68, "win_1d": false, - "return_7d": -6.66, + "return_7d": -6.68, "win_7d": false }, { @@ -289,13 +516,13 @@ "entry_price": 56.5, "discovery_date": "2026-01-30", "status": "open", - "current_price": 47.79499816894531, - "return_pct": -15.41, + "current_price": 47.54999923706055, + "return_pct": -15.84, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": -15.41, + "return_1d": -15.84, "win_1d": false, - "return_7d": -15.41, + "return_7d": -15.84, "win_7d": false }, { @@ -308,13 +535,13 @@ "entry_price": 22.950000762939453, "discovery_date": "2026-01-30", "status": "open", - "current_price": 22.770000457763672, - "return_pct": -0.78, + "current_price": 21.8799991607666, + "return_pct": -4.66, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": -0.78, + "return_1d": -4.66, "win_1d": false, - "return_7d": -0.78, + "return_7d": -4.66, "win_7d": false }, { @@ -327,13 +554,13 @@ "entry_price": 78.58000183105469, "discovery_date": "2026-01-30", "status": "open", - "current_price": 87.50350189208984, - "return_pct": 11.36, + "current_price": 86.29000091552734, + "return_pct": 9.81, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": 11.36, + "return_1d": 9.81, "win_1d": true, - "return_7d": 11.36, + "return_7d": 9.81, "win_7d": true }, { @@ -346,13 +573,13 @@ "entry_price": 37.064998626708984, "discovery_date": "2026-01-30", "status": "open", - "current_price": 41.689998626708984, - "return_pct": 12.48, + "current_price": 41.61000061035156, + "return_pct": 12.26, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": 12.48, + "return_1d": 12.26, "win_1d": true, - "return_7d": 12.48, + "return_7d": 12.26, "win_7d": true }, { @@ -365,13 +592,13 @@ "entry_price": 192.41000366210938, "discovery_date": "2026-01-30", "status": "open", - "current_price": 189.24000549316406, - "return_pct": -1.65, + "current_price": 188.5399932861328, + "return_pct": -2.01, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": -1.65, + "return_1d": -2.01, "win_1d": false, - "return_7d": -1.65, + "return_7d": -2.01, "win_7d": false }, { @@ -384,13 +611,13 @@ "entry_price": 5.989999771118164, "discovery_date": "2026-01-30", "status": "open", - "current_price": 7.074999809265137, - "return_pct": 18.11, + "current_price": 6.840000152587891, + "return_pct": 14.19, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": 18.11, + "return_1d": 14.19, "win_1d": true, - "return_7d": 18.11, + "return_7d": 14.19, "win_7d": true }, { @@ -403,13 +630,13 @@ "entry_price": 39.91999816894531, "discovery_date": "2026-01-30", "status": "open", - "current_price": 47.52000045776367, - "return_pct": 19.04, + "current_price": 47.4900016784668, + "return_pct": 18.96, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": 19.04, + "return_1d": 18.96, "win_1d": true, - "return_7d": 19.04, + "return_7d": 18.96, "win_7d": true }, { @@ -422,13 +649,13 @@ "entry_price": 248.30999755859375, "discovery_date": "2026-01-30", "status": "open", - "current_price": 262.01300048828125, - "return_pct": 5.52, + "current_price": 262.55999755859375, + "return_pct": 5.74, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": 5.52, + "return_1d": 5.74, "win_1d": true, - "return_7d": 5.52, + "return_7d": 5.74, "win_7d": true }, { @@ -441,13 +668,13 @@ "entry_price": 331.6199951171875, "discovery_date": "2026-01-30", "status": "open", - "current_price": 341.7950134277344, - "return_pct": 3.07, + "current_price": 340.44000244140625, + "return_pct": 2.66, "days_held": 11, "last_updated": "2026-02-10", - "return_1d": 3.07, + "return_1d": 2.66, "win_1d": true, - "return_7d": 3.07, + "return_7d": 2.66, "win_7d": true } ], @@ -462,13 +689,13 @@ "entry_price": 24.010000228881836, "discovery_date": "2026-01-26", "status": "open", - "current_price": 24.924999237060547, - "return_pct": 3.81, + "current_price": 24.81999969482422, + "return_pct": 3.37, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": 3.81, + "return_1d": 3.37, "win_1d": true, - "return_7d": 3.81, + "return_7d": 3.37, "win_7d": true }, { @@ -481,13 +708,13 @@ "entry_price": 56.290000915527344, "discovery_date": "2026-01-26", "status": "open", - "current_price": 59.064998626708984, - "return_pct": 4.93, + "current_price": 59.150001525878906, + "return_pct": 5.08, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": 4.93, + "return_1d": 5.08, "win_1d": true, - "return_7d": 4.93, + "return_7d": 5.08, "win_7d": true }, { @@ -500,13 +727,13 @@ "entry_price": 19.920000076293945, "discovery_date": "2026-01-26", "status": "open", - "current_price": 27.510000228881836, - "return_pct": 38.1, + "current_price": 27.329999923706055, + "return_pct": 37.2, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": 38.1, + "return_1d": 37.2, "win_1d": true, - "return_7d": 38.1, + "return_7d": 37.2, "win_7d": true }, { @@ -519,13 +746,13 @@ "entry_price": 36.18000030517578, "discovery_date": "2026-01-26", "status": "open", - "current_price": 37.709999084472656, - "return_pct": 4.23, + "current_price": 37.470001220703125, + "return_pct": 3.57, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": 4.23, + "return_1d": 3.57, "win_1d": true, - "return_7d": 4.23, + "return_7d": 3.57, "win_7d": true }, { @@ -538,13 +765,13 @@ "entry_price": 238.4199981689453, "discovery_date": "2026-01-26", "status": "open", - "current_price": 210.16000366210938, - "return_pct": -11.85, + "current_price": 206.9600067138672, + "return_pct": -13.2, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": -11.85, + "return_1d": -13.2, "win_1d": false, - "return_7d": -11.85, + "return_7d": -13.2, "win_7d": false }, { @@ -557,13 +784,13 @@ "entry_price": 136.63999938964844, "discovery_date": "2026-01-26", "status": "open", - "current_price": 132.35000610351562, - "return_pct": -3.14, + "current_price": 129.6699981689453, + "return_pct": -5.1, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": -3.14, + "return_1d": -5.1, "win_1d": false, - "return_7d": -3.14, + "return_7d": -5.1, "win_7d": false }, { @@ -576,13 +803,13 @@ "entry_price": 98.33999633789062, "discovery_date": "2026-01-26", "status": "open", - "current_price": 72.6050033569336, - "return_pct": -26.17, + "current_price": 73.41000366210938, + "return_pct": -25.35, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": -26.17, + "return_1d": -25.35, "win_1d": false, - "return_7d": -26.17, + "return_7d": -25.35, "win_7d": false }, { @@ -595,13 +822,13 @@ "entry_price": 186.47000122070312, "discovery_date": "2026-01-26", "status": "open", - "current_price": 189.24000549316406, - "return_pct": 1.49, + "current_price": 188.5399932861328, + "return_pct": 1.11, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": 1.49, + "return_1d": 1.11, "win_1d": true, - "return_7d": 1.49, + "return_7d": 1.11, "win_7d": true }, { @@ -614,13 +841,13 @@ "entry_price": 236.7100067138672, "discovery_date": "2026-01-26", "status": "open", - "current_price": 219.8699951171875, - "return_pct": -7.11, + "current_price": 219.75, + "return_pct": -7.16, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": -7.11, + "return_1d": -7.16, "win_1d": false, - "return_7d": -7.11, + "return_7d": -7.16, "win_7d": false }, { @@ -633,13 +860,13 @@ "entry_price": 173.32000732421875, "discovery_date": "2026-01-26", "status": "open", - "current_price": 199.63499450683594, - "return_pct": 15.18, + "current_price": 201.1199951171875, + "return_pct": 16.04, "days_held": 15, "last_updated": "2026-02-10", - "return_1d": 15.18, + "return_1d": 16.04, "win_1d": true, - "return_7d": 15.18, + "return_7d": 16.04, "win_7d": true } ], @@ -654,13 +881,13 @@ "entry_price": 1.7899999618530273, "discovery_date": "2026-02-01", "status": "open", - "current_price": 1.6100000143051147, - "return_pct": -10.06, + "current_price": 1.559999942779541, + "return_pct": -12.85, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": -10.06, + "return_1d": -12.85, "win_1d": false, - "return_7d": -10.06, + "return_7d": -12.85, "win_7d": false }, { @@ -673,13 +900,13 @@ "entry_price": 200.92999267578125, "discovery_date": "2026-02-01", "status": "open", - "current_price": 194.3249969482422, - "return_pct": -3.29, + "current_price": 195.19000244140625, + "return_pct": -2.86, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": -3.29, + "return_1d": -2.86, "win_1d": false, - "return_7d": -3.29, + "return_7d": -2.86, "win_7d": false }, { @@ -692,13 +919,13 @@ "entry_price": 306.70001220703125, "discovery_date": "2026-02-01", "status": "open", - "current_price": 294.239990234375, - "return_pct": -4.06, + "current_price": 291.760009765625, + "return_pct": -4.87, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": -4.06, + "return_1d": -4.87, "win_1d": false, - "return_7d": -4.06, + "return_7d": -4.87, "win_7d": false }, { @@ -711,13 +938,13 @@ "entry_price": 23.8799991607666, "discovery_date": "2026-02-01", "status": "open", - "current_price": 24.924999237060547, - "return_pct": 4.38, + "current_price": 24.81999969482422, + "return_pct": 3.94, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": 4.38, + "return_1d": 3.94, "win_1d": true, - "return_7d": 4.38, + "return_7d": 3.94, "win_7d": true }, { @@ -730,13 +957,13 @@ "entry_price": 46.470001220703125, "discovery_date": "2026-02-01", "status": "open", - "current_price": 47.6349983215332, - "return_pct": 2.51, + "current_price": 47.130001068115234, + "return_pct": 1.42, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": 2.51, + "return_1d": 1.42, "win_1d": true, - "return_7d": 2.51, + "return_7d": 1.42, "win_7d": true }, { @@ -749,13 +976,13 @@ "entry_price": 29.110000610351562, "discovery_date": "2026-02-01", "status": "open", - "current_price": 34.08000183105469, - "return_pct": 17.07, + "current_price": 33.33000183105469, + "return_pct": 14.5, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": 17.07, + "return_1d": 14.5, "win_1d": true, - "return_7d": 17.07, + "return_7d": 14.5, "win_7d": true }, { @@ -768,13 +995,13 @@ "entry_price": 146.58999633789062, "discovery_date": "2026-02-01", "status": "open", - "current_price": 141.00010681152344, - "return_pct": -3.81, + "current_price": 139.50999450683594, + "return_pct": -4.83, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": -3.81, + "return_1d": -4.83, "win_1d": false, - "return_7d": -3.81, + "return_7d": -4.83, "win_7d": false }, { @@ -787,13 +1014,13 @@ "entry_price": 250.22999572753906, "discovery_date": "2026-02-01", "status": "open", - "current_price": 262.01300048828125, - "return_pct": 4.71, + "current_price": 262.55999755859375, + "return_pct": 4.93, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": 4.71, + "return_1d": 4.93, "win_1d": true, - "return_7d": 4.71, + "return_7d": 4.93, "win_7d": true }, { @@ -806,13 +1033,13 @@ "entry_price": 1.0399999618530273, "discovery_date": "2026-02-01", "status": "open", - "current_price": 0.7613999843597412, - "return_pct": -26.79, + "current_price": 0.7020000219345093, + "return_pct": -32.5, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": -26.79, + "return_1d": -32.5, "win_1d": false, - "return_7d": -26.79, + "return_7d": -32.5, "win_7d": false }, { @@ -825,13 +1052,13 @@ "entry_price": 48.02000045776367, "discovery_date": "2026-02-01", "status": "open", - "current_price": 57.36000061035156, - "return_pct": 19.45, + "current_price": 57.47999954223633, + "return_pct": 19.7, "days_held": 9, "last_updated": "2026-02-10", - "return_1d": 19.45, + "return_1d": 19.7, "win_1d": true, - "return_7d": 19.45, + "return_7d": 19.7, "win_7d": true } ], @@ -846,13 +1073,13 @@ "entry_price": 672.969970703125, "discovery_date": "2026-01-27", "status": "open", - "current_price": 670.8400268554688, - "return_pct": -0.32, + "current_price": 670.719970703125, + "return_pct": -0.33, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": -0.32, + "return_1d": -0.33, "win_1d": false, - "return_7d": -0.32, + "return_7d": -0.33, "win_7d": false }, { @@ -865,13 +1092,13 @@ "entry_price": 109.73999786376953, "discovery_date": "2026-01-27", "status": "open", - "current_price": 129.94000244140625, - "return_pct": 18.41, + "current_price": 128.10000610351562, + "return_pct": 16.73, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": 18.41, + "return_1d": 16.73, "win_1d": true, - "return_7d": 18.41, + "return_7d": 16.73, "win_7d": true }, { @@ -884,13 +1111,13 @@ "entry_price": 101.58999633789062, "discovery_date": "2026-01-27", "status": "open", - "current_price": 72.6050033569336, - "return_pct": -28.53, + "current_price": 73.41000366210938, + "return_pct": -27.74, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": -28.53, + "return_1d": -27.74, "win_1d": false, - "return_7d": -28.53, + "return_7d": -27.74, "win_7d": false }, { @@ -903,13 +1130,13 @@ "entry_price": 270.42999267578125, "discovery_date": "2026-01-27", "status": "open", - "current_price": 280.6600036621094, - "return_pct": 3.78, + "current_price": 282.3800048828125, + "return_pct": 4.42, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": 3.78, + "return_1d": 4.42, "win_1d": true, - "return_7d": 3.78, + "return_7d": 4.42, "win_7d": true }, { @@ -922,13 +1149,13 @@ "entry_price": 1.5199999809265137, "discovery_date": "2026-01-27", "status": "open", - "current_price": 1.8115999698638916, - "return_pct": 19.18, + "current_price": 1.7699999809265137, + "return_pct": 16.45, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": 19.18, + "return_1d": 16.45, "win_1d": true, - "return_7d": 19.18, + "return_7d": 16.45, "win_7d": true }, { @@ -941,13 +1168,13 @@ "entry_price": 196.6300048828125, "discovery_date": "2026-01-27", "status": "open", - "current_price": 220.9499969482422, - "return_pct": 12.37, + "current_price": 220.9199981689453, + "return_pct": 12.35, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": 12.37, + "return_1d": 12.35, "win_1d": true, - "return_7d": 12.37, + "return_7d": 12.35, "win_7d": true }, { @@ -960,13 +1187,13 @@ "entry_price": 1616.3299560546875, "discovery_date": "2026-01-27", "status": "open", - "current_price": 1423.2900390625, - "return_pct": -11.94, + "current_price": 1430.8399658203125, + "return_pct": -11.48, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": -11.94, + "return_1d": -11.48, "win_1d": false, - "return_7d": -11.94, + "return_7d": -11.48, "win_7d": false }, { @@ -979,13 +1206,13 @@ "entry_price": 116.0, "discovery_date": "2026-01-27", "status": "open", - "current_price": 109.05989837646484, - "return_pct": -5.98, + "current_price": 107.20999908447266, + "return_pct": -7.58, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": -5.98, + "return_1d": -7.58, "win_1d": false, - "return_7d": -5.98, + "return_7d": -7.58, "win_7d": false }, { @@ -998,13 +1225,13 @@ "entry_price": 2.75, "discovery_date": "2026-01-27", "status": "open", - "current_price": 2.2149999141693115, - "return_pct": -19.45, + "current_price": 2.240000009536743, + "return_pct": -18.55, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": -19.45, + "return_1d": -18.55, "win_1d": false, - "return_7d": -19.45, + "return_7d": -18.55, "win_7d": false }, { @@ -1017,13 +1244,13 @@ "entry_price": 95.98999786376953, "discovery_date": "2026-01-27", "status": "open", - "current_price": 99.27999877929688, - "return_pct": 3.43, + "current_price": 99.2699966430664, + "return_pct": 3.42, "days_held": 14, "last_updated": "2026-02-10", - "return_1d": 3.43, + "return_1d": 3.42, "win_1d": true, - "return_7d": 3.43, + "return_7d": 3.42, "win_7d": true } ], @@ -1038,13 +1265,13 @@ "entry_price": 1.7899999618530273, "discovery_date": "2026-01-31", "status": "open", - "current_price": 1.6100000143051147, - "return_pct": -10.06, + "current_price": 1.559999942779541, + "return_pct": -12.85, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -10.06, + "return_1d": -12.85, "win_1d": false, - "return_7d": -10.06, + "return_7d": -12.85, "win_7d": false }, { @@ -1057,13 +1284,13 @@ "entry_price": 206.1199951171875, "discovery_date": "2026-01-31", "status": "open", - "current_price": 247.5500030517578, - "return_pct": 20.1, + "current_price": 248.19000244140625, + "return_pct": 20.41, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": 20.1, + "return_1d": 20.41, "win_1d": true, - "return_7d": 20.1, + "return_7d": 20.41, "win_7d": true }, { @@ -1076,13 +1303,13 @@ "entry_price": 6.639999866485596, "discovery_date": "2026-01-31", "status": "open", - "current_price": 6.199999809265137, - "return_pct": -6.63, + "current_price": 6.21999979019165, + "return_pct": -6.33, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -6.63, + "return_1d": -6.33, "win_1d": false, - "return_7d": -6.63, + "return_7d": -6.33, "win_7d": false }, { @@ -1095,13 +1322,13 @@ "entry_price": 489.44000244140625, "discovery_date": "2026-01-31", "status": "open", - "current_price": 475.1300048828125, - "return_pct": -2.92, + "current_price": 466.2900085449219, + "return_pct": -4.73, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -2.92, + "return_1d": -4.73, "win_1d": false, - "return_7d": -2.92, + "return_7d": -4.73, "win_7d": false }, { @@ -1114,13 +1341,13 @@ "entry_price": 113.83000183105469, "discovery_date": "2026-01-31", "status": "open", - "current_price": 107.69000244140625, - "return_pct": -5.39, + "current_price": 106.98999786376953, + "return_pct": -6.01, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -5.39, + "return_1d": -6.01, "win_1d": false, - "return_7d": -5.39, + "return_7d": -6.01, "win_7d": false }, { @@ -1133,13 +1360,13 @@ "entry_price": 146.58999633789062, "discovery_date": "2026-01-31", "status": "open", - "current_price": 141.00010681152344, - "return_pct": -3.81, + "current_price": 139.50999450683594, + "return_pct": -4.83, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -3.81, + "return_1d": -4.83, "win_1d": false, - "return_7d": -3.81, + "return_7d": -4.83, "win_7d": false }, { @@ -1152,13 +1379,13 @@ "entry_price": 78.91999816894531, "discovery_date": "2026-01-31", "status": "open", - "current_price": 82.80999755859375, - "return_pct": 4.93, + "current_price": 82.01000213623047, + "return_pct": 3.92, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": 4.93, + "return_1d": 3.92, "win_1d": true, - "return_7d": 4.93, + "return_7d": 3.92, "win_7d": true }, { @@ -1171,13 +1398,13 @@ "entry_price": 33.95000076293945, "discovery_date": "2026-01-31", "status": "open", - "current_price": 28.260000228881836, - "return_pct": -16.76, + "current_price": 27.43000030517578, + "return_pct": -19.2, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -16.76, + "return_1d": -19.2, "win_1d": false, - "return_7d": -16.76, + "return_7d": -19.2, "win_7d": false }, { @@ -1190,13 +1417,13 @@ "entry_price": 239.3000030517578, "discovery_date": "2026-01-31", "status": "open", - "current_price": 210.16000366210938, - "return_pct": -12.18, + "current_price": 206.9600067138672, + "return_pct": -13.51, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -12.18, + "return_1d": -13.51, "win_1d": false, - "return_7d": -12.18, + "return_7d": -13.51, "win_7d": false }, { @@ -1209,13 +1436,13 @@ "entry_price": 576.25, "discovery_date": "2026-01-31", "status": "open", - "current_price": 556.869873046875, - "return_pct": -3.36, + "current_price": 541.6400146484375, + "return_pct": -6.01, "days_held": 10, "last_updated": "2026-02-10", - "return_1d": -3.36, + "return_1d": -6.01, "win_1d": false, - "return_7d": -3.36, + "return_7d": -6.01, "win_7d": false } ], @@ -1230,13 +1457,13 @@ "entry_price": 51.689998626708984, "discovery_date": "2026-01-28", "status": "open", - "current_price": 36.56999969482422, - "return_pct": -29.25, + "current_price": 36.650001525878906, + "return_pct": -29.1, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": -29.25, + "return_1d": -29.1, "win_1d": false, - "return_7d": -29.25, + "return_7d": -29.1, "win_7d": false }, { @@ -1249,13 +1476,13 @@ "entry_price": 668.72998046875, "discovery_date": "2026-01-28", "status": "open", - "current_price": 670.8400268554688, - "return_pct": 0.32, + "current_price": 670.719970703125, + "return_pct": 0.3, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": 0.32, + "return_1d": 0.3, "win_1d": true, - "return_7d": 0.32, + "return_7d": 0.3, "win_7d": true }, { @@ -1268,14 +1495,14 @@ "entry_price": 100.8499984741211, "discovery_date": "2026-01-28", "status": "open", - "current_price": 100.24500274658203, - "return_pct": -0.6, + "current_price": 100.91000366210938, + "return_pct": 0.06, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": -0.6, - "win_1d": false, - "return_7d": -0.6, - "win_7d": false + "return_1d": 0.06, + "win_1d": true, + "return_7d": 0.06, + "win_7d": true }, { "ticker": "LRCX", @@ -1287,13 +1514,13 @@ "entry_price": 239.5800018310547, "discovery_date": "2026-01-28", "status": "open", - "current_price": 223.2100067138672, - "return_pct": -6.83, + "current_price": 226.61000061035156, + "return_pct": -5.41, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": -6.83, + "return_1d": -5.41, "win_1d": false, - "return_7d": -6.83, + "return_7d": -5.41, "win_7d": false }, { @@ -1306,13 +1533,13 @@ "entry_price": 336.75, "discovery_date": "2026-01-28", "status": "open", - "current_price": 329.70001220703125, - "return_pct": -2.09, + "current_price": 329.07000732421875, + "return_pct": -2.28, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": -2.09, + "return_1d": -2.28, "win_1d": false, - "return_7d": -2.09, + "return_7d": -2.28, "win_7d": false }, { @@ -1325,13 +1552,13 @@ "entry_price": 279.70001220703125, "discovery_date": "2026-01-28", "status": "open", - "current_price": 262.01300048828125, - "return_pct": -6.32, + "current_price": 262.55999755859375, + "return_pct": -6.13, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": -6.32, + "return_1d": -6.13, "win_1d": false, - "return_7d": -6.32, + "return_7d": -6.13, "win_7d": false }, { @@ -1344,13 +1571,13 @@ "entry_price": 67.66999816894531, "discovery_date": "2026-01-28", "status": "open", - "current_price": 69.05000305175781, - "return_pct": 2.04, + "current_price": 69.91999816894531, + "return_pct": 3.32, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": 2.04, + "return_1d": 3.32, "win_1d": true, - "return_7d": 2.04, + "return_7d": 3.32, "win_7d": true }, { @@ -1363,13 +1590,13 @@ "entry_price": 21.030000686645508, "discovery_date": "2026-01-28", "status": "open", - "current_price": 27.510000228881836, - "return_pct": 30.81, + "current_price": 27.329999923706055, + "return_pct": 29.96, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": 30.81, + "return_1d": 29.96, "win_1d": true, - "return_7d": 30.81, + "return_7d": 29.96, "win_7d": true }, { @@ -1382,13 +1609,13 @@ "entry_price": 47.58000183105469, "discovery_date": "2026-01-28", "status": "open", - "current_price": 44.31999969482422, - "return_pct": -6.85, + "current_price": 45.0, + "return_pct": -5.42, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": -6.85, + "return_1d": -5.42, "win_1d": false, - "return_7d": -6.85, + "return_7d": -5.42, "win_7d": false }, { @@ -1401,13 +1628,13 @@ "entry_price": 48.779998779296875, "discovery_date": "2026-01-28", "status": "open", - "current_price": 47.6349983215332, - "return_pct": -2.35, + "current_price": 47.130001068115234, + "return_pct": -3.38, "days_held": 13, "last_updated": "2026-02-10", - "return_1d": -2.35, + "return_1d": -3.38, "win_1d": false, - "return_7d": -2.35, + "return_7d": -3.38, "win_7d": false } ], @@ -1422,13 +1649,13 @@ "entry_price": 1.809999942779541, "discovery_date": "2026-02-02", "status": "open", - "current_price": 1.6100000143051147, - "return_pct": -11.05, + "current_price": 1.559999942779541, + "return_pct": -13.81, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": -11.05, + "return_1d": -13.81, "win_1d": false, - "return_7d": -11.05, + "return_7d": -13.81, "win_7d": false }, { @@ -1441,13 +1668,13 @@ "entry_price": 147.75999450683594, "discovery_date": "2026-02-02", "status": "open", - "current_price": 141.00010681152344, - "return_pct": -4.57, + "current_price": 139.50999450683594, + "return_pct": -5.58, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": -4.57, + "return_1d": -5.58, "win_1d": false, - "return_7d": -4.57, + "return_7d": -5.58, "win_7d": false }, { @@ -1460,13 +1687,13 @@ "entry_price": 166.52000427246094, "discovery_date": "2026-02-02", "status": "open", - "current_price": 172.11500549316406, - "return_pct": 3.36, + "current_price": 174.1699981689453, + "return_pct": 4.59, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 3.36, + "return_1d": 4.59, "win_1d": true, - "return_7d": 3.36, + "return_7d": 4.59, "win_7d": true }, { @@ -1479,13 +1706,13 @@ "entry_price": 6.800000190734863, "discovery_date": "2026-02-02", "status": "open", - "current_price": 6.199999809265137, - "return_pct": -8.82, + "current_price": 6.21999979019165, + "return_pct": -8.53, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": -8.82, + "return_1d": -8.53, "win_1d": false, - "return_7d": -8.82, + "return_7d": -8.53, "win_7d": false }, { @@ -1498,13 +1725,13 @@ "entry_price": 40.689998626708984, "discovery_date": "2026-02-02", "status": "open", - "current_price": 47.900001525878906, - "return_pct": 17.72, + "current_price": 48.0, + "return_pct": 17.97, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 17.72, + "return_1d": 17.97, "win_1d": true, - "return_7d": 17.72, + "return_7d": 17.97, "win_7d": true }, { @@ -1517,13 +1744,13 @@ "entry_price": 185.27000427246094, "discovery_date": "2026-02-02", "status": "open", - "current_price": 199.55999755859375, - "return_pct": 7.71, + "current_price": 198.5, + "return_pct": 7.14, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 7.71, + "return_1d": 7.14, "win_1d": true, - "return_7d": 7.71, + "return_7d": 7.14, "win_7d": true }, { @@ -1536,13 +1763,13 @@ "entry_price": 46.810001373291016, "discovery_date": "2026-02-02", "status": "open", - "current_price": 49.44990158081055, - "return_pct": 5.64, + "current_price": 49.060001373291016, + "return_pct": 4.81, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 5.64, + "return_1d": 4.81, "win_1d": true, - "return_7d": 5.64, + "return_7d": 4.81, "win_7d": true }, { @@ -1555,13 +1782,13 @@ "entry_price": 41.880001068115234, "discovery_date": "2026-02-02", "status": "open", - "current_price": 39.79990005493164, - "return_pct": -4.97, + "current_price": 39.90999984741211, + "return_pct": -4.7, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": -4.97, + "return_1d": -4.7, "win_1d": false, - "return_7d": -4.97, + "return_7d": -4.7, "win_7d": false }, { @@ -1574,13 +1801,13 @@ "entry_price": 10.920000076293945, "discovery_date": "2026-02-02", "status": "open", - "current_price": 11.821999549865723, - "return_pct": 8.26, + "current_price": 11.479999542236328, + "return_pct": 5.13, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 8.26, + "return_1d": 5.13, "win_1d": true, - "return_7d": 8.26, + "return_7d": 5.13, "win_7d": true }, { @@ -1593,13 +1820,13 @@ "entry_price": 34.79999923706055, "discovery_date": "2026-02-02", "status": "open", - "current_price": 37.709999084472656, - "return_pct": 8.36, + "current_price": 37.470001220703125, + "return_pct": 7.67, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 8.36, + "return_1d": 7.67, "win_1d": true, - "return_7d": 8.36, + "return_7d": 7.67, "win_7d": true }, { @@ -1612,13 +1839,13 @@ "entry_price": 59.810001373291016, "discovery_date": "2026-02-02", "status": "open", - "current_price": 63.525001525878906, - "return_pct": 6.21, + "current_price": 64.08000183105469, + "return_pct": 7.14, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 6.21, + "return_1d": 7.14, "win_1d": true, - "return_7d": 6.21, + "return_7d": 7.14, "win_7d": true }, { @@ -1631,13 +1858,13 @@ "entry_price": 270.8800048828125, "discovery_date": "2026-02-02", "status": "open", - "current_price": 272.1549987792969, - "return_pct": 0.47, + "current_price": 271.1400146484375, + "return_pct": 0.1, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 0.47, + "return_1d": 0.1, "win_1d": true, - "return_7d": 0.47, + "return_7d": 0.1, "win_7d": true }, { @@ -1650,14 +1877,14 @@ "entry_price": 160.05999755859375, "discovery_date": "2026-02-02", "status": "open", - "current_price": 162.1143035888672, - "return_pct": 1.28, + "current_price": 159.88999938964844, + "return_pct": -0.11, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 1.28, - "win_1d": true, - "return_7d": 1.28, - "win_7d": true + "return_1d": -0.11, + "win_1d": false, + "return_7d": -0.11, + "win_7d": false }, { "ticker": "RTX", @@ -1669,13 +1896,13 @@ "entry_price": 201.08999633789062, "discovery_date": "2026-02-02", "status": "open", - "current_price": 194.3249969482422, - "return_pct": -3.36, + "current_price": 195.19000244140625, + "return_pct": -2.93, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": -3.36, + "return_1d": -2.93, "win_1d": false, - "return_7d": -3.36, + "return_7d": -2.93, "win_7d": false }, { @@ -1688,13 +1915,13 @@ "entry_price": 327.25, "discovery_date": "2026-02-02", "status": "open", - "current_price": 390.0, - "return_pct": 19.17, + "current_price": 391.5299987792969, + "return_pct": 19.64, "days_held": 8, "last_updated": "2026-02-10", - "return_1d": 19.17, + "return_1d": 19.64, "win_1d": true, - "return_7d": 19.17, + "return_7d": 19.64, "win_7d": true } ], @@ -1709,13 +1936,13 @@ "entry_price": 29.670000076293945, "discovery_date": "2026-02-03", "status": "open", - "current_price": 34.08000183105469, - "return_pct": 14.86, + "current_price": 33.33000183105469, + "return_pct": 12.34, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 14.86, + "return_1d": 12.34, "win_1d": true, - "return_7d": 14.86, + "return_7d": 12.34, "win_7d": true }, { @@ -1728,13 +1955,13 @@ "entry_price": 180.33999633789062, "discovery_date": "2026-02-03", "status": "open", - "current_price": 189.28990173339844, - "return_pct": 4.96, + "current_price": 188.5399932861328, + "return_pct": 4.55, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 4.96, + "return_1d": 4.55, "win_1d": true, - "return_7d": 4.96, + "return_7d": 4.55, "win_7d": true }, { @@ -1747,13 +1974,13 @@ "entry_price": 318.6700134277344, "discovery_date": "2026-02-03", "status": "open", - "current_price": 329.70001220703125, - "return_pct": 3.46, + "current_price": 329.07000732421875, + "return_pct": 3.26, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 3.46, + "return_1d": 3.26, "win_1d": true, - "return_7d": 3.46, + "return_7d": 3.26, "win_7d": true }, { @@ -1766,13 +1993,13 @@ "entry_price": 230.10000610351562, "discovery_date": "2026-02-03", "status": "open", - "current_price": 223.2100067138672, - "return_pct": -2.99, + "current_price": 226.61000061035156, + "return_pct": -1.52, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": -2.99, + "return_1d": -1.52, "win_1d": false, - "return_7d": -2.99, + "return_7d": -1.52, "win_7d": false }, { @@ -1785,13 +2012,13 @@ "entry_price": 242.11000061035156, "discovery_date": "2026-02-03", "status": "open", - "current_price": 216.25, - "return_pct": -10.68, + "current_price": 213.57000732421875, + "return_pct": -11.79, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": -10.68, + "return_1d": -11.79, "win_1d": false, - "return_7d": -10.68, + "return_7d": -11.79, "win_7d": false }, { @@ -1804,13 +2031,13 @@ "entry_price": 83.11000061035156, "discovery_date": "2026-02-03", "status": "open", - "current_price": 87.51000213623047, - "return_pct": 5.29, + "current_price": 86.29000091552734, + "return_pct": 3.83, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 5.29, + "return_1d": 3.83, "win_1d": true, - "return_7d": 5.29, + "return_7d": 3.83, "win_7d": true }, { @@ -1823,13 +2050,13 @@ "entry_price": 91.79000091552734, "discovery_date": "2026-02-03", "status": "open", - "current_price": 94.75, - "return_pct": 3.22, + "current_price": 94.4000015258789, + "return_pct": 2.84, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 3.22, + "return_1d": 2.84, "win_1d": true, - "return_7d": 3.22, + "return_7d": 2.84, "win_7d": true }, { @@ -1842,13 +2069,13 @@ "entry_price": 120.41999816894531, "discovery_date": "2026-02-03", "status": "open", - "current_price": 130.7050018310547, - "return_pct": 8.54, + "current_price": 131.32000732421875, + "return_pct": 9.05, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 8.54, + "return_1d": 9.05, "win_1d": true, - "return_7d": 8.54, + "return_7d": 9.05, "win_7d": true }, { @@ -1861,13 +2088,13 @@ "entry_price": 41.36000061035156, "discovery_date": "2026-02-03", "status": "open", - "current_price": 39.79990005493164, - "return_pct": -3.77, + "current_price": 39.90999984741211, + "return_pct": -3.51, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": -3.77, + "return_1d": -3.51, "win_1d": false, - "return_7d": -3.77, + "return_7d": -3.51, "win_7d": false }, { @@ -1880,13 +2107,13 @@ "entry_price": 263.0299987792969, "discovery_date": "2026-02-03", "status": "open", - "current_price": 278.5, - "return_pct": 5.88, + "current_price": 279.0400085449219, + "return_pct": 6.09, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 5.88, + "return_1d": 6.09, "win_1d": true, - "return_7d": 5.88, + "return_7d": 6.09, "win_7d": true }, { @@ -1899,13 +2126,13 @@ "entry_price": 8.460000038146973, "discovery_date": "2026-02-03", "status": "open", - "current_price": 7.965000152587891, - "return_pct": -5.85, + "current_price": 7.78000020980835, + "return_pct": -8.04, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": -5.85, + "return_1d": -8.04, "win_1d": false, - "return_7d": -5.85, + "return_7d": -8.04, "win_7d": false }, { @@ -1918,13 +2145,13 @@ "entry_price": 15.899999618530273, "discovery_date": "2026-02-03", "status": "open", - "current_price": 15.319999694824219, - "return_pct": -3.65, + "current_price": 14.619999885559082, + "return_pct": -8.05, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": -3.65, + "return_1d": -8.05, "win_1d": false, - "return_7d": -3.65, + "return_7d": -8.05, "win_7d": false }, { @@ -1937,13 +2164,13 @@ "entry_price": 274.6300048828125, "discovery_date": "2026-02-03", "status": "open", - "current_price": 280.6600036621094, - "return_pct": 2.2, + "current_price": 282.3800048828125, + "return_pct": 2.82, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 2.2, + "return_1d": 2.82, "win_1d": true, - "return_7d": 2.2, + "return_7d": 2.82, "win_7d": true }, { @@ -1956,13 +2183,13 @@ "entry_price": 63.459999084472656, "discovery_date": "2026-02-03", "status": "open", - "current_price": 66.16000366210938, - "return_pct": 4.25, + "current_price": 66.79000091552734, + "return_pct": 5.25, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 4.25, + "return_1d": 5.25, "win_1d": true, - "return_7d": 4.25, + "return_7d": 5.25, "win_7d": true }, { @@ -1975,13 +2202,13 @@ "entry_price": 269.4800109863281, "discovery_date": "2026-02-03", "status": "open", - "current_price": 273.3699951171875, - "return_pct": 1.44, + "current_price": 273.67999267578125, + "return_pct": 1.56, "days_held": 7, "last_updated": "2026-02-10", - "return_1d": 1.44, + "return_1d": 1.56, "win_1d": true, - "return_7d": 1.44, + "return_7d": 1.56, "win_7d": true } ], @@ -1996,13 +2223,13 @@ "entry_price": 38.06999969482422, "discovery_date": "2026-01-29", "status": "open", - "current_price": 37.709999084472656, - "return_pct": -0.95, + "current_price": 37.470001220703125, + "return_pct": -1.58, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": -0.95, + "return_1d": -1.58, "win_1d": false, - "return_7d": -0.95, + "return_7d": -1.58, "win_7d": false }, { @@ -2015,13 +2242,13 @@ "entry_price": 258.2799987792969, "discovery_date": "2026-01-29", "status": "open", - "current_price": 273.3699951171875, - "return_pct": 5.84, + "current_price": 273.67999267578125, + "return_pct": 5.96, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": 5.84, + "return_1d": 5.96, "win_1d": true, - "return_7d": 5.84, + "return_7d": 5.96, "win_7d": true }, { @@ -2034,13 +2261,13 @@ "entry_price": 5.929999828338623, "discovery_date": "2026-01-29", "status": "open", - "current_price": 7.074999809265137, - "return_pct": 19.31, + "current_price": 6.840000152587891, + "return_pct": 15.35, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": 19.31, + "return_1d": 15.35, "win_1d": true, - "return_7d": 19.31, + "return_7d": 15.35, "win_7d": true }, { @@ -2053,13 +2280,13 @@ "entry_price": 79.36000061035156, "discovery_date": "2026-01-29", "status": "open", - "current_price": 77.20999908447266, - "return_pct": -2.71, + "current_price": 76.86000061035156, + "return_pct": -3.15, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": -2.71, + "return_1d": -3.15, "win_1d": false, - "return_7d": -2.71, + "return_7d": -3.15, "win_7d": false }, { @@ -2072,13 +2299,13 @@ "entry_price": 48.65999984741211, "discovery_date": "2026-01-29", "status": "open", - "current_price": 47.650001525878906, - "return_pct": -2.08, + "current_price": 47.130001068115234, + "return_pct": -3.14, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": -2.08, + "return_1d": -3.14, "win_1d": false, - "return_7d": -2.08, + "return_7d": -3.14, "win_7d": false }, { @@ -2091,14 +2318,14 @@ "entry_price": 22.06999969482422, "discovery_date": "2026-01-29", "status": "open", - "current_price": 22.78499984741211, - "return_pct": 3.24, + "current_price": 21.8799991607666, + "return_pct": -0.86, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": 3.24, - "win_1d": true, - "return_7d": 3.24, - "win_7d": true + "return_1d": -0.86, + "win_1d": false, + "return_7d": -0.86, + "win_7d": false }, { "ticker": "LTRX", @@ -2110,13 +2337,13 @@ "entry_price": 6.800000190734863, "discovery_date": "2026-01-29", "status": "open", - "current_price": 6.199999809265137, - "return_pct": -8.82, + "current_price": 6.21999979019165, + "return_pct": -8.53, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": -8.82, + "return_1d": -8.53, "win_1d": false, - "return_7d": -8.82, + "return_7d": -8.53, "win_7d": false }, { @@ -2129,13 +2356,13 @@ "entry_price": 1684.7099609375, "discovery_date": "2026-01-29", "status": "open", - "current_price": 1423.2900390625, - "return_pct": -15.52, + "current_price": 1430.8399658203125, + "return_pct": -15.07, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": -15.52, + "return_1d": -15.07, "win_1d": false, - "return_7d": -15.52, + "return_7d": -15.07, "win_7d": false }, { @@ -2148,13 +2375,13 @@ "entry_price": 252.17999267578125, "discovery_date": "2026-01-29", "status": "open", - "current_price": 216.25, - "return_pct": -14.25, + "current_price": 213.57000732421875, + "return_pct": -15.31, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": -14.25, + "return_1d": -15.31, "win_1d": false, - "return_7d": -14.25, + "return_7d": -15.31, "win_7d": false }, { @@ -2167,13 +2394,13 @@ "entry_price": 2.990000009536743, "discovery_date": "2026-01-29", "status": "open", - "current_price": 2.575000047683716, - "return_pct": -13.88, + "current_price": 2.640000104904175, + "return_pct": -11.71, "days_held": 12, "last_updated": "2026-02-10", - "return_1d": -13.88, + "return_1d": -11.71, "win_1d": false, - "return_7d": -13.88, + "return_7d": -11.71, "win_7d": false } ], @@ -2188,13 +2415,13 @@ "entry_price": 658.760009765625, "discovery_date": "2026-01-25", "status": "open", - "current_price": 670.9718017578125, - "return_pct": 1.85, + "current_price": 670.719970703125, + "return_pct": 1.82, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": 1.85, + "return_1d": 1.82, "win_1d": true, - "return_7d": 1.85, + "return_7d": 1.82, "win_7d": true }, { @@ -2207,14 +2434,14 @@ "entry_price": 37.689998626708984, "discovery_date": "2026-01-25", "status": "open", - "current_price": 37.709999084472656, - "return_pct": 0.05, + "current_price": 37.470001220703125, + "return_pct": -0.58, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": 0.05, - "win_1d": true, - "return_7d": 0.05, - "win_7d": true + "return_1d": -0.58, + "win_1d": false, + "return_7d": -0.58, + "win_7d": false }, { "ticker": "FCX", @@ -2226,13 +2453,13 @@ "entry_price": 60.40999984741211, "discovery_date": "2026-01-25", "status": "open", - "current_price": 62.814998626708984, - "return_pct": 3.98, + "current_price": 63.2599983215332, + "return_pct": 4.72, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": 3.98, + "return_1d": 4.72, "win_1d": true, - "return_7d": 3.98, + "return_7d": 4.72, "win_7d": true }, { @@ -2245,13 +2472,13 @@ "entry_price": 47.540000915527344, "discovery_date": "2026-01-25", "status": "open", - "current_price": 50.400001525878906, - "return_pct": 6.02, + "current_price": 50.2400016784668, + "return_pct": 5.68, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": 6.02, + "return_1d": 5.68, "win_1d": true, - "return_7d": 6.02, + "return_7d": 5.68, "win_7d": true }, { @@ -2283,13 +2510,13 @@ "entry_price": 110.13999938964844, "discovery_date": "2026-01-25", "status": "open", - "current_price": 99.27999877929688, - "return_pct": -9.86, + "current_price": 97.91999816894531, + "return_pct": -11.09, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": -9.86, + "return_1d": -11.09, "win_1d": false, - "return_7d": -9.86, + "return_7d": -11.09, "win_7d": false }, { @@ -2302,13 +2529,13 @@ "entry_price": 56.619998931884766, "discovery_date": "2026-01-25", "status": "open", - "current_price": 42.040000915527344, - "return_pct": -25.75, + "current_price": 41.4900016784668, + "return_pct": -26.72, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": -25.75, + "return_1d": -26.72, "win_1d": false, - "return_7d": -25.75, + "return_7d": -26.72, "win_7d": false }, { @@ -2321,13 +2548,13 @@ "entry_price": 36.20000076293945, "discovery_date": "2026-01-25", "status": "open", - "current_price": 38.349998474121094, - "return_pct": 5.94, + "current_price": 37.900001525878906, + "return_pct": 4.7, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": 5.94, + "return_1d": 4.7, "win_1d": true, - "return_7d": 5.94, + "return_7d": 4.7, "win_7d": true }, { @@ -2340,13 +2567,13 @@ "entry_price": 12.489999771118164, "discovery_date": "2026-01-25", "status": "open", - "current_price": 13.244999885559082, - "return_pct": 6.04, + "current_price": 13.15999984741211, + "return_pct": 5.36, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": 6.04, + "return_1d": 5.36, "win_1d": true, - "return_7d": 6.04, + "return_7d": 5.36, "win_7d": true }, { @@ -2359,13 +2586,13 @@ "entry_price": 4.409999847412109, "discovery_date": "2026-01-25", "status": "open", - "current_price": 5.054999828338623, - "return_pct": 14.63, + "current_price": 5.059999942779541, + "return_pct": 14.74, "days_held": 16, "last_updated": "2026-02-10", - "return_1d": 14.63, + "return_1d": 14.74, "win_1d": true, - "return_7d": 14.63, + "return_7d": 14.74, "win_7d": true } ], @@ -2380,11 +2607,11 @@ "entry_price": 8.835000038146973, "discovery_date": "2026-02-04", "status": "open", - "current_price": 8.550000190734863, - "return_pct": -3.23, + "current_price": 8.460000038146973, + "return_pct": -4.24, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": -3.23, + "return_1d": -4.24, "win_1d": false }, { @@ -2397,11 +2624,11 @@ "entry_price": 53.834999084472656, "discovery_date": "2026-02-04", "status": "open", - "current_price": 56.75, - "return_pct": 5.41, + "current_price": 56.599998474121094, + "return_pct": 5.14, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 5.41, + "return_1d": 5.14, "win_1d": true }, { @@ -2414,11 +2641,11 @@ "entry_price": 22.80500030517578, "discovery_date": "2026-02-04", "status": "open", - "current_price": 22.510000228881836, - "return_pct": -1.29, + "current_price": 22.43000030517578, + "return_pct": -1.64, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": -1.29, + "return_1d": -1.64, "win_1d": false }, { @@ -2431,11 +2658,11 @@ "entry_price": 1100.3299560546875, "discovery_date": "2026-02-04", "status": "open", - "current_price": 1034.2900390625, - "return_pct": -6.0, + "current_price": 1025.0, + "return_pct": -6.85, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": -6.0, + "return_1d": -6.85, "win_1d": false }, { @@ -2448,11 +2675,11 @@ "entry_price": 236.21499633789062, "discovery_date": "2026-02-04", "status": "open", - "current_price": 243.39500427246094, - "return_pct": 3.04, + "current_price": 243.33999633789062, + "return_pct": 3.02, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 3.04, + "return_1d": 3.02, "win_1d": true }, { @@ -2465,11 +2692,11 @@ "entry_price": 119.63500213623047, "discovery_date": "2026-02-04", "status": "open", - "current_price": 125.16999816894531, - "return_pct": 4.63, + "current_price": 126.01000213623047, + "return_pct": 5.33, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 4.63, + "return_1d": 5.33, "win_1d": true }, { @@ -2482,11 +2709,11 @@ "entry_price": 278.9100036621094, "discovery_date": "2026-02-04", "status": "open", - "current_price": 268.3599853515625, - "return_pct": -3.78, + "current_price": 264.6700134277344, + "return_pct": -5.11, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": -3.78, + "return_1d": -5.11, "win_1d": false }, { @@ -2499,11 +2726,11 @@ "entry_price": 22.645099639892578, "discovery_date": "2026-02-04", "status": "open", - "current_price": 24.05500030517578, - "return_pct": 6.23, + "current_price": 23.969999313354492, + "return_pct": 5.85, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 6.23, + "return_1d": 5.85, "win_1d": true }, { @@ -2516,11 +2743,11 @@ "entry_price": 77.70999908447266, "discovery_date": "2026-02-04", "status": "open", - "current_price": 76.49500274658203, - "return_pct": -1.56, + "current_price": 76.80999755859375, + "return_pct": -1.16, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": -1.56, + "return_1d": -1.16, "win_1d": false }, { @@ -2533,11 +2760,11 @@ "entry_price": 236.90499877929688, "discovery_date": "2026-02-04", "status": "open", - "current_price": 226.30999755859375, - "return_pct": -4.47, + "current_price": 225.52999877929688, + "return_pct": -4.8, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": -4.47, + "return_1d": -4.8, "win_1d": false }, { @@ -2550,11 +2777,11 @@ "entry_price": 50.13999938964844, "discovery_date": "2026-02-04", "status": "open", - "current_price": 53.685001373291016, - "return_pct": 7.07, + "current_price": 53.97999954223633, + "return_pct": 7.66, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 7.07, + "return_1d": 7.66, "win_1d": true }, { @@ -2567,12 +2794,12 @@ "entry_price": 13.84000015258789, "discovery_date": "2026-02-04", "status": "open", - "current_price": 13.890000343322754, - "return_pct": 0.36, + "current_price": 13.65999984741211, + "return_pct": -1.3, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 0.36, - "win_1d": true + "return_1d": -1.3, + "win_1d": false }, { "ticker": "MCD", @@ -2584,11 +2811,11 @@ "entry_price": 325.6925048828125, "discovery_date": "2026-02-04", "status": "open", - "current_price": 326.4049987792969, - "return_pct": 0.22, + "current_price": 325.9700012207031, + "return_pct": 0.09, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 0.22, + "return_1d": 0.09, "win_1d": true }, { @@ -2601,11 +2828,11 @@ "entry_price": 32.88159942626953, "discovery_date": "2026-02-04", "status": "open", - "current_price": 34.150001525878906, - "return_pct": 3.86, + "current_price": 33.33000183105469, + "return_pct": 1.36, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 3.86, + "return_1d": 1.36, "win_1d": true }, { @@ -2618,11 +2845,11 @@ "entry_price": 198.91000366210938, "discovery_date": "2026-02-04", "status": "open", - "current_price": 202.75, - "return_pct": 1.93, + "current_price": 202.5800018310547, + "return_pct": 1.85, "days_held": 6, "last_updated": "2026-02-10", - "return_1d": 1.93, + "return_1d": 1.85, "win_1d": true } ], @@ -2637,11 +2864,11 @@ "entry_price": 24.639999389648438, "discovery_date": "2026-02-09", "status": "open", - "current_price": 24.950000762939453, - "return_pct": 1.26, + "current_price": 24.81999969482422, + "return_pct": 0.73, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 1.26, + "return_1d": 0.73, "win_1d": true }, { @@ -2654,11 +2881,11 @@ "entry_price": 8.699999809265137, "discovery_date": "2026-02-09", "status": "open", - "current_price": 9.010000228881836, - "return_pct": 3.56, + "current_price": 8.75, + "return_pct": 0.57, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 3.56, + "return_1d": 0.57, "win_1d": true }, { @@ -2671,12 +2898,12 @@ "entry_price": 194.02999877929688, "discovery_date": "2026-02-09", "status": "open", - "current_price": 196.1999969482422, - "return_pct": 1.12, + "current_price": 193.4499969482422, + "return_pct": -0.3, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 1.12, - "win_1d": true + "return_1d": -0.3, + "win_1d": false }, { "ticker": "WRB", @@ -2688,12 +2915,12 @@ "entry_price": 69.25, "discovery_date": "2026-02-09", "status": "open", - "current_price": 69.04000091552734, - "return_pct": -0.3, + "current_price": 69.91999816894531, + "return_pct": 0.97, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": -0.3, - "win_1d": false + "return_1d": 0.97, + "win_1d": true }, { "ticker": "TDOC", @@ -2705,12 +2932,12 @@ "entry_price": 4.980000019073486, "discovery_date": "2026-02-09", "status": "open", - "current_price": 5.034999847412109, - "return_pct": 1.1, + "current_price": 4.849999904632568, + "return_pct": -2.61, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 1.1, - "win_1d": true + "return_1d": -2.61, + "win_1d": false }, { "ticker": "CZR", @@ -2722,11 +2949,11 @@ "entry_price": 20.649999618530273, "discovery_date": "2026-02-09", "status": "open", - "current_price": 21.05500030517578, - "return_pct": 1.96, + "current_price": 20.75, + "return_pct": 0.48, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 1.96, + "return_1d": 0.48, "win_1d": true }, { @@ -2739,12 +2966,12 @@ "entry_price": 2.549999952316284, "discovery_date": "2026-02-09", "status": "open", - "current_price": 2.619999885559082, - "return_pct": 2.75, + "current_price": 2.5299999713897705, + "return_pct": -0.78, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 2.75, - "win_1d": true + "return_1d": -0.78, + "win_1d": false }, { "ticker": "UWMC", @@ -2756,11 +2983,11 @@ "entry_price": 4.630000114440918, "discovery_date": "2026-02-09", "status": "open", - "current_price": 4.855000019073486, - "return_pct": 4.86, + "current_price": 4.840000152587891, + "return_pct": 4.54, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 4.86, + "return_1d": 4.54, "win_1d": true }, { @@ -2773,12 +3000,12 @@ "entry_price": 7.909999847412109, "discovery_date": "2026-02-09", "status": "open", - "current_price": 7.945000171661377, - "return_pct": 0.44, + "current_price": 7.809999942779541, + "return_pct": -1.26, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 0.44, - "win_1d": true + "return_1d": -1.26, + "win_1d": false }, { "ticker": "PMN", @@ -2790,11 +3017,11 @@ "entry_price": 13.050000190734863, "discovery_date": "2026-02-09", "status": "open", - "current_price": 13.550000190734863, - "return_pct": 3.83, + "current_price": 14.289999961853027, + "return_pct": 9.5, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 3.83, + "return_1d": 9.5, "win_1d": true }, { @@ -2807,11 +3034,11 @@ "entry_price": 4.349999904632568, "discovery_date": "2026-02-09", "status": "open", - "current_price": 4.25, - "return_pct": -2.3, + "current_price": 3.9600000381469727, + "return_pct": -8.97, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": -2.3, + "return_1d": -8.97, "win_1d": false }, { @@ -2824,11 +3051,11 @@ "entry_price": 102.12000274658203, "discovery_date": "2026-02-09", "status": "open", - "current_price": 99.86000061035156, - "return_pct": -2.21, + "current_price": 96.2699966430664, + "return_pct": -5.73, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": -2.21, + "return_1d": -5.73, "win_1d": false }, { @@ -2841,11 +3068,11 @@ "entry_price": 6.210000038146973, "discovery_date": "2026-02-09", "status": "open", - "current_price": 6.119999885559082, - "return_pct": -1.45, + "current_price": 5.840000152587891, + "return_pct": -5.96, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": -1.45, + "return_1d": -5.96, "win_1d": false }, { @@ -2858,11 +3085,11 @@ "entry_price": 43.779998779296875, "discovery_date": "2026-02-09", "status": "open", - "current_price": 45.625, - "return_pct": 4.21, + "current_price": 44.880001068115234, + "return_pct": 2.51, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 4.21, + "return_1d": 2.51, "win_1d": true }, { @@ -2875,11 +3102,11 @@ "entry_price": 5.610000133514404, "discovery_date": "2026-02-09", "status": "open", - "current_price": 5.849999904632568, - "return_pct": 4.28, + "current_price": 5.789999961853027, + "return_pct": 3.21, "days_held": 1, "last_updated": "2026-02-10", - "return_1d": 4.28, + "return_1d": 3.21, "win_1d": true } ], @@ -2894,11 +3121,11 @@ "entry_price": 24.690000534057617, "discovery_date": "2026-02-05", "status": "open", - "current_price": 24.950000762939453, - "return_pct": 1.05, + "current_price": 24.81999969482422, + "return_pct": 0.53, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 1.05, + "return_1d": 0.53, "win_1d": true }, { @@ -2911,11 +3138,11 @@ "entry_price": 44.369998931884766, "discovery_date": "2026-02-05", "status": "open", - "current_price": 47.92499923706055, - "return_pct": 8.01, + "current_price": 48.0, + "return_pct": 8.18, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 8.01, + "return_1d": 8.18, "win_1d": true }, { @@ -2928,11 +3155,11 @@ "entry_price": 30.059999465942383, "discovery_date": "2026-02-05", "status": "open", - "current_price": 31.510000228881836, - "return_pct": 4.82, + "current_price": 31.600000381469727, + "return_pct": 5.12, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 4.82, + "return_1d": 5.12, "win_1d": true }, { @@ -2945,11 +3172,11 @@ "entry_price": 104.41000366210938, "discovery_date": "2026-02-05", "status": "open", - "current_price": 110.5999984741211, - "return_pct": 5.93, + "current_price": 109.6500015258789, + "return_pct": 5.02, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 5.93, + "return_1d": 5.02, "win_1d": true }, { @@ -2962,11 +3189,11 @@ "entry_price": 103.5, "discovery_date": "2026-02-05", "status": "open", - "current_price": 104.08000183105469, - "return_pct": 0.56, + "current_price": 104.0, + "return_pct": 0.48, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 0.56, + "return_1d": 0.48, "win_1d": true }, { @@ -2979,11 +3206,11 @@ "entry_price": 42.36000061035156, "discovery_date": "2026-02-05", "status": "open", - "current_price": 39.80500030517578, - "return_pct": -6.03, + "current_price": 39.90999984741211, + "return_pct": -5.78, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": -6.03, + "return_1d": -5.78, "win_1d": false }, { @@ -2996,11 +3223,11 @@ "entry_price": 406.70001220703125, "discovery_date": "2026-02-05", "status": "open", - "current_price": 413.5, - "return_pct": 1.67, + "current_price": 412.6000061035156, + "return_pct": 1.45, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 1.67, + "return_1d": 1.45, "win_1d": true }, { @@ -3013,11 +3240,11 @@ "entry_price": 397.2099914550781, "discovery_date": "2026-02-05", "status": "open", - "current_price": 423.6000061035156, - "return_pct": 6.64, + "current_price": 425.2099914550781, + "return_pct": 7.05, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 6.64, + "return_1d": 7.05, "win_1d": true }, { @@ -3030,12 +3257,12 @@ "entry_price": 37.81999969482422, "discovery_date": "2026-02-05", "status": "open", - "current_price": 37.81999969482422, - "return_pct": 0.0, + "current_price": 37.970001220703125, + "return_pct": 0.4, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 0.0, - "win_1d": false + "return_1d": 0.4, + "win_1d": true }, { "ticker": "STE", @@ -3047,11 +3274,11 @@ "entry_price": 243.80999755859375, "discovery_date": "2026-02-05", "status": "open", - "current_price": 247.11000061035156, - "return_pct": 1.35, + "current_price": 244.6699981689453, + "return_pct": 0.35, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 1.35, + "return_1d": 0.35, "win_1d": true }, { @@ -3064,11 +3291,11 @@ "entry_price": 147.47999572753906, "discovery_date": "2026-02-05", "status": "open", - "current_price": 152.57000732421875, - "return_pct": 3.45, + "current_price": 148.94000244140625, + "return_pct": 0.99, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 3.45, + "return_1d": 0.99, "win_1d": true }, { @@ -3081,11 +3308,11 @@ "entry_price": 37.15999984741211, "discovery_date": "2026-02-05", "status": "open", - "current_price": 37.689998626708984, - "return_pct": 1.43, + "current_price": 40.45000076293945, + "return_pct": 8.85, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 1.43, + "return_1d": 8.85, "win_1d": true }, { @@ -3098,11 +3325,11 @@ "entry_price": 148.5399932861328, "discovery_date": "2026-02-05", "status": "open", - "current_price": 165.77000427246094, - "return_pct": 11.6, + "current_price": 166.0, + "return_pct": 11.75, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 11.6, + "return_1d": 11.75, "win_1d": true }, { @@ -3115,11 +3342,11 @@ "entry_price": 4.690000057220459, "discovery_date": "2026-02-05", "status": "open", - "current_price": 4.820000171661377, - "return_pct": 2.77, + "current_price": 4.699999809265137, + "return_pct": 0.21, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 2.77, + "return_1d": 0.21, "win_1d": true }, { @@ -3132,11 +3359,11 @@ "entry_price": 187.77999877929688, "discovery_date": "2026-02-05", "status": "open", - "current_price": 199.55999755859375, - "return_pct": 6.27, + "current_price": 198.5, + "return_pct": 5.71, "days_held": 5, "last_updated": "2026-02-10", - "return_1d": 6.27, + "return_1d": 5.71, "win_1d": true } ] diff --git a/data/recommendations/statistics.json b/data/recommendations/statistics.json index 705e9b10..c758d7d1 100644 --- a/data/recommendations/statistics.json +++ b/data/recommendations/statistics.json @@ -1,33 +1,47 @@ { - "total_recommendations": 170, + "total_recommendations": 185, "by_strategy": { "momentum": { - "count": 35, - "wins_1d": 29, - "losses_1d": 6, - "wins_7d": 5, + "count": 92, + "wins_1d": 45, + "losses_1d": 33, + "wins_7d": 25, + "losses_7d": 23, + "wins_30d": 0, + "losses_30d": 0, + "avg_return_1d": 1.0, + "avg_return_7d": 0.71, + "avg_return_30d": 0, + "win_rate_1d": 57.7, + "win_rate_7d": 52.1 + }, + "volume_accumulation": { + "count": 2, + "wins_1d": 1, + "losses_1d": 0, + "wins_7d": 1, "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": 19.7, + "avg_return_7d": 19.7, "avg_return_30d": 0, - "win_rate_1d": 82.9, + "win_rate_1d": 100.0, "win_rate_7d": 100.0 }, "insider_buying": { - "count": 8, - "wins_1d": 6, - "losses_1d": 2, - "wins_7d": 2, - "losses_7d": 0, + "count": 21, + "wins_1d": 15, + "losses_1d": 6, + "wins_7d": 10, + "losses_7d": 5, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": 0.84, + "avg_return_7d": 0.18, "avg_return_30d": 0, - "win_rate_1d": 75.0, - "win_rate_7d": 100.0 + "win_rate_1d": 71.4, + "win_rate_7d": 66.7 }, "options_flow": { "count": 5, @@ -37,53 +51,26 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, + "avg_return_1d": 3.09, "avg_return_7d": 0, "avg_return_30d": 0, "win_rate_1d": 80.0 }, "earnings_calendar": { - "count": 4, - "wins_1d": 1, - "losses_1d": 3, - "wins_7d": 0, - "losses_7d": 0, + "count": 17, + "wins_1d": 6, + "losses_1d": 11, + "wins_7d": 4, + "losses_7d": 8, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": -0.23, + "avg_return_7d": 0.36, "avg_return_30d": 0, - "win_rate_1d": 25.0 + "win_rate_1d": 35.3, + "win_rate_7d": 33.3 }, - "Momentum": { - "count": 40, - "wins_1d": 18, - "losses_1d": 22, - "wins_7d": 18, - "losses_7d": 22, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 45.0, - "win_rate_7d": 45.0 - }, - "Insider Play": { - "count": 13, - "wins_1d": 7, - "losses_1d": 6, - "wins_7d": 7, - "losses_7d": 6, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 53.8, - "win_rate_7d": 53.8 - }, - "Contrarian Value": { + "contrarian_value": { "count": 6, "wins_1d": 3, "losses_1d": 3, @@ -91,67 +78,39 @@ "losses_7d": 3, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": -4.91, + "avg_return_7d": -4.91, "avg_return_30d": 0, "win_rate_1d": 50.0, "win_rate_7d": 50.0 }, - "Earnings Play": { + "news_catalyst": { "count": 3, - "wins_1d": 1, - "losses_1d": 2, - "wins_7d": 1, - "losses_7d": 2, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 33.3, - "win_rate_7d": 33.3 - }, - "News Catalyst": { - "count": 1, "wins_1d": 0, - "losses_1d": 1, + "losses_1d": 3, "wins_7d": 0, - "losses_7d": 1, + "losses_7d": 3, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": -13.55, + "avg_return_7d": -13.55, "avg_return_30d": 0, "win_rate_1d": 0.0, "win_rate_7d": 0.0 }, - "Volume Accumulation": { - "count": 1, - "wins_1d": 1, - "losses_1d": 0, - "wins_7d": 1, - "losses_7d": 0, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 100.0, - "win_rate_7d": 100.0 - }, "short_squeeze": { - "count": 7, - "wins_1d": 4, - "losses_1d": 3, - "wins_7d": 2, - "losses_7d": 2, + "count": 10, + "wins_1d": 5, + "losses_1d": 5, + "wins_7d": 4, + "losses_7d": 3, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": 0.56, + "avg_return_7d": 0.85, "avg_return_30d": 0, - "win_rate_1d": 57.1, - "win_rate_7d": 50.0 + "win_rate_1d": 50.0, + "win_rate_7d": 57.1 }, "early_accumulation": { "count": 1, @@ -161,8 +120,8 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": 20.41, + "avg_return_7d": 20.41, "avg_return_30d": 0, "win_rate_1d": 100.0, "win_rate_7d": 100.0 @@ -175,26 +134,12 @@ "losses_7d": 4, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": -2.0, + "avg_return_7d": -2.06, "avg_return_30d": 0, "win_rate_1d": 28.6, "win_rate_7d": 33.3 }, - "earnings_play": { - "count": 10, - "wins_1d": 4, - "losses_1d": 6, - "wins_7d": 3, - "losses_7d": 6, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 40.0, - "win_rate_7d": 33.3 - }, "analyst_upgrade": { "count": 8, "wins_1d": 6, @@ -203,8 +148,8 @@ "losses_7d": 2, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": 1.32, + "avg_return_7d": -0.1, "avg_return_30d": 0, "win_rate_1d": 75.0, "win_rate_7d": 66.7 @@ -217,8 +162,8 @@ "losses_7d": 1, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": -19.2, + "avg_return_7d": -19.2, "avg_return_30d": 0, "win_rate_1d": 0.0, "win_rate_7d": 0.0 @@ -231,26 +176,12 @@ "losses_7d": 2, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": -8.9, + "avg_return_7d": -8.9, "avg_return_30d": 0, "win_rate_1d": 0.0, "win_rate_7d": 0.0 }, - "news_catalyst": { - "count": 2, - "wins_1d": 1, - "losses_1d": 1, - "wins_7d": 1, - "losses_7d": 1, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 50.0, - "win_rate_7d": 50.0 - }, "undiscovered_dd": { "count": 2, "wins_1d": 2, @@ -259,36 +190,8 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 100.0, - "win_rate_7d": 100.0 - }, - "Momentum/Hype": { - "count": 3, - "wins_1d": 3, - "losses_1d": 0, - "wins_7d": 3, - "losses_7d": 0, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - "win_rate_1d": 100.0, - "win_rate_7d": 100.0 - }, - "Momentum/Hype / Short Squeeze": { - "count": 3, - "wins_1d": 3, - "losses_1d": 0, - "wins_7d": 3, - "losses_7d": 0, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_1d": 0, - "avg_return_7d": 0, + "avg_return_1d": 6.44, + "avg_return_7d": 6.44, "avg_return_30d": 0, "win_rate_1d": 100.0, "win_rate_7d": 100.0 @@ -301,7 +204,7 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, + "avg_return_1d": -3.38, "avg_return_7d": 0, "avg_return_30d": 0, "win_rate_1d": 50.0 @@ -314,7 +217,7 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, + "avg_return_1d": 0.93, "avg_return_7d": 0, "avg_return_30d": 0, "win_rate_1d": 50.0 @@ -327,7 +230,7 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, + "avg_return_1d": -5.11, "avg_return_7d": 0, "avg_return_30d": 0, "win_rate_1d": 0.0 @@ -340,7 +243,7 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, + "avg_return_1d": -1.47, "avg_return_7d": 0, "avg_return_30d": 0, "win_rate_1d": 50.0 @@ -353,7 +256,7 @@ "losses_7d": 0, "wins_30d": 0, "losses_30d": 0, - "avg_return_1d": 0, + "avg_return_1d": 1.36, "avg_return_7d": 0, "avg_return_30d": 0, "win_rate_1d": 100.0 @@ -361,15 +264,15 @@ }, "overall_1d": { "count": 170, - "wins": 100, - "avg_return": 0.95, - "win_rate": 58.8 + "wins": 94, + "avg_return": 0.26, + "win_rate": 55.3 }, "overall_7d": { "count": 110, - "wins": 58, - "avg_return": 0.45, - "win_rate": 52.7 + "wins": 56, + "avg_return": -0.18, + "win_rate": 50.9 }, "overall_30d": { "count": 0, diff --git a/scripts/build_historical_memories.py b/scripts/build_historical_memories.py index e91b6e49..d1eb40e0 100644 --- a/scripts/build_historical_memories.py +++ b/scripts/build_historical_memories.py @@ -27,11 +27,13 @@ logger = get_logger(__name__) def main(): - logger.info(""" + logger.info( + """ ╔══════════════════════════════════════════════════════════════╗ ║ TradingAgents - Historical Memory Builder ║ ╚══════════════════════════════════════════════════════════════╝ - """) + """ + ) # Configuration tickers = [ diff --git a/scripts/build_strategy_specific_memories.py b/scripts/build_strategy_specific_memories.py index 2849367c..d91dd050 100644 --- a/scripts/build_strategy_specific_memories.py +++ b/scripts/build_strategy_specific_memories.py @@ -89,7 +89,8 @@ def build_strategy_memories(strategy_name: str, config: dict): strategy = STRATEGIES[strategy_name] - logger.info(f""" + logger.info( + f""" ╔══════════════════════════════════════════════════════════════╗ ║ Building Memories: {strategy_name.upper().replace('_', ' ')} ╚══════════════════════════════════════════════════════════════╝ @@ -98,7 +99,8 @@ Strategy: {strategy['description']} Lookforward: {strategy['lookforward_days']} days Sampling: Every {strategy['interval_days']} days Tickers: {', '.join(strategy['tickers'])} - """) + """ + ) # Date range - last 2 years end_date = datetime.now() @@ -157,7 +159,8 @@ Tickers: {', '.join(strategy['tickers'])} def main(): - logger.info(""" + logger.info( + """ ╔══════════════════════════════════════════════════════════════╗ ║ TradingAgents - Strategy-Specific Memory Builder ║ ╚══════════════════════════════════════════════════════════════╝ @@ -168,7 +171,8 @@ This script builds optimized memories for different trading styles: 2. Swing Trading - 7-day returns, weekly samples 3. Position Trading - 30-day returns, monthly samples 4. Long-term - 90-day returns, quarterly samples - """) + """ + ) logger.info("Available strategies:") for i, (name, config) in enumerate(STRATEGIES.items(), 1): @@ -216,11 +220,13 @@ This script builds optimized memories for different trading styles: logger.info("\n" + "=" * 70) logger.info("\n💡 TIP: To use a specific strategy's memories, update your config:") - logger.info(""" + logger.info( + """ config = DEFAULT_CONFIG.copy() config["memory_dir"] = "data/memories/swing_trading" # or your strategy config["load_historical_memories"] = True - """) + """ + ) if __name__ == "__main__": diff --git a/scripts/track_recommendation_performance.py b/scripts/track_recommendation_performance.py index 098faf27..dcf349f1 100644 --- a/scripts/track_recommendation_performance.py +++ b/scripts/track_recommendation_performance.py @@ -29,30 +29,73 @@ logger = get_logger(__name__) def load_recommendations() -> List[Dict[str, Any]]: - """Load all historical recommendations from the recommendations directory.""" + """Load all historical recommendations, preferring the performance database. + + The performance database preserves accumulated return data (return_1d, + return_7d, win_1d, etc.) across runs. Raw date files are only used to + pick up new recommendations not yet in the database. + """ recommendations_dir = "data/recommendations" if not os.path.exists(recommendations_dir): logger.warning(f"No recommendations directory found at {recommendations_dir}") return [] - all_recs = [] - pattern = os.path.join(recommendations_dir, "*.json") + # Step 1: Load existing accumulated data from the performance database + existing: Dict[str, Dict[str, Any]] = {} + db_path = os.path.join(recommendations_dir, "performance_database.json") + if os.path.exists(db_path): + try: + with open(db_path, "r") as f: + db = json.load(f) + for recs in db.get("recommendations_by_date", {}).values(): + if isinstance(recs, list): + for rec in recs: + key = f"{rec.get('ticker')}|{rec.get('discovery_date')}" + existing[key] = rec + logger.info(f"Loaded {len(existing)} records from performance database") + except Exception as e: + logger.error(f"Error loading performance database: {e}") - for filepath in glob.glob(pattern): + # Step 2: Scan raw date files for any new recommendations + new_count = 0 + for filepath in glob.glob(os.path.join(recommendations_dir, "*.json")): + basename = os.path.basename(filepath) + if basename in ("performance_database.json", "statistics.json"): + continue try: with open(filepath, "r") as f: data = json.load(f) - # Each file contains recommendations from one discovery run - recs = data.get("recommendations", []) - run_date = data.get("date", os.path.basename(filepath).replace(".json", "")) - - for rec in recs: - rec["discovery_date"] = run_date - all_recs.append(rec) + recs = data.get("recommendations", []) + run_date = data.get("date", basename.replace(".json", "")) + for rec in recs: + rec["discovery_date"] = run_date + key = f"{rec.get('ticker')}|{run_date}" + if key not in existing: + existing[key] = rec + new_count += 1 except Exception as e: logger.error(f"Error loading {filepath}: {e}") - return all_recs + if new_count: + logger.info(f"Merged {new_count} new recommendations from raw files") + + return list(existing.values()) + + +def _parse_price(raw) -> float | None: + """Extract a numeric price from get_stock_price output. + + The function may return a float directly or a markdown string like + "**Current Price**: $123.45". Handle both cases. + """ + if raw is None: + return None + if isinstance(raw, (int, float)): + return float(raw) + import re + + m = re.search(r"\$([0-9,.]+)", str(raw)) + return float(m.group(1).replace(",", "")) if m else None def update_performance(recommendations: List[Dict[str, Any]]) -> List[Dict[str, Any]]: @@ -67,52 +110,37 @@ def update_performance(recommendations: List[Dict[str, Any]]) -> List[Dict[str, if not all([ticker, discovery_date, entry_price]): continue - # Skip if already marked as closed if rec.get("status") == "closed": continue try: - # Get current price - current_price_data = get_stock_price(ticker, curr_date=today) - - # Parse the price from the response (it returns a markdown report) - # Format is typically: "**Current Price**: $XXX.XX" - import re - - price_match = re.search(r"\$([0-9,.]+)", current_price_data) - if price_match: - current_price = float(price_match.group(1).replace(",", "")) - else: - logger.warning(f"Could not parse price for {ticker}") + current_price = _parse_price(get_stock_price(ticker, curr_date=today)) + if current_price is None: + logger.warning(f"Could not get price for {ticker}") continue - # Calculate days since recommendation rec_date = datetime.strptime(discovery_date, "%Y-%m-%d") days_held = (datetime.now() - rec_date).days - - # Calculate return return_pct = ((current_price - entry_price) / entry_price) * 100 - # Update metrics rec["current_price"] = current_price rec["return_pct"] = round(return_pct, 2) rec["days_held"] = days_held rec["last_updated"] = today - # Check specific time periods + # Capture milestone returns (only once per milestone) + if days_held >= 1 and "return_1d" not in rec: + rec["return_1d"] = round(return_pct, 2) + rec["win_1d"] = return_pct > 0 + if days_held >= 7 and "return_7d" not in rec: rec["return_7d"] = round(return_pct, 2) + rec["win_7d"] = return_pct > 0 if days_held >= 30 and "return_30d" not in rec: rec["return_30d"] = round(return_pct, 2) - rec["status"] = "closed" # Mark as complete after 30 days - - # Determine win/loss for completed periods - if "return_7d" in rec: - rec["win_7d"] = rec["return_7d"] > 0 - - if "return_30d" in rec: - rec["win_30d"] = rec["return_30d"] > 0 + rec["win_30d"] = return_pct > 0 + rec["status"] = "closed" logger.info( f"✓ {ticker}: Entry ${entry_price:.2f} → Current ${current_price:.2f} ({return_pct:+.1f}%) [{days_held}d]" @@ -149,80 +177,15 @@ def save_performance_database(recommendations: List[Dict[str, Any]]): def calculate_statistics(recommendations: List[Dict[str, Any]]) -> Dict[str, Any]: - """Calculate aggregate statistics from historical performance.""" - stats = { - "total_recommendations": len(recommendations), - "by_strategy": {}, - "overall_7d": {"count": 0, "wins": 0, "avg_return": 0}, - "overall_30d": {"count": 0, "wins": 0, "avg_return": 0}, - } + """Calculate aggregate statistics from historical performance. - # Calculate by strategy - for rec in recommendations: - strategy = rec.get("strategy_match", "unknown") + Delegates to DiscoveryAnalytics.calculate_statistics so there is a single + source of truth for strategy normalization and metric calculation. + """ + from tradingagents.dataflows.discovery.analytics import DiscoveryAnalytics - if strategy not in stats["by_strategy"]: - stats["by_strategy"][strategy] = { - "count": 0, - "wins_7d": 0, - "losses_7d": 0, - "wins_30d": 0, - "losses_30d": 0, - "avg_return_7d": 0, - "avg_return_30d": 0, - } - - stats["by_strategy"][strategy]["count"] += 1 - - # 7-day stats - if "return_7d" in rec: - stats["overall_7d"]["count"] += 1 - if rec.get("win_7d"): - stats["overall_7d"]["wins"] += 1 - stats["by_strategy"][strategy]["wins_7d"] += 1 - else: - stats["by_strategy"][strategy]["losses_7d"] += 1 - stats["overall_7d"]["avg_return"] += rec["return_7d"] - - # 30-day stats - if "return_30d" in rec: - stats["overall_30d"]["count"] += 1 - if rec.get("win_30d"): - stats["overall_30d"]["wins"] += 1 - stats["by_strategy"][strategy]["wins_30d"] += 1 - else: - stats["by_strategy"][strategy]["losses_30d"] += 1 - stats["overall_30d"]["avg_return"] += rec["return_30d"] - - # Calculate averages and win rates - if stats["overall_7d"]["count"] > 0: - stats["overall_7d"]["win_rate"] = round( - (stats["overall_7d"]["wins"] / stats["overall_7d"]["count"]) * 100, 1 - ) - stats["overall_7d"]["avg_return"] = round( - stats["overall_7d"]["avg_return"] / stats["overall_7d"]["count"], 2 - ) - - if stats["overall_30d"]["count"] > 0: - stats["overall_30d"]["win_rate"] = round( - (stats["overall_30d"]["wins"] / stats["overall_30d"]["count"]) * 100, 1 - ) - stats["overall_30d"]["avg_return"] = round( - stats["overall_30d"]["avg_return"] / stats["overall_30d"]["count"], 2 - ) - - # Calculate per-strategy stats - for strategy, data in stats["by_strategy"].items(): - total_7d = data["wins_7d"] + data["losses_7d"] - total_30d = data["wins_30d"] + data["losses_30d"] - - if total_7d > 0: - data["win_rate_7d"] = round((data["wins_7d"] / total_7d) * 100, 1) - - if total_30d > 0: - data["win_rate_30d"] = round((data["wins_30d"] / total_30d) * 100, 1) - - return stats + analytics = DiscoveryAnalytics() + return analytics.calculate_statistics(recommendations) def print_statistics(stats: Dict[str, Any]): diff --git a/scripts/update_positions.py b/scripts/update_positions.py index 7bb99b60..d727d143 100755 --- a/scripts/update_positions.py +++ b/scripts/update_positions.py @@ -129,10 +129,12 @@ def main(): 6. Save updated positions 7. Print progress messages """ - logger.info(""" + logger.info( + """ ╔══════════════════════════════════════════════════════════════╗ ║ TradingAgents - Position Updater ║ -╚══════════════════════════════════════════════════════════════╝""".strip()) +╚══════════════════════════════════════════════════════════════╝""".strip() + ) # Initialize position tracker tracker = PositionTracker(data_dir="data") diff --git a/tradingagents/dataflows/discovery/analytics.py b/tradingagents/dataflows/discovery/analytics.py index 47c2dc55..33bb27f9 100644 --- a/tradingagents/dataflows/discovery/analytics.py +++ b/tradingagents/dataflows/discovery/analytics.py @@ -20,24 +20,40 @@ class DiscoveryAnalytics: self.recommendations_dir = self.data_dir / "recommendations" self.recommendations_dir.mkdir(parents=True, exist_ok=True) - def update_performance_tracking(self): - """Update performance metrics for all open recommendations.""" - logger.info("📊 Updating recommendation performance tracking...") + def _load_existing_database(self) -> Dict[str, Dict]: + """Load existing performance database keyed by (ticker, discovery_date). - if not self.recommendations_dir.exists(): - logger.info("No historical recommendations to track yet.") - return + Returns a dict mapping "TICKER|DATE" -> rec dict, preserving accumulated + return data (return_1d, return_7d, etc.) across runs. + """ + db_path = self.recommendations_dir / "performance_database.json" + if not db_path.exists(): + return {} - # Load all recommendations + try: + with open(db_path, "r") as f: + data = json.load(f) + except Exception as e: + logger.warning(f"Error loading performance database: {e}") + return {} + + existing = {} + by_date = data.get("recommendations_by_date", {}) + for recs in by_date.values(): + if isinstance(recs, list): + for rec in recs: + key = f"{rec.get('ticker')}|{rec.get('discovery_date')}" + existing[key] = rec + return existing + + def _load_raw_recommendations(self) -> List[Dict]: + """Load recommendations from raw date files.""" all_recs = [] - # Use glob directly on the path object if python 3.10+ otherwise str() pattern = str(self.recommendations_dir / "*.json") for filepath in glob.glob(pattern): - # Skip the database and stats files if "performance_database" in filepath or "statistics" in filepath: continue - try: with open(filepath, "r") as f: data = json.load(f) @@ -49,16 +65,46 @@ class DiscoveryAnalytics: all_recs.append(rec) except Exception as e: logger.warning(f"Error loading {filepath}: {e}") + return all_recs - if not all_recs: + def update_performance_tracking(self): + """Update performance metrics for all recommendations. + + Loads accumulated data from performance_database.json first, merges in + any new recs from raw date files, then updates prices for open positions. + This preserves return_1d/return_7d/return_30d across runs. + """ + logger.info("📊 Updating recommendation performance tracking...") + + if not self.recommendations_dir.exists(): + logger.info("No historical recommendations to track yet.") + return + + # Step 1: Load existing database (preserves accumulated return data) + existing = self._load_existing_database() + logger.info(f"Loaded {len(existing)} existing records from performance database") + + # Step 2: Load raw recommendation files and merge new ones + raw_recs = self._load_raw_recommendations() + new_count = 0 + for rec in raw_recs: + key = f"{rec.get('ticker')}|{rec.get('discovery_date')}" + if key not in existing: + existing[key] = rec + new_count += 1 + + if not existing: logger.info("No recommendations found to track.") return - # Filter to only track open positions + if new_count > 0: + logger.info(f"Added {new_count} new recommendations") + + all_recs = list(existing.values()) open_recs = [r for r in all_recs if r.get("status") != "closed"] logger.info(f"Tracking {len(open_recs)} open positions (out of {len(all_recs)} total)...") - # Update performance + # Step 3: Update prices for open positions today = datetime.now().strftime("%Y-%m-%d") updated_count = 0 @@ -67,13 +113,10 @@ class DiscoveryAnalytics: discovery_date = rec.get("discovery_date") entry_price = rec.get("entry_price") - # Skip if already closed or missing data if rec.get("status") == "closed" or not all([ticker, discovery_date, entry_price]): continue try: - # Get current price - # We interpret this import here to avoid circular dependency if this class is imported early from tradingagents.dataflows.y_finance import get_stock_price current_price = get_stock_price(ticker, curr_date=today) @@ -81,18 +124,16 @@ class DiscoveryAnalytics: if current_price is None: continue - # Calculate metrics rec_date = datetime.strptime(discovery_date, "%Y-%m-%d") days_held = (datetime.now() - rec_date).days return_pct = ((current_price - entry_price) / entry_price) * 100 - # Update rec["current_price"] = current_price rec["return_pct"] = round(return_pct, 2) rec["days_held"] = days_held rec["last_updated"] = today - # Capture specific time periods (1d, 7d, 30d) + # Capture milestone returns (only once, at the first eligible run) if days_held >= 1 and "return_1d" not in rec: rec["return_1d"] = round(return_pct, 2) rec["win_1d"] = return_pct > 0 @@ -109,11 +150,11 @@ class DiscoveryAnalytics: updated_count += 1 except Exception: - # Silently skip errors to not interrupt discovery pass - if updated_count > 0: - logger.info(f"Updated {updated_count} positions") + # Step 4: Always save — even if no price updates, the merge may have added new recs + if updated_count > 0 or new_count > 0: + logger.info(f"Updated {updated_count} positions, {new_count} new recs") self._save_performance_db(all_recs) else: logger.info("No updates needed") @@ -148,6 +189,35 @@ class DiscoveryAnalytics: logger.info("💾 Updated performance database and statistics") + @staticmethod + def _normalize_strategy(name: str) -> str: + """Normalize strategy names to snake_case canonical form. + + Merges duplicates like 'Momentum' / 'momentum', 'Insider Play' / 'insider_buying'. + """ + import re + + if not name: + return "unknown" + + # Lowercase and replace separators with underscore + normalized = name.strip().lower() + normalized = re.sub(r"[\s/]+", "_", normalized) + # Collapse multiple underscores + normalized = re.sub(r"_+", "_", normalized).strip("_") + + # Map known aliases to canonical names + aliases = { + "insider_play": "insider_buying", + "earnings_play": "earnings_calendar", + "contrarian_value": "contrarian_value", + "news_catalyst": "news_catalyst", + "volume_accumulation": "volume_accumulation", + "momentum_hype": "momentum", + "momentum_hype_short_squeeze": "short_squeeze", + } + return aliases.get(normalized, normalized) + def calculate_statistics(self, recommendations: list) -> dict: """Calculate aggregate statistics from historical performance.""" stats = { @@ -158,12 +228,9 @@ class DiscoveryAnalytics: "overall_30d": {"count": 0, "wins": 0, "avg_return": 0}, } - # Calculate by strategy - for rec in recommendations: - strategy = rec.get("strategy_match", "unknown") - - if strategy not in stats["by_strategy"]: - stats["by_strategy"][strategy] = { + def _get_strategy_bucket(strategy_name): + if strategy_name not in stats["by_strategy"]: + stats["by_strategy"][strategy_name] = { "count": 0, "wins_1d": 0, "losses_1d": 0, @@ -175,45 +242,53 @@ class DiscoveryAnalytics: "avg_return_7d": 0, "avg_return_30d": 0, } + return stats["by_strategy"][strategy_name] - stats["by_strategy"][strategy]["count"] += 1 + # Calculate by strategy + for rec in recommendations: + strategy = self._normalize_strategy(rec.get("strategy_match", "unknown")) + bucket = _get_strategy_bucket(strategy) + bucket["count"] += 1 # 1-day stats if "return_1d" in rec: stats["overall_1d"]["count"] += 1 + bucket["avg_return_1d"] += rec["return_1d"] if rec.get("win_1d"): stats["overall_1d"]["wins"] += 1 - stats["by_strategy"][strategy]["wins_1d"] += 1 + bucket["wins_1d"] += 1 else: - stats["by_strategy"][strategy]["losses_1d"] += 1 + bucket["losses_1d"] += 1 stats["overall_1d"]["avg_return"] += rec["return_1d"] # 7-day stats if "return_7d" in rec: stats["overall_7d"]["count"] += 1 + bucket["avg_return_7d"] += rec["return_7d"] if rec.get("win_7d"): stats["overall_7d"]["wins"] += 1 - stats["by_strategy"][strategy]["wins_7d"] += 1 + bucket["wins_7d"] += 1 else: - stats["by_strategy"][strategy]["losses_7d"] += 1 + bucket["losses_7d"] += 1 stats["overall_7d"]["avg_return"] += rec["return_7d"] # 30-day stats if "return_30d" in rec: stats["overall_30d"]["count"] += 1 + bucket["avg_return_30d"] += rec["return_30d"] if rec.get("win_30d"): stats["overall_30d"]["wins"] += 1 - stats["by_strategy"][strategy]["wins_30d"] += 1 + bucket["wins_30d"] += 1 else: - stats["by_strategy"][strategy]["losses_30d"] += 1 + bucket["losses_30d"] += 1 stats["overall_30d"]["avg_return"] += rec["return_30d"] - # Calculate averages and win rates + # Calculate overall averages and win rates self._calculate_metric_averages(stats["overall_1d"]) self._calculate_metric_averages(stats["overall_7d"]) self._calculate_metric_averages(stats["overall_30d"]) - # Calculate per-strategy stats + # Calculate per-strategy win rates and avg returns for strategy, data in stats["by_strategy"].items(): total_1d = data["wins_1d"] + data["losses_1d"] total_7d = data["wins_7d"] + data["losses_7d"] @@ -221,12 +296,15 @@ class DiscoveryAnalytics: if total_1d > 0: data["win_rate_1d"] = round((data["wins_1d"] / total_1d) * 100, 1) + data["avg_return_1d"] = round(data["avg_return_1d"] / total_1d, 2) if total_7d > 0: data["win_rate_7d"] = round((data["wins_7d"] / total_7d) * 100, 1) + data["avg_return_7d"] = round(data["avg_return_7d"] / total_7d, 2) if total_30d > 0: data["win_rate_30d"] = round((data["wins_30d"] / total_30d) * 100, 1) + data["avg_return_30d"] = round(data["avg_return_30d"] / total_30d, 2) return stats diff --git a/tradingagents/ui/dashboard.py b/tradingagents/ui/dashboard.py index 82b0744a..ffedf114 100644 --- a/tradingagents/ui/dashboard.py +++ b/tradingagents/ui/dashboard.py @@ -1,21 +1,23 @@ """ Main Streamlit app entry point for the Trading Agents Dashboard. -This module sets up the dashboard page configuration, sidebar navigation, -and routing to different pages based on user selection. +Dark terminal-inspired trading interface with sidebar navigation. """ +from datetime import datetime + import streamlit as st from tradingagents.ui import pages +from tradingagents.ui.theme import COLORS, GLOBAL_CSS from tradingagents.ui.utils import load_quick_stats def setup_page_config(): """Configure the Streamlit page settings.""" st.set_page_config( - page_title="Trading Agents Dashboard", - page_icon="📊", + page_title="Trading Agents", + page_icon="", layout="wide", initial_sidebar_state="expanded", ) @@ -24,46 +26,101 @@ def setup_page_config(): def render_sidebar(): """Render the sidebar with navigation and quick stats.""" with st.sidebar: - st.title("Trading Agents") + # Brand header + st.markdown( + f""" +
+
+ TRADINGAGENTS +
+
+ v2.0 — {datetime.now().strftime('%b %d, %Y')} +
+
+ """, + unsafe_allow_html=True, + ) + + st.markdown( + f"""
""", + unsafe_allow_html=True, + ) # Navigation - st.markdown("### Navigation") page = st.radio( - "Select a page:", - options=["Home", "Today's Picks", "Portfolio", "Performance", "Settings"], + "Navigation", + options=["Overview", "Signals", "Portfolio", "Performance", "Config"], label_visibility="collapsed", ) - st.markdown("---") + st.markdown( + f"""
""", + unsafe_allow_html=True, + ) - # Quick stats section - st.markdown("### Quick Stats") + # Quick stats try: open_positions, win_rate = load_quick_stats() - - col1, col2 = st.columns(2) - with col1: - st.metric("Open Positions", open_positions) - with col2: - st.metric("Win Rate", f"{win_rate:.1f}%") - except Exception as e: - st.warning(f"Could not load quick stats: {str(e)}") + st.markdown( + f""" +
+
+ Quick Stats +
+
+
+
+ {open_positions} +
+
+ Open +
+
+
+
+ {win_rate:.0f}% +
+
+ Win Rate +
+
+
+
+ """, + unsafe_allow_html=True, + ) + except Exception: + pass return page def route_page(page): """Route to the appropriate page based on selection.""" - if page == "Home": - pages.home.render() - elif page == "Today's Picks": - pages.todays_picks.render() - elif page == "Portfolio": - pages.portfolio.render() - elif page == "Performance": - pages.performance.render() - elif page == "Settings": - pages.settings.render() + page_map = { + "Overview": pages.home, + "Signals": pages.todays_picks, + "Portfolio": pages.portfolio, + "Performance": pages.performance, + "Config": pages.settings, + } + module = page_map.get(page) + if module: + module.render() else: st.error(f"Unknown page: {page}") @@ -72,17 +129,8 @@ def main(): """Main entry point for the Streamlit app.""" setup_page_config() - # Custom CSS for better styling - st.markdown( - """ - - """, - unsafe_allow_html=True, - ) + # Inject global theme CSS + st.markdown(GLOBAL_CSS, unsafe_allow_html=True) # Render sidebar and get selected page selected_page = render_sidebar() diff --git a/tradingagents/ui/pages/home.py b/tradingagents/ui/pages/home.py index 928423e7..1f88f180 100644 --- a/tradingagents/ui/pages/home.py +++ b/tradingagents/ui/pages/home.py @@ -1,133 +1,180 @@ """ -Home page for the Trading Agents Dashboard. +Overview page — trading terminal home screen. -This module displays the main dashboard with overview metrics and -pipeline performance visualization. +Shows KPI cards, strategy scatter plot, and recent signal summary. """ +from datetime import datetime + import pandas as pd import plotly.express as px import streamlit as st -from tradingagents.ui.utils import load_open_positions, load_statistics, load_strategy_metrics +from tradingagents.ui.theme import COLORS, get_plotly_template, kpi_card, page_header +from tradingagents.ui.utils import ( + load_open_positions, + load_recommendations, + load_statistics, + load_strategy_metrics, +) def render() -> None: - """ - Render the home page with overview metrics and pipeline performance. + """Render the overview page.""" - Displays: - - Dashboard title - - Warning if no statistics available - - 4-column metric layout (Win Rate, Open Positions, Avg Return, Best Pipeline) - - Pipeline performance scatter plot with quadrant lines - """ - # Page title - st.title("🎯 Trading Discovery Dashboard") + st.markdown( + page_header("Overview", f"Market session {datetime.now().strftime('%A, %B %d %Y')}"), + unsafe_allow_html=True, + ) - # Load data stats = load_statistics() positions = load_open_positions() strategy_metrics = load_strategy_metrics() - # Check if statistics are available - if not stats or not stats.get("overall_7d"): - st.warning("No statistics data available. Run the discovery pipeline to generate data.") - return + overall = stats.get("overall_7d", {}) if stats else {} + win_rate_7d = overall.get("win_rate", 0) + avg_return_7d = overall.get("avg_return", 0) + total_recs = stats.get("total_recommendations", 0) if stats else 0 + open_count = len(positions) if positions else 0 - if not strategy_metrics: - st.warning("No strategy performance data available yet.") - return + best_strat_name = "N/A" + best_strat_wr = 0.0 + for item in (strategy_metrics or []): + wr = item.get("Win Rate", 0) or 0 + if wr > best_strat_wr: + best_strat_wr = wr + best_strat_name = item.get("Strategy", "unknown") - # Extract overall metrics from 7-day period - overall_metrics = stats.get("overall_7d", {}) - win_rate_7d = overall_metrics.get("win_rate", 0) - avg_return_7d = overall_metrics.get("avg_return", 0) - open_positions_count = len(positions) if positions else 0 + # ---- KPI Row ---- + cols = st.columns(5) + kpis = [ + ("Win Rate 7d", f"{win_rate_7d:.0f}%", f"+{win_rate_7d - 50:.0f}pp vs 50%" if win_rate_7d >= 50 else f"{win_rate_7d - 50:.0f}pp vs 50%", "green" if win_rate_7d >= 50 else "red"), + ("Avg Return 7d", f"{avg_return_7d:+.2f}%", "", "green" if avg_return_7d > 0 else "red"), + ("Open Positions", str(open_count), "", "blue"), + ("Total Signals", str(total_recs), "", "amber"), + ("Top Strategy", best_strat_name.upper(), f"{best_strat_wr:.0f}% WR" if best_strat_wr else "", "green" if best_strat_wr >= 60 else "amber"), + ] + for col, (label, value, delta, color) in zip(cols, kpis): + with col: + st.markdown(kpi_card(label, value, delta, color), unsafe_allow_html=True) - # Find best strategy - best_strategy = None - best_win_rate = 0.0 - for item in strategy_metrics: - win_rate = item.get("Win Rate", 0) or 0 - if win_rate > best_win_rate: - best_win_rate = win_rate - best_strategy = {"name": item.get("Strategy", "unknown"), "win_rate": win_rate} + st.markdown("
", unsafe_allow_html=True) - # Display 4-column metric layout - col1, col2, col3, col4 = st.columns(4) + # ---- Two-column: strategy chart + today's signals ---- + left_col, right_col = st.columns([3, 2]) - with col1: - st.metric( - label="Win Rate (7d)", - value=f"{win_rate_7d:.1f}%", - delta=f"{win_rate_7d - 50:.1f}%" if win_rate_7d >= 50 else None, + with left_col: + st.markdown( + '
Strategy Performance // scatter
', + unsafe_allow_html=True, ) - with col2: - st.metric( - label="Open Positions", - value=open_positions_count, - ) + if strategy_metrics: + df = pd.DataFrame(strategy_metrics) + template = get_plotly_template() - with col3: - st.metric( - label="Avg Return (7d)", - value=f"{avg_return_7d:.2f}%", - delta=f"{avg_return_7d:.2f}%" if avg_return_7d > 0 else None, - ) - - with col4: - if best_strategy: - st.metric( - label="Best Strategy", - value=best_strategy["name"], - delta=f"{best_strategy['win_rate']:.1f}% WR", + fig = px.scatter( + df, + x="Win Rate", + y="Avg Return", + size="Count", + color="Strategy", + hover_name="Strategy", + hover_data={"Win Rate": ":.1f", "Avg Return": ":.2f", "Count": True, "Strategy": False}, + labels={"Win Rate": "Win Rate (%)", "Avg Return": "Avg Return (%)"}, + size_max=40, ) + + fig.add_hline(y=0, line_dash="dot", line_color=COLORS["text_muted"], opacity=0.4) + fig.add_vline(x=50, line_dash="dot", line_color=COLORS["text_muted"], opacity=0.4) + fig.add_annotation(x=75, y=5, text="WINNERS", showarrow=False, font=dict(size=10, color=COLORS["green"], family="JetBrains Mono"), opacity=0.3) + fig.add_annotation(x=25, y=-5, text="LOSERS", showarrow=False, font=dict(size=10, color=COLORS["red"], family="JetBrains Mono"), opacity=0.3) + + fig.update_layout( + **template, + height=380, + showlegend=True, + legend=dict(bgcolor="rgba(0,0,0,0)", font=dict(size=10), orientation="h", yanchor="bottom", y=-0.25), + ) + st.plotly_chart(fig, width="stretch") else: - st.metric( - label="Best Strategy", - value="N/A", - ) + st.info("Run the discovery pipeline to generate strategy data.") - # Strategy Performance scatter plot - st.subheader("Strategy Performance") + with right_col: + st.markdown( + '
Today\'s Signals // latest
', + unsafe_allow_html=True, + ) + today = datetime.now().strftime("%Y-%m-%d") + recs = load_recommendations(today) + + if recs: + for rec in recs[:6]: + ticker = rec.get("ticker", "???") + score = rec.get("final_score", 0) + conf = rec.get("confidence", 0) + strat = (rec.get("strategy_match") or "momentum").upper() + entry = rec.get("entry_price") + entry_str = f"${entry:.2f}" if entry else "N/A" + + score_color = COLORS["green"] if score >= 35 else (COLORS["amber"] if score >= 20 else COLORS["text_muted"]) + conf_bar_w = conf * 10 + conf_color = COLORS["green"] if conf >= 8 else (COLORS["amber"] if conf >= 6 else COLORS["red"]) + + st.markdown( + f""" +
+
+
+ {ticker} + {strat} +
+ {score} +
+
+ {entry_str} +
+
+
+
+
+ """, + unsafe_allow_html=True, + ) + + if len(recs) > 6: + st.caption(f"+{len(recs) - 6} more signals. Switch to Signals page for the full list.") + else: + st.info("No signals generated today.") + + # ---- Strategy table ---- if strategy_metrics: - df = pd.DataFrame(strategy_metrics) - - # Create scatter plot with plotly - fig = px.scatter( - df, - x="Win Rate", - y="Avg Return", - size="Count", - color="Strategy", - hover_name="Strategy", - hover_data={ - "Win Rate": ":.1f", - "Avg Return": ":.2f", - "Count": True, - "Strategy": False, - }, - title="Strategy Performance Analysis", - labels={ - "Win Rate": "Win Rate (%)", - "Avg Return": "Avg Return (%)", + st.markdown("
", unsafe_allow_html=True) + st.markdown( + '
Strategy Breakdown // table
', + unsafe_allow_html=True, + ) + df_table = pd.DataFrame(strategy_metrics).sort_values("Win Rate", ascending=False) + st.dataframe( + df_table, + width="stretch", + hide_index=True, + column_config={ + "Win Rate": st.column_config.NumberColumn(format="%.1f%%"), + "Avg Return": st.column_config.NumberColumn(format="%+.2f%%"), + "Count": st.column_config.NumberColumn(format="%d"), }, ) - - # Add quadrant lines at y=0 (breakeven) and x=50 (50% win rate) - fig.add_hline(y=0, line_dash="dash", line_color="gray", opacity=0.5) - fig.add_vline(x=50, line_dash="dash", line_color="gray", opacity=0.5) - - # Update layout for better visibility - fig.update_layout( - height=400, - showlegend=True, - hovermode="closest", - ) - - st.plotly_chart(fig, use_container_width=True) - else: - st.info("No strategy data available for visualization.") diff --git a/tradingagents/ui/pages/performance.py b/tradingagents/ui/pages/performance.py index ef6c6bb2..f5ae1f20 100644 --- a/tradingagents/ui/pages/performance.py +++ b/tradingagents/ui/pages/performance.py @@ -1,50 +1,73 @@ """ -Performance analytics page for the Trading Agents Dashboard. +Performance analytics page — strategy comparison and win/loss analysis. -This module displays performance metrics and visualization for different scanners, -including win rates, average returns, and trading volume analysis. +Shows strategy scatter plot with themed Plotly charts, per-strategy +breakdown table, and win rate distribution. """ import pandas as pd import plotly.express as px +import plotly.graph_objects as go import streamlit as st -from tradingagents.ui.utils import load_strategy_metrics +from tradingagents.ui.theme import COLORS, get_plotly_template, page_header +from tradingagents.ui.utils import load_performance_database, load_statistics, load_strategy_metrics def render() -> None: - """ - Render the performance analytics page. + """Render the performance analytics page.""" + st.markdown(page_header("Performance", "Strategy analytics & win/loss breakdown"), unsafe_allow_html=True) - Displays: - - Page title - - Warning if no statistics available - - Scanner Performance heatmap with scatter plot showing: - - Win Rate (x-axis) vs Avg Return (y-axis) - - Bubble size = Trade count - - Color = Win Rate (RdYlGn colorscale) - - Quadrant lines at y=0 and x=50 - """ - # Page title - st.title("📊 Performance Analytics") - - # Load data strategy_metrics = load_strategy_metrics() + stats = load_statistics() - # Check if data is available if not strategy_metrics: - st.warning( - "No strategy performance data available. Run performance tracking to generate data." - ) + st.warning("No performance data available yet. Run the discovery pipeline and track outcomes.") return - # Strategy Performance section - st.subheader("Strategy Performance") + template = get_plotly_template() + df = pd.DataFrame(strategy_metrics) - if strategy_metrics: - df = pd.DataFrame(strategy_metrics) + # ---- Summary KPIs ---- + total_trades = df["Count"].sum() + avg_wr = (df["Win Rate"] * df["Count"]).sum() / total_trades if total_trades > 0 else 0 + avg_ret = (df["Avg Return"] * df["Count"]).sum() / total_trades if total_trades > 0 else 0 + n_strategies = len(df) + + cols = st.columns(4) + summaries = [ + ("Total Trades", str(int(total_trades))), + ("Weighted Win Rate", f"{avg_wr:.1f}%"), + ("Weighted Avg Return", f"{avg_ret:+.2f}%"), + ("Active Strategies", str(n_strategies)), + ] + for col, (label, val) in zip(cols, summaries): + with col: + st.markdown( + f""" +
+
{label}
+
{val}
+
+ """, + unsafe_allow_html=True, + ) + + st.markdown("
", unsafe_allow_html=True) + + # ---- Two-column: scatter + bar chart ---- + left_col, right_col = st.columns(2) + + with left_col: + st.markdown( + '
Win Rate vs Return // scatter
', + unsafe_allow_html=True, + ) - # Create scatter plot with plotly fig = px.scatter( df, x="Win Rate", @@ -52,31 +75,99 @@ def render() -> None: size="Count", color="Win Rate", hover_name="Strategy", - hover_data={ - "Win Rate": ":.1f", - "Avg Return": ":.2f", - "Count": True, - "Strategy": False, - }, - title="Strategy Performance Analysis", - labels={ - "Win Rate": "Win Rate (%)", - "Avg Return": "Avg Return (%)", - }, - color_continuous_scale="RdYlGn", + hover_data={"Win Rate": ":.1f", "Avg Return": ":.2f", "Count": True}, + labels={"Win Rate": "Win Rate (%)", "Avg Return": "Avg Return (%)"}, + color_continuous_scale=[ + [0, COLORS["red"]], + [0.5, COLORS["amber"]], + [1.0, COLORS["green"]], + ], + size_max=45, ) - # Add quadrant lines at y=0 (breakeven) and x=50 (50% win rate) - fig.add_hline(y=0, line_dash="dash", line_color="gray", opacity=0.5) - fig.add_vline(x=50, line_dash="dash", line_color="gray", opacity=0.5) + fig.add_hline(y=0, line_dash="dot", line_color=COLORS["text_muted"], opacity=0.4) + fig.add_vline(x=50, line_dash="dot", line_color=COLORS["text_muted"], opacity=0.4) - # Update layout for better visibility fig.update_layout( - height=500, - showlegend=True, - hovermode="closest", + **template, + height=400, + showlegend=False, + coloraxis_showscale=False, + ) + st.plotly_chart(fig, width="stretch") + + with right_col: + st.markdown( + '
Win Rate by Strategy // bar
', + unsafe_allow_html=True, ) - st.plotly_chart(fig, use_container_width=True) - else: - st.info("No strategy data available for visualization.") + df_sorted = df.sort_values("Win Rate", ascending=True) + colors = [COLORS["green"] if wr >= 50 else COLORS["red"] for wr in df_sorted["Win Rate"]] + + fig_bar = go.Figure(go.Bar( + x=df_sorted["Win Rate"], + y=df_sorted["Strategy"], + orientation="h", + marker_color=colors, + text=[f"{wr:.0f}%" for wr in df_sorted["Win Rate"]], + textposition="auto", + textfont=dict(family="JetBrains Mono", size=11, color=COLORS["text_primary"]), + )) + + fig_bar.add_vline(x=50, line_dash="dot", line_color=COLORS["text_muted"], opacity=0.5) + + fig_bar.update_layout( + **template, + height=400, + xaxis_title="Win Rate (%)", + yaxis_title="", + ) + fig_bar.update_yaxes(tickfont=dict(family="JetBrains Mono", size=11)) + st.plotly_chart(fig_bar, width="stretch") + + # ---- Strategy breakdown table ---- + st.markdown("
", unsafe_allow_html=True) + st.markdown( + '
Detailed Breakdown // table
', + unsafe_allow_html=True, + ) + + display_df = df.copy() + display_df = display_df.sort_values("Win Rate", ascending=False) + display_df["Count"] = display_df["Count"].astype(int) + st.dataframe( + display_df, + width="stretch", + hide_index=True, + column_config={ + "Win Rate": st.column_config.NumberColumn(format="%.1f%%"), + "Avg Return": st.column_config.NumberColumn(format="%+.2f%%"), + "Count": st.column_config.NumberColumn(format="%d"), + }, + ) + + # ---- Per-strategy stats from statistics.json ---- + if stats and stats.get("by_strategy"): + st.markdown("
", unsafe_allow_html=True) + st.markdown( + '
Time-Period Breakdown // 1d / 7d / 30d
', + unsafe_allow_html=True, + ) + + by_strat = stats["by_strategy"] + rows = [] + for strat_name, data in by_strat.items(): + rows.append({ + "Strategy": strat_name, + "Count": data.get("count", 0), + "Win Rate 1d": f"{data.get('win_rate_1d', 0):.0f}%" if "win_rate_1d" in data else "N/A", + "Win Rate 7d": f"{data.get('win_rate_7d', 0):.0f}%" if "win_rate_7d" in data else "N/A", + "Wins 1d": data.get("wins_1d", 0), + "Losses 1d": data.get("losses_1d", 0), + "Wins 7d": data.get("wins_7d", 0), + "Losses 7d": data.get("losses_7d", 0), + }) + + if rows: + st.dataframe(pd.DataFrame(rows), width="stretch", hide_index=True) diff --git a/tradingagents/ui/pages/portfolio.py b/tradingagents/ui/pages/portfolio.py index 5b7897f8..09655251 100644 --- a/tradingagents/ui/pages/portfolio.py +++ b/tradingagents/ui/pages/portfolio.py @@ -1,90 +1,169 @@ -"""Portfolio tracker.""" +""" +Portfolio page — position tracker with P/L visualization. + +Shows portfolio summary KPIs and individual position rows +with color-coded P/L indicators. +""" from datetime import datetime import pandas as pd import streamlit as st +from tradingagents.ui.theme import COLORS, kpi_card, page_header, pnl_color from tradingagents.ui.utils import load_open_positions def render(): - st.title("💼 Portfolio Tracker") + st.markdown(page_header("Portfolio", "Open positions & P/L tracker"), unsafe_allow_html=True) - # Manual add form - with st.expander("➕ Add Position"): + # ---- Add position form ---- + with st.expander("Add Position"): col1, col2, col3, col4 = st.columns(4) with col1: - ticker = st.text_input("Ticker") + ticker = st.text_input("Ticker", placeholder="AAPL") with col2: - entry_price = st.number_input("Entry Price", min_value=0.0) + entry_price = st.number_input("Entry Price", min_value=0.0, format="%.2f") with col3: shares = st.number_input("Shares", min_value=0, step=1) with col4: - st.write("") # Spacing - if st.button("Add"): + st.write("") + if st.button("Add Position"): if ticker and entry_price > 0 and shares > 0: - from tradingagents.dataflows.discovery.performance.position_tracker import ( - PositionTracker, - ) + from tradingagents.dataflows.discovery.performance.position_tracker import PositionTracker tracker = PositionTracker() - pos = tracker.create_position( - { - "ticker": ticker.upper(), - "entry_price": entry_price, - "shares": shares, - "recommendation_date": datetime.now().isoformat(), - "pipeline": "manual", - "scanner": "manual", - "strategy_match": "manual", - "confidence": 5, - } - ) + pos = tracker.create_position({ + "ticker": ticker.upper(), + "entry_price": entry_price, + "shares": shares, + "recommendation_date": datetime.now().isoformat(), + "pipeline": "manual", + "scanner": "manual", + "strategy_match": "manual", + "confidence": 5, + }) tracker.save_position(pos) st.success(f"Added {ticker.upper()}") st.rerun() - # Load positions positions = load_open_positions() if not positions: - st.info("No open positions") + st.markdown( + f""" +
+
No open positions
+
+ Enter positions manually above or run the discovery pipeline. +
+
+ """, + unsafe_allow_html=True, + ) return - # Summary + # ---- Portfolio summary ---- total_invested = sum(p["entry_price"] * p.get("shares", 0) for p in positions) total_current = sum(p["metrics"]["current_price"] * p.get("shares", 0) for p in positions) total_pnl = total_current - total_invested total_pnl_pct = (total_pnl / total_invested * 100) if total_invested > 0 else 0 - col1, col2, col3, col4 = st.columns(4) - with col1: - st.metric("Invested", f"${total_invested:,.2f}") - with col2: - st.metric("Current", f"${total_current:,.2f}") - with col3: - st.metric("P/L", f"${total_pnl:,.2f}", delta=f"{total_pnl_pct:+.1f}%") - with col4: - st.metric("Positions", len(positions)) + cols = st.columns(4) + summary_kpis = [ + ("Invested", f"${total_invested:,.0f}", "", "blue"), + ("Current Value", f"${total_current:,.0f}", "", "blue"), + ("P/L", f"${total_pnl:+,.0f}", f"{total_pnl_pct:+.1f}%", "green" if total_pnl >= 0 else "red"), + ("Positions", str(len(positions)), "", "amber"), + ] + for col, (label, value, delta, color) in zip(cols, summary_kpis): + with col: + st.markdown(kpi_card(label, value, delta, color), unsafe_allow_html=True) - # Table - st.subheader("📊 Positions") + st.markdown("
", unsafe_allow_html=True) + + # ---- Position cards ---- + st.markdown( + '
Open Positions // live
', + unsafe_allow_html=True, + ) - data = [] for p in positions: - pnl = (p["metrics"]["current_price"] - p["entry_price"]) * p.get("shares", 0) - data.append( - { - "Ticker": p["ticker"], - "Entry": f"${p['entry_price']:.2f}", - "Current": f"${p['metrics']['current_price']:.2f}", - "Shares": p.get("shares", 0), - "P/L": f"${pnl:.2f}", - "P/L %": f"{p['metrics']['current_return']:+.1f}%", - "Days": p["metrics"]["days_held"], - } + ticker = p["ticker"] + entry = p["entry_price"] + current = p["metrics"]["current_price"] + shares = p.get("shares", 0) + pnl_dollar = (current - entry) * shares + pnl_pct = p["metrics"]["current_return"] + days = p["metrics"]["days_held"] + color = pnl_color(pnl_pct) + + st.markdown( + f""" +
+
+
+ {ticker} + + {shares} shares · {days}d + +
+
+ + {pnl_pct:+.1f}% + + + ${pnl_dollar:+,.0f} + +
+
+
+ + Entry ${entry:.2f} + + + Current ${current:.2f} + +
+
+ """, + unsafe_allow_html=True, ) - df = pd.DataFrame(data) - st.dataframe(df, use_container_width=True) + # ---- Data table fallback ---- + with st.expander("Detailed Table"): + data = [] + for p in positions: + pnl = (p["metrics"]["current_price"] - p["entry_price"]) * p.get("shares", 0) + data.append({ + "Ticker": p["ticker"], + "Entry": p["entry_price"], + "Current": p["metrics"]["current_price"], + "Shares": p.get("shares", 0), + "P/L": pnl, + "P/L %": p["metrics"]["current_return"], + "Days": p["metrics"]["days_held"], + }) + st.dataframe( + pd.DataFrame(data), + width="stretch", + hide_index=True, + column_config={ + "Entry": st.column_config.NumberColumn(format="$%.2f"), + "Current": st.column_config.NumberColumn(format="$%.2f"), + "Shares": st.column_config.NumberColumn(format="%d"), + "P/L": st.column_config.NumberColumn(format="$%+.2f"), + "P/L %": st.column_config.NumberColumn(format="%+.1f%%"), + "Days": st.column_config.NumberColumn(format="%d"), + }, + ) diff --git a/tradingagents/ui/pages/settings.py b/tradingagents/ui/pages/settings.py index 71b5cc6d..a8c491ae 100644 --- a/tradingagents/ui/pages/settings.py +++ b/tradingagents/ui/pages/settings.py @@ -1,147 +1,155 @@ """ -Settings page for the Trading Agents Dashboard. +Config page — displays pipeline configuration in a terminal-style layout. -This module displays configuration settings and scanner/pipeline status information. -It provides a read-only view of current settings with expandable sections for detailed configuration. +Read-only view of scanners, pipelines, and data source configuration. """ import streamlit as st from tradingagents.default_config import DEFAULT_CONFIG +from tradingagents.ui.theme import COLORS, page_header def render() -> None: - """ - Render the settings page. + """Render the configuration page.""" + st.markdown(page_header("Config", "Pipeline & scanner configuration (read-only)"), unsafe_allow_html=True) - Displays: - - Page title - - Configuration info message - - Discovery configuration settings - - Pipelines section with expandable cards showing: - - enabled status - - priority - - deep_dive_budget - - Scanners section with checkboxes showing: - - enabled status for each scanner - """ - # Page title - st.title("⚙️ Settings") - - # Info message - st.info("Configuration UI - TODO: Implement save functionality") - - # Get configuration config = DEFAULT_CONFIG discovery_config = config.get("discovery", {}) - # Display current configuration section - st.subheader("📋 Configuration") + # ---- Top-level settings ---- + st.markdown( + '
Discovery Settings // core
', + unsafe_allow_html=True, + ) - # Show key discovery settings - col1, col2, col3 = st.columns(3) + settings_grid = [ + ("Discovery Mode", discovery_config.get("discovery_mode", "N/A")), + ("Max Candidates", str(discovery_config.get("max_candidates_to_analyze", "N/A"))), + ("Final Recommendations", str(discovery_config.get("final_recommendations", "N/A"))), + ("Deep Dive Workers", str(discovery_config.get("deep_dive_max_workers", "N/A"))), + ] - with col1: - st.metric( - label="Discovery Mode", - value=discovery_config.get("discovery_mode", "N/A"), + cols = st.columns(len(settings_grid)) + for col, (label, val) in zip(cols, settings_grid): + with col: + st.markdown( + f""" +
+
{label}
+
{val}
+
+ """, + unsafe_allow_html=True, + ) + + st.markdown("
", unsafe_allow_html=True) + + # ---- Pipelines ---- + left_col, right_col = st.columns(2) + + with left_col: + st.markdown( + '
Pipelines // routing
', + unsafe_allow_html=True, ) - with col2: - st.metric( - label="Max Candidates", - value=discovery_config.get("max_candidates_to_analyze", "N/A"), + pipelines = discovery_config.get("pipelines", {}) + for name, cfg in pipelines.items(): + enabled = cfg.get("enabled", False) + priority = cfg.get("priority", "N/A") + budget = cfg.get("deep_dive_budget", "N/A") + status_color = COLORS["green"] if enabled else COLORS["red"] + status_dot = f'' + + st.markdown( + f""" +
+
+ + {status_dot}{name} + +
+ P:{priority} + B:{budget} +
+
+
+ """, + unsafe_allow_html=True, + ) + + with right_col: + st.markdown( + '
Scanners // sources
', + unsafe_allow_html=True, ) - with col3: - st.metric( - label="Final Recommendations", - value=discovery_config.get("final_recommendations", "N/A"), - ) + scanners = discovery_config.get("scanners", {}) + for name, cfg in scanners.items(): + enabled = cfg.get("enabled", False) + pipeline = cfg.get("pipeline", "N/A") + limit = cfg.get("limit", "N/A") + status_color = COLORS["green"] if enabled else COLORS["red"] + status_dot = f'' - # Pipelines section - st.subheader("🔄 Pipelines") + st.markdown( + f""" +
+
+ + {status_dot}{name.replace('_', ' ')} + +
+ {pipeline} + limit:{limit} +
+
+
+ """, + unsafe_allow_html=True, + ) - pipelines = discovery_config.get("pipelines", {}) - - if pipelines: - for pipeline_name, pipeline_config in pipelines.items(): - with st.expander( - f"{'✅' if pipeline_config.get('enabled') else '❌'} {pipeline_name.title()}" - ): - col1, col2, col3 = st.columns(3) - - with col1: - st.metric( - label="Enabled", - value="Yes" if pipeline_config.get("enabled") else "No", - ) - - with col2: - st.metric( - label="Priority", - value=pipeline_config.get("priority", "N/A"), - ) - - with col3: - st.metric( - label="Budget", - value=pipeline_config.get("deep_dive_budget", "N/A"), - ) - - if "ranker_prompt" in pipeline_config: - st.caption(f"Ranker: {pipeline_config.get('ranker_prompt', 'N/A')}") - else: - st.info("No pipelines configured") - - # Scanners section - st.subheader("🔍 Scanners") - - scanners = discovery_config.get("scanners", {}) - - if scanners: - col1, col2 = st.columns([2, 1]) - - with col1: - st.write("**Scanner Status**") - - with col2: - st.write("**Enabled**") - - # Display each scanner with checkbox showing enabled status - for scanner_name, scanner_config in scanners.items(): - col1, col2 = st.columns([2, 1]) - - with col1: - st.write(f"• {scanner_name.replace('_', ' ').title()}") - - with col2: - is_enabled = scanner_config.get("enabled", False) - st.write("✅" if is_enabled else "❌") - - # Additional scanner configuration in expander - with st.expander("📊 Scanner Details"): - for scanner_name, scanner_config in scanners.items(): - pipeline = scanner_config.get("pipeline", "N/A") - limit = scanner_config.get("limit", "N/A") - enabled = scanner_config.get("enabled", False) - - st.write( - f"**{scanner_name}** | " - f"Pipeline: {pipeline} | " - f"Limit: {limit} | " - f"Status: {'✅ Enabled' if enabled else '❌ Disabled'}" - ) - else: - st.info("No scanners configured") - - # Data sources section - st.subheader("📡 Data Sources") + # ---- Data Sources ---- + st.markdown("
", unsafe_allow_html=True) + st.markdown( + '
Data Sources // vendors
', + unsafe_allow_html=True, + ) data_vendors = config.get("data_vendors", {}) - if data_vendors: - for vendor_type, vendor_name in data_vendors.items(): - st.write(f"**{vendor_type.replace('_', ' ').title()}**: {vendor_name}") + cols = st.columns(3) + for i, (vendor_type, vendor_name) in enumerate(data_vendors.items()): + with cols[i % 3]: + st.markdown( + f""" +
+
{vendor_type.replace('_', ' ')}
+
+ {vendor_name}
+
+ """, + unsafe_allow_html=True, + ) else: - st.info("No data sources configured") + st.info("No data sources configured.") diff --git a/tradingagents/ui/pages/todays_picks.py b/tradingagents/ui/pages/todays_picks.py index 47b907b9..4f8ddff5 100644 --- a/tradingagents/ui/pages/todays_picks.py +++ b/tradingagents/ui/pages/todays_picks.py @@ -1,4 +1,9 @@ -"""Today's recommendations.""" +""" +Signals page — today's recommendation cards with rich visual indicators. + +Each signal is displayed as a data-dense card with strategy badges, +confidence bars, and expandable thesis sections. +""" from datetime import datetime @@ -6,6 +11,7 @@ import pandas as pd import plotly.express as px import streamlit as st +from tradingagents.ui.theme import COLORS, get_plotly_template, page_header, signal_card from tradingagents.ui.utils import load_recommendations @@ -17,13 +23,8 @@ def _load_price_history(ticker: str, period: str) -> pd.DataFrame: return pd.DataFrame() data = download_history( - ticker, - period=period, - interval="1d", - auto_adjust=True, - progress=False, + ticker, period=period, interval="1d", auto_adjust=True, progress=False, ) - if data is None or data.empty: return pd.DataFrame() @@ -42,100 +43,116 @@ def _load_price_history(ticker: str, period: str) -> pd.DataFrame: def render(): - st.title("📋 Today's Recommendations") - today = datetime.now().strftime("%Y-%m-%d") recommendations, meta = load_recommendations(today, return_meta=True) + display_date = meta.get("date", today) if meta else today + + st.markdown( + page_header("Signals", f"Recommendations for {display_date}"), + unsafe_allow_html=True, + ) if not recommendations: - st.warning(f"No recommendations for {today}") + st.warning(f"No recommendations for {today}.") return if meta.get("is_fallback") and meta.get("date"): - st.info(f"No recommendations for {today}. Showing latest from {meta['date']}.") + st.info(f"Showing latest signals from **{meta['date']}** (none for today).") - show_charts = st.checkbox("Show price charts", value=True) - chart_window = st.selectbox( - "Price history window", - ["1mo", "3mo", "6mo", "1y"], - index=1, - ) - - # Filters - col1, col2, col3 = st.columns(3) - with col1: - pipelines = list( - set( - (r.get("pipeline") or r.get("strategy_match") or "unknown") for r in recommendations - ) - ) - pipeline_filter = st.multiselect("Pipeline", pipelines, default=pipelines) - with col2: - min_confidence = st.slider("Min Confidence", 1, 10, 7) - with col3: - min_score = st.slider("Min Score", 0, 100, 70) + # ---- Controls row ---- + ctrl_cols = st.columns([1, 1, 1, 1]) + with ctrl_cols[0]: + pipelines = sorted(set( + (r.get("pipeline") or r.get("strategy_match") or "unknown") for r in recommendations + )) + pipeline_filter = st.multiselect("Strategy", pipelines, default=pipelines) + with ctrl_cols[1]: + min_confidence = st.slider("Min Confidence", 1, 10, 1) + with ctrl_cols[2]: + min_score = st.slider("Min Score", 0, 100, 0) + with ctrl_cols[3]: + show_charts = st.checkbox("Price Charts", value=False) + if show_charts: + chart_window = st.selectbox("Window", ["1mo", "3mo", "6mo", "1y"], index=1) + else: + chart_window = "3mo" # Apply filters filtered = [ - r - for r in recommendations + r for r in recommendations if (r.get("pipeline") or r.get("strategy_match") or "unknown") in pipeline_filter and r.get("confidence", 0) >= min_confidence and r.get("final_score", 0) >= min_score ] - st.write(f"**{len(filtered)}** of **{len(recommendations)}** recommendations") + # ---- Summary bar ---- + st.markdown( + f""" +
+ + Showing + {len(filtered)} of {len(recommendations)} signals + + + {display_date} + +
+ """, + unsafe_allow_html=True, + ) - # Display recommendations - for i, rec in enumerate(filtered, 1): - ticker = rec.get("ticker", "UNKNOWN") - score = rec.get("final_score", 0) - confidence = rec.get("confidence", 0) - pipeline = (rec.get("pipeline") or rec.get("strategy_match") or "unknown").title() - scanner = rec.get("scanner") or rec.get("strategy_match") or "unknown" - entry_price = rec.get("entry_price") - current_price = rec.get("current_price") + # ---- Signal cards in 2-column grid ---- + for i in range(0, len(filtered), 2): + cols = st.columns(2) + for j, col in enumerate(cols): + idx = i + j + if idx >= len(filtered): + break + rec = filtered[idx] + ticker = rec.get("ticker", "UNKNOWN") + rank = rec.get("rank", idx + 1) + score = rec.get("final_score", 0) + confidence = rec.get("confidence", 0) + strategy = (rec.get("pipeline") or rec.get("strategy_match") or "unknown").title() + entry_price = rec.get("entry_price", 0) + reason = rec.get("reason", "No thesis provided.") - with st.expander( - f"#{i} {ticker} - {rec.get('company_name', '')} (Score: {score}, Conf: {confidence}/10)" - ): - col1, col2 = st.columns([2, 1]) + with col: + st.markdown( + signal_card(rank, ticker, score, confidence, strategy, entry_price, reason), + unsafe_allow_html=True, + ) - with col1: - st.write(f"**Pipeline:** {pipeline}") - st.write(f"**Scanner/Strategy:** {scanner}") - if entry_price is not None: - st.write(f"**Entry Price:** ${entry_price:.2f}") - if current_price is not None: - st.write(f"**Current Price:** ${current_price:.2f}") - st.write(f"**Thesis:** {rec.get('reason', 'N/A')}") if show_charts: history = _load_price_history(ticker, chart_window) - if history.empty: - st.caption("Price history unavailable.") - else: - last_close = history["close"].iloc[-1] - st.caption(f"Last close: ${last_close:.2f}") - fig = px.line( - history, - x="date", - y="close", - title=None, - labels={"date": "", "close": "Price"}, - ) - fig.update_traces(line=dict(color="#1f77b4", width=2)) - fig.update_layout( - height=260, - margin=dict(l=10, r=10, t=10, b=10), - xaxis=dict(showgrid=False), - yaxis=dict(showgrid=True, gridcolor="rgba(0,0,0,0.08)"), - hovermode="x unified", - ) - fig.update_yaxes(tickprefix="$") - st.plotly_chart(fig, use_container_width=True) + if not history.empty: + template = get_plotly_template() + fig = px.line(history, x="date", y="close", labels={"date": "", "close": "Price"}) - with col2: - if st.button("✅ Enter Position", key=f"enter_{ticker}"): - st.info("Position entry modal (TODO)") - if st.button("👀 Watch", key=f"watch_{ticker}"): - st.success(f"Added {ticker} to watchlist") + # Color line green if trending up, red if down + first_close = history["close"].iloc[0] + last_close = history["close"].iloc[-1] + line_color = COLORS["green"] if last_close >= first_close else COLORS["red"] + + fig.update_traces(line=dict(color=line_color, width=1.5)) + fig.update_layout( + **template, + height=160, + showlegend=False, + ) + fig.update_layout(margin=dict(l=0, r=0, t=0, b=0)) + fig.update_xaxes(showticklabels=False, showgrid=False) + fig.update_yaxes(showgrid=True, gridcolor="rgba(42,53,72,0.3)", tickprefix="$") + st.plotly_chart(fig, width="stretch") + + # Action buttons + btn_cols = st.columns(2) + with btn_cols[0]: + if st.button("Enter Position", key=f"enter_{ticker}_{idx}"): + st.info(f"Position entry for {ticker} (TODO)") + with btn_cols[1]: + if st.button("Watchlist", key=f"watch_{ticker}_{idx}"): + st.success(f"Added {ticker} to watchlist")