Add AI scoring results: 121 stocks scored with GPT-4.1-mini
- MemelyAlphaStockRanking_scored.xlsx: Excel with AI scores (yellow highlighted) - research_scores.json: Raw scoring data - score_stocks.py: AI scoring script - write_scores_to_excel.py: Excel writing script - Updated README with methodology and top 10 rankings
这个提交包含在:
二进制
MemelyAlphaStockRanking_scored.xlsx
普通文件
二进制
MemelyAlphaStockRanking_scored.xlsx
普通文件
二进制文件未显示。
66
README.md
66
README.md
@@ -1,3 +1,65 @@
|
||||
# memely-alpha-stock-ranking
|
||||
# Memely Alpha Stock Ranking - AI Scoring Analysis
|
||||
|
||||
Memely Alpha Stock Ranking - Deep Research & AI Scoring Analysis
|
||||
## Overview
|
||||
|
||||
This repository contains the **Memely Alpha Stock Ranking** data with AI-powered scoring analysis for 121 unique stocks/assets across multiple thematic sectors.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `MemelyAlphaStockRanking_original.xlsx` | Original Excel file with stock master list, weekly/YTD rankings |
|
||||
| `MemelyAlphaStockRanking_scored.xlsx` | Updated Excel with AI scores (yellow-highlighted cells) |
|
||||
| `research_scores.json` | Raw AI scoring data in JSON format |
|
||||
| `scoring_progress.json` | Scoring progress tracking file |
|
||||
| `score_stocks.py` | Python script used for AI model scoring |
|
||||
| `write_scores_to_excel.py` | Python script to write scores into Excel |
|
||||
|
||||
## AI Scoring Methodology
|
||||
|
||||
Each stock was evaluated by an AI model (GPT-4.1-mini) based on:
|
||||
|
||||
1. **Overall Score (1-100)**: Comprehensive investment attractiveness
|
||||
2. **Momentum Score (1-100)**: Based on 7-day and YTD price performance
|
||||
3. **Theme Score (1-100)**: Relevance of thematic play (AI, Defense, Aerospace, Crypto, etc.)
|
||||
4. **Risk Score (1-100)**: Volatility and risk level (100 = highest risk)
|
||||
5. **Social Buzz Score (1-100)**: KOL backing and social media attention
|
||||
|
||||
## Top 10 Stocks by Overall Score
|
||||
|
||||
| Rank | Symbol | Theme | Overall | Momentum | Theme Score |
|
||||
|------|--------|-------|---------|----------|-------------|
|
||||
| 1 | UMAC | Drone | 88 | 95 | 80 |
|
||||
| 2 | EQRLF | - | 85 | 95 | 20 |
|
||||
| 3 | OSS | Defense | 82 | 80 | 70 |
|
||||
| 4 | AXTI | Photonics | 80 | 90 | 75 |
|
||||
| 5 | LASR | Defense | 80 | 85 | 75 |
|
||||
| 6 | LFMD | GLP-1 | 80 | 90 | 75 |
|
||||
| 7 | AMPX | Battery | 78 | 60 | 80 |
|
||||
| 8 | CRCL | Stablecoin | 78 | 75 | 70 |
|
||||
| 9 | FSLY | - | 75 | 80 | 25 |
|
||||
| 10 | PSRHF | Helium 3 | 75 | 85 | 70 |
|
||||
|
||||
## Sector Distribution
|
||||
|
||||
Key sectors covered include:
|
||||
- **Defense & Drone**: UMAC, OSS, LASR, RCAT, KTOS, MRCY, AIRO, etc.
|
||||
- **Aerospace**: ASTS, RDW, PL, IRDM, RKLB, VELO, etc.
|
||||
- **AI & Technology**: ACMR, NBIS, ZETA, PATH, GTLB, etc.
|
||||
- **Photonics**: AXTI, AAOI, POET, LPTH, ALMU
|
||||
- **Crypto & Fintech**: CRCL, GLXY, BKKT, FIGR, ETOR, GEMI
|
||||
- **Biotech & Pharma**: LFMD, NVO, RXRX, CERT, HIMS
|
||||
- **Quantum Computing**: IONQ, RGTI, QUBT, QBTS, INFQ
|
||||
- **Nuclear/Energy**: UUUU, OKLO, SMR, NEE, PPSI
|
||||
|
||||
## Excel Modifications
|
||||
|
||||
All AI-generated scores are written into the Excel file with **yellow background highlighting** to clearly distinguish new data from original data. The modifications include:
|
||||
|
||||
1. **"stock master list" sheet**: 6 new columns added (AI Overall Score, Momentum, Theme, Risk, Social Buzz, Brief Analysis)
|
||||
2. **"Sheet4"**: Same 6 columns added
|
||||
3. **"AI Score Summary" (new sheet)**: Complete ranking sorted by Overall Score with color-coded cells
|
||||
|
||||
## Date
|
||||
|
||||
Analysis performed on: **March 14, 2026**
|
||||
1091
research_scores.json
普通文件
1091
research_scores.json
普通文件
文件差异内容过多而无法显示
加载差异
196
score_stocks.py
普通文件
196
score_stocks.py
普通文件
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Score all stocks in the MemelyAlpha ranking using AI model.
|
||||
Processes in batches to handle rate limits and save progress.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from openai import OpenAI
|
||||
|
||||
client = OpenAI()
|
||||
|
||||
EXCEL_PATH = "/home/ubuntu/upload/MemelyAlphaStockRanking(副本)(副本).xlsx"
|
||||
OUTPUT_JSON = "/home/ubuntu/memely-alpha-stock-ranking/research_scores.json"
|
||||
PROGRESS_FILE = "/home/ubuntu/memely-alpha-stock-ranking/scoring_progress.json"
|
||||
|
||||
# Read the master list
|
||||
df = pd.read_excel(EXCEL_PATH, sheet_name='stock master list')
|
||||
|
||||
# Also read Sheet4 for sector/theme info
|
||||
df4 = pd.read_excel(EXCEL_PATH, sheet_name='Sheet4')
|
||||
sector_map = {}
|
||||
for _, row in df4.iterrows():
|
||||
sym = row.get('Symbol')
|
||||
if pd.notna(sym):
|
||||
sector_map[sym] = {
|
||||
'sector': row.get('Sector', ''),
|
||||
'theme': row.get('Theme', ''),
|
||||
'cap_category': row.get('Cap Category', '')
|
||||
}
|
||||
|
||||
# Read Sheet6 for additional YTD data
|
||||
df6 = pd.read_excel(EXCEL_PATH, sheet_name='Sheet6')
|
||||
ytd_extra = {}
|
||||
for _, row in df6.iterrows():
|
||||
sym = row.get('Symbol')
|
||||
if pd.notna(sym):
|
||||
ytd_extra[sym] = row.get('performance Year to Date', None)
|
||||
|
||||
# Build stock info for each symbol
|
||||
stocks_info = []
|
||||
seen = set()
|
||||
for _, row in df.iterrows():
|
||||
sym = row['Symbol']
|
||||
if pd.isna(sym) or sym in seen:
|
||||
continue
|
||||
seen.add(sym)
|
||||
|
||||
info = {
|
||||
'symbol': sym,
|
||||
'theme': str(row.get('Theme', '')),
|
||||
'perf_7d': row.get('performance past 7 days', None),
|
||||
'perf_ytd': row.get('performance Year to Date', None),
|
||||
'perf_specific': row.get('performance on specific dates', None),
|
||||
'first_call_kol': str(row.get('first call X kol', '')),
|
||||
'top_contributor': str(row.get('top contributor', '')),
|
||||
}
|
||||
|
||||
# Add sector info from Sheet4
|
||||
if sym in sector_map:
|
||||
info['sector'] = sector_map[sym].get('sector', '')
|
||||
info['theme_s4'] = sector_map[sym].get('theme', '')
|
||||
|
||||
# Add extra YTD from Sheet6
|
||||
if sym in ytd_extra and pd.notna(ytd_extra[sym]):
|
||||
info['ytd_extra'] = ytd_extra[sym]
|
||||
|
||||
stocks_info.append(info)
|
||||
|
||||
print(f"Total unique stocks to score: {len(stocks_info)}")
|
||||
|
||||
# Load progress if exists
|
||||
scored = {}
|
||||
if os.path.exists(PROGRESS_FILE):
|
||||
with open(PROGRESS_FILE, 'r') as f:
|
||||
scored = json.load(f)
|
||||
print(f"Loaded {len(scored)} previously scored stocks")
|
||||
|
||||
def score_batch(batch_stocks):
|
||||
"""Score a batch of stocks using AI model."""
|
||||
stocks_text = ""
|
||||
for s in batch_stocks:
|
||||
perf_7d = f"{s['perf_7d']:.2%}" if pd.notna(s.get('perf_7d')) else "N/A"
|
||||
perf_ytd = f"{s['perf_ytd']:.2%}" if pd.notna(s.get('perf_ytd')) else "N/A"
|
||||
perf_spec = f"{s['perf_specific']:.2%}" if pd.notna(s.get('perf_specific')) else "N/A"
|
||||
sector = s.get('sector', s.get('theme', 'N/A'))
|
||||
theme = s.get('theme_s4', s.get('theme', 'N/A'))
|
||||
|
||||
stocks_text += f"""
|
||||
---
|
||||
Symbol: {s['symbol']}
|
||||
Sector: {sector}
|
||||
Theme: {theme}
|
||||
Performance (Past 7 Days): {perf_7d}
|
||||
Performance (Year to Date): {perf_ytd}
|
||||
Performance (Specific Date): {perf_spec}
|
||||
First Call KOL: {s.get('first_call_kol', 'N/A')}
|
||||
Top Contributor: {s.get('top_contributor', 'N/A')}
|
||||
"""
|
||||
|
||||
prompt = f"""You are a professional stock/crypto analyst. Evaluate each of the following stocks/assets and provide a comprehensive score.
|
||||
|
||||
For EACH stock, provide:
|
||||
1. **overall_score** (1-100): Overall investment attractiveness score
|
||||
2. **momentum_score** (1-100): Based on recent price performance and momentum
|
||||
3. **theme_score** (1-100): How strong/relevant is the thematic play (e.g., AI, Defense, Aerospace, Crypto, etc.)
|
||||
4. **risk_score** (1-100): Risk level (100 = highest risk)
|
||||
5. **social_buzz_score** (1-100): Social media attention and KOL backing strength
|
||||
6. **brief_analysis**: 2-3 sentence analysis explaining the scores
|
||||
|
||||
Consider these factors:
|
||||
- YTD performance indicates momentum strength
|
||||
- Thematic relevance to current market trends (AI, Defense, Aerospace, Crypto, Quantum Computing are hot in 2025-2026)
|
||||
- Small/micro cap stocks with strong themes get higher theme scores
|
||||
- Stocks with notable KOL backing get higher social buzz scores
|
||||
- Higher volatility = higher risk score
|
||||
|
||||
Here are the stocks to evaluate:
|
||||
{stocks_text}
|
||||
|
||||
Return ONLY a valid JSON array with objects for each stock. Each object must have: symbol, overall_score, momentum_score, theme_score, risk_score, social_buzz_score, brief_analysis.
|
||||
Do not include any text outside the JSON array."""
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4.1-mini",
|
||||
messages=[
|
||||
{"role": "system", "content": "You are a professional financial analyst. Always respond with valid JSON only."},
|
||||
{"role": "user", "content": prompt}
|
||||
],
|
||||
temperature=0.3,
|
||||
max_tokens=8000
|
||||
)
|
||||
|
||||
content = response.choices[0].message.content.strip()
|
||||
# Try to extract JSON from the response
|
||||
if content.startswith("```"):
|
||||
content = content.split("```")[1]
|
||||
if content.startswith("json"):
|
||||
content = content[4:]
|
||||
|
||||
results = json.loads(content)
|
||||
return results
|
||||
except Exception as e:
|
||||
print(f"Error in batch scoring: {e}")
|
||||
return None
|
||||
|
||||
# Process in batches of 15
|
||||
batch_size = 15
|
||||
remaining = [s for s in stocks_info if s['symbol'] not in scored]
|
||||
print(f"Remaining to score: {len(remaining)}")
|
||||
|
||||
total_batches = (len(remaining) + batch_size - 1) // batch_size
|
||||
|
||||
for i in range(0, len(remaining), batch_size):
|
||||
batch = remaining[i:i+batch_size]
|
||||
batch_num = i // batch_size + 1
|
||||
symbols_in_batch = [s['symbol'] for s in batch]
|
||||
print(f"\nBatch {batch_num}/{total_batches}: Scoring {symbols_in_batch}")
|
||||
|
||||
results = score_batch(batch)
|
||||
|
||||
if results:
|
||||
for r in results:
|
||||
sym = r.get('symbol', '')
|
||||
if sym:
|
||||
scored[sym] = r
|
||||
print(f" ✓ {sym}: Overall={r.get('overall_score')}, Momentum={r.get('momentum_score')}, Theme={r.get('theme_score')}")
|
||||
|
||||
# Save progress after each batch
|
||||
with open(PROGRESS_FILE, 'w') as f:
|
||||
json.dump(scored, f, indent=2, ensure_ascii=False)
|
||||
else:
|
||||
print(f" ✗ Batch {batch_num} failed, will retry...")
|
||||
# Retry once
|
||||
time.sleep(2)
|
||||
results = score_batch(batch)
|
||||
if results:
|
||||
for r in results:
|
||||
sym = r.get('symbol', '')
|
||||
if sym:
|
||||
scored[sym] = r
|
||||
with open(PROGRESS_FILE, 'w') as f:
|
||||
json.dump(scored, f, indent=2, ensure_ascii=False)
|
||||
|
||||
time.sleep(1) # Rate limit
|
||||
|
||||
# Save final results
|
||||
with open(OUTPUT_JSON, 'w') as f:
|
||||
json.dump(scored, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n=== SCORING COMPLETE ===")
|
||||
print(f"Total scored: {len(scored)}/{len(stocks_info)}")
|
||||
print(f"Results saved to: {OUTPUT_JSON}")
|
||||
1091
scoring_progress.json
普通文件
1091
scoring_progress.json
普通文件
文件差异内容过多而无法显示
加载差异
326
write_scores_to_excel.py
普通文件
326
write_scores_to_excel.py
普通文件
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Write AI scores into the Excel file and highlight modified cells with yellow background.
|
||||
"""
|
||||
|
||||
import json
|
||||
import pandas as pd
|
||||
from openpyxl import load_workbook
|
||||
from openpyxl.styles import PatternFill, Font, Alignment, Border, Side
|
||||
from copy import copy
|
||||
|
||||
EXCEL_SRC = "/home/ubuntu/upload/MemelyAlphaStockRanking(副本)(副本).xlsx"
|
||||
SCORES_JSON = "/home/ubuntu/memely-alpha-stock-ranking/research_scores.json"
|
||||
EXCEL_OUT = "/home/ubuntu/memely-alpha-stock-ranking/MemelyAlphaStockRanking_scored.xlsx"
|
||||
|
||||
# Load scores
|
||||
with open(SCORES_JSON, 'r') as f:
|
||||
scores = json.load(f)
|
||||
|
||||
print(f"Loaded scores for {len(scores)} stocks")
|
||||
|
||||
# Load workbook
|
||||
wb = load_workbook(EXCEL_SRC)
|
||||
|
||||
# Yellow fill for modified cells
|
||||
yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
|
||||
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
|
||||
header_font = Font(bold=True, color="FFFFFF", size=11)
|
||||
score_font = Font(size=10)
|
||||
thin_border = Border(
|
||||
left=Side(style='thin'),
|
||||
right=Side(style='thin'),
|
||||
top=Side(style='thin'),
|
||||
bottom=Side(style='thin')
|
||||
)
|
||||
|
||||
# ========== Modify "stock master list" sheet ==========
|
||||
ws = wb['stock master list']
|
||||
|
||||
# Find the last column with data
|
||||
max_col = ws.max_column
|
||||
|
||||
# Add new score columns
|
||||
new_cols = {
|
||||
max_col + 1: 'AI Overall Score',
|
||||
max_col + 2: 'AI Momentum Score',
|
||||
max_col + 3: 'AI Theme Score',
|
||||
max_col + 4: 'AI Risk Score',
|
||||
max_col + 5: 'AI Social Buzz Score',
|
||||
max_col + 6: 'AI Brief Analysis',
|
||||
}
|
||||
|
||||
# Write headers
|
||||
for col_offset, header_name in new_cols.items():
|
||||
cell = ws.cell(row=1, column=col_offset, value=header_name)
|
||||
cell.fill = header_fill
|
||||
cell.font = header_font
|
||||
cell.alignment = Alignment(horizontal='center', wrap_text=True)
|
||||
cell.border = thin_border
|
||||
|
||||
# Write scores for each stock
|
||||
for row in range(2, ws.max_row + 1):
|
||||
symbol = ws.cell(row=row, column=1).value
|
||||
if symbol and symbol in scores:
|
||||
s = scores[symbol]
|
||||
|
||||
# Overall Score
|
||||
cell = ws.cell(row=row, column=max_col + 1, value=s.get('overall_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Momentum Score
|
||||
cell = ws.cell(row=row, column=max_col + 2, value=s.get('momentum_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Theme Score
|
||||
cell = ws.cell(row=row, column=max_col + 3, value=s.get('theme_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Risk Score
|
||||
cell = ws.cell(row=row, column=max_col + 4, value=s.get('risk_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Social Buzz Score
|
||||
cell = ws.cell(row=row, column=max_col + 5, value=s.get('social_buzz_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Brief Analysis
|
||||
cell = ws.cell(row=row, column=max_col + 6, value=s.get('brief_analysis', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(wrap_text=True)
|
||||
cell.border = thin_border
|
||||
|
||||
# Set column widths
|
||||
ws.column_dimensions[ws.cell(row=1, column=max_col + 1).column_letter].width = 15
|
||||
ws.column_dimensions[ws.cell(row=1, column=max_col + 2).column_letter].width = 15
|
||||
ws.column_dimensions[ws.cell(row=1, column=max_col + 3).column_letter].width = 15
|
||||
ws.column_dimensions[ws.cell(row=1, column=max_col + 4).column_letter].width = 15
|
||||
ws.column_dimensions[ws.cell(row=1, column=max_col + 5).column_letter].width = 18
|
||||
ws.column_dimensions[ws.cell(row=1, column=max_col + 6).column_letter].width = 60
|
||||
|
||||
print(f"Updated 'stock master list' sheet with scores")
|
||||
|
||||
# ========== Also modify Sheet4 with scores ==========
|
||||
ws4 = wb['Sheet4']
|
||||
max_col4 = ws4.max_column
|
||||
|
||||
new_cols4 = {
|
||||
max_col4 + 1: 'AI Overall Score',
|
||||
max_col4 + 2: 'AI Momentum Score',
|
||||
max_col4 + 3: 'AI Theme Score',
|
||||
max_col4 + 4: 'AI Risk Score',
|
||||
max_col4 + 5: 'AI Social Buzz Score',
|
||||
max_col4 + 6: 'AI Brief Analysis',
|
||||
}
|
||||
|
||||
# Write headers for Sheet4
|
||||
for col_offset, header_name in new_cols4.items():
|
||||
cell = ws4.cell(row=1, column=col_offset, value=header_name)
|
||||
cell.fill = header_fill
|
||||
cell.font = header_font
|
||||
cell.alignment = Alignment(horizontal='center', wrap_text=True)
|
||||
cell.border = thin_border
|
||||
|
||||
# Write scores for Sheet4
|
||||
for row in range(2, ws4.max_row + 1):
|
||||
symbol = ws4.cell(row=row, column=1).value
|
||||
if symbol and symbol in scores:
|
||||
s = scores[symbol]
|
||||
|
||||
cell = ws4.cell(row=row, column=max_col4 + 1, value=s.get('overall_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
cell = ws4.cell(row=row, column=max_col4 + 2, value=s.get('momentum_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
cell = ws4.cell(row=row, column=max_col4 + 3, value=s.get('theme_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
cell = ws4.cell(row=row, column=max_col4 + 4, value=s.get('risk_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
cell = ws4.cell(row=row, column=max_col4 + 5, value=s.get('social_buzz_score', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
cell = ws4.cell(row=row, column=max_col4 + 6, value=s.get('brief_analysis', ''))
|
||||
cell.fill = yellow_fill
|
||||
cell.font = score_font
|
||||
cell.alignment = Alignment(wrap_text=True)
|
||||
cell.border = thin_border
|
||||
|
||||
ws4.column_dimensions[ws4.cell(row=1, column=max_col4 + 1).column_letter].width = 15
|
||||
ws4.column_dimensions[ws4.cell(row=1, column=max_col4 + 2).column_letter].width = 15
|
||||
ws4.column_dimensions[ws4.cell(row=1, column=max_col4 + 3).column_letter].width = 15
|
||||
ws4.column_dimensions[ws4.cell(row=1, column=max_col4 + 4).column_letter].width = 15
|
||||
ws4.column_dimensions[ws4.cell(row=1, column=max_col4 + 5).column_letter].width = 18
|
||||
ws4.column_dimensions[ws4.cell(row=1, column=max_col4 + 6).column_letter].width = 60
|
||||
|
||||
print(f"Updated 'Sheet4' with scores")
|
||||
|
||||
# ========== Create a new summary sheet ==========
|
||||
ws_summary = wb.create_sheet("AI Score Summary", 0)
|
||||
|
||||
# Headers
|
||||
summary_headers = ['Rank', 'Symbol', 'Theme', 'Sector', 'Overall Score', 'Momentum Score',
|
||||
'Theme Score', 'Risk Score', 'Social Buzz Score', 'Perf YTD', 'Brief Analysis']
|
||||
|
||||
for col, header in enumerate(summary_headers, 1):
|
||||
cell = ws_summary.cell(row=1, column=col, value=header)
|
||||
cell.fill = header_fill
|
||||
cell.font = header_font
|
||||
cell.alignment = Alignment(horizontal='center', wrap_text=True)
|
||||
cell.border = thin_border
|
||||
|
||||
# Sort stocks by overall score
|
||||
sorted_stocks = sorted(scores.items(), key=lambda x: x[1].get('overall_score', 0), reverse=True)
|
||||
|
||||
# Read original data for theme/sector info
|
||||
df = pd.read_excel(EXCEL_SRC, sheet_name='stock master list')
|
||||
df4 = pd.read_excel(EXCEL_SRC, sheet_name='Sheet4')
|
||||
|
||||
symbol_info = {}
|
||||
for _, row in df.iterrows():
|
||||
sym = row['Symbol']
|
||||
if pd.notna(sym):
|
||||
symbol_info[sym] = {
|
||||
'theme': row.get('Theme', ''),
|
||||
'perf_ytd': row.get('performance Year to Date', None)
|
||||
}
|
||||
|
||||
sector_info = {}
|
||||
for _, row in df4.iterrows():
|
||||
sym = row.get('Symbol')
|
||||
if pd.notna(sym):
|
||||
sector_info[sym] = row.get('Sector', '')
|
||||
|
||||
# Color coding for scores
|
||||
from openpyxl.styles import PatternFill
|
||||
|
||||
def get_score_fill(score):
|
||||
if score >= 70:
|
||||
return PatternFill(start_color="92D050", end_color="92D050", fill_type="solid") # Green
|
||||
elif score >= 50:
|
||||
return PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") # Yellow
|
||||
elif score >= 30:
|
||||
return PatternFill(start_color="FFC000", end_color="FFC000", fill_type="solid") # Orange
|
||||
else:
|
||||
return PatternFill(start_color="FF6B6B", end_color="FF6B6B", fill_type="solid") # Red
|
||||
|
||||
for rank, (symbol, s) in enumerate(sorted_stocks, 1):
|
||||
row = rank + 1
|
||||
info = symbol_info.get(symbol, {})
|
||||
|
||||
ws_summary.cell(row=row, column=1, value=rank).border = thin_border
|
||||
ws_summary.cell(row=row, column=1).alignment = Alignment(horizontal='center')
|
||||
|
||||
ws_summary.cell(row=row, column=2, value=symbol).border = thin_border
|
||||
ws_summary.cell(row=row, column=2).font = Font(bold=True)
|
||||
|
||||
theme = info.get('theme', '')
|
||||
ws_summary.cell(row=row, column=3, value=theme if pd.notna(theme) else '').border = thin_border
|
||||
|
||||
sector = sector_info.get(symbol, '')
|
||||
ws_summary.cell(row=row, column=4, value=sector if pd.notna(sector) else '').border = thin_border
|
||||
|
||||
# Overall Score with color
|
||||
overall = s.get('overall_score', 0)
|
||||
cell = ws_summary.cell(row=row, column=5, value=overall)
|
||||
cell.fill = get_score_fill(overall)
|
||||
cell.font = Font(bold=True, size=11)
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Momentum Score
|
||||
momentum = s.get('momentum_score', 0)
|
||||
cell = ws_summary.cell(row=row, column=6, value=momentum)
|
||||
cell.fill = get_score_fill(momentum)
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Theme Score
|
||||
theme_sc = s.get('theme_score', 0)
|
||||
cell = ws_summary.cell(row=row, column=7, value=theme_sc)
|
||||
cell.fill = get_score_fill(theme_sc)
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Risk Score (higher = more risky, use inverse coloring)
|
||||
risk = s.get('risk_score', 0)
|
||||
cell = ws_summary.cell(row=row, column=8, value=risk)
|
||||
# For risk, high is bad
|
||||
if risk >= 70:
|
||||
cell.fill = PatternFill(start_color="FF6B6B", end_color="FF6B6B", fill_type="solid")
|
||||
elif risk >= 50:
|
||||
cell.fill = PatternFill(start_color="FFC000", end_color="FFC000", fill_type="solid")
|
||||
else:
|
||||
cell.fill = PatternFill(start_color="92D050", end_color="92D050", fill_type="solid")
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Social Buzz Score
|
||||
buzz = s.get('social_buzz_score', 0)
|
||||
cell = ws_summary.cell(row=row, column=9, value=buzz)
|
||||
cell.fill = get_score_fill(buzz)
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# YTD Performance
|
||||
ytd = info.get('perf_ytd', None)
|
||||
if pd.notna(ytd):
|
||||
cell = ws_summary.cell(row=row, column=10, value=ytd)
|
||||
cell.number_format = '0.00%'
|
||||
else:
|
||||
cell = ws_summary.cell(row=row, column=10, value='N/A')
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = thin_border
|
||||
|
||||
# Brief Analysis
|
||||
cell = ws_summary.cell(row=row, column=11, value=s.get('brief_analysis', ''))
|
||||
cell.alignment = Alignment(wrap_text=True)
|
||||
cell.border = thin_border
|
||||
|
||||
# Set column widths for summary
|
||||
col_widths = [6, 10, 20, 25, 14, 16, 14, 12, 18, 12, 60]
|
||||
for i, w in enumerate(col_widths, 1):
|
||||
ws_summary.column_dimensions[ws_summary.cell(row=1, column=i).column_letter].width = w
|
||||
|
||||
# Freeze panes
|
||||
ws_summary.freeze_panes = 'A2'
|
||||
|
||||
print(f"Created 'AI Score Summary' sheet with {len(sorted_stocks)} stocks ranked")
|
||||
|
||||
# Save
|
||||
wb.save(EXCEL_OUT)
|
||||
print(f"\nSaved scored Excel to: {EXCEL_OUT}")
|
||||
print("All modified/new cells are highlighted with yellow background")
|
||||
在新工单中引用
屏蔽一个用户