Spaces:
Running
Running
Update src/services/spot_engine.py
Browse files- src/services/spot_engine.py +115 -27
src/services/spot_engine.py
CHANGED
|
@@ -58,54 +58,131 @@ def spot_volume_tracker(user_keys, user_id) -> None:
|
|
| 58 |
}
|
| 59 |
|
| 60 |
def create_html_report(hot_tokens: List[Dict[str, Any]]) -> str:
|
| 61 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 62 |
date_prefix = datetime.datetime.now().strftime("%b-%d-%y_%H-%M")
|
| 63 |
user_dir = get_user_temp_dir(user_id)
|
| 64 |
html_file = user_dir / f"Spot_Analysis_Report_{date_prefix}.html"
|
| 65 |
current_time = now_str("%d-%m-%Y %H:%M:%S")
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
html_content = f"""
|
| 68 |
<!DOCTYPE html>
|
| 69 |
-
<html>
|
| 70 |
<head>
|
| 71 |
-
<title>Spot Analysis {date_prefix}</title>
|
| 72 |
<meta charset="UTF-8">
|
| 73 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
|
|
|
|
|
| 74 |
<style>
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
.ticker-btn {{
|
| 88 |
-
display: block; width: 100%; height: 100%; padding:
|
| 89 |
-
color:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
}}
|
| 91 |
-
.vol-high {{ color: #e74c3c; font-weight: bold; }}
|
| 92 |
</style>
|
| 93 |
</head>
|
| 94 |
<body>
|
| 95 |
<div class="header">
|
| 96 |
-
<
|
| 97 |
-
<
|
| 98 |
</div>
|
| 99 |
|
| 100 |
<div class="summary">
|
| 101 |
-
<
|
| 102 |
</div>
|
| 103 |
|
| 104 |
<div class="table-container">
|
| 105 |
<table>
|
| 106 |
<thead>
|
| 107 |
<tr>
|
| 108 |
-
<th style="width:
|
|
|
|
| 109 |
</thead>
|
| 110 |
<tbody>
|
| 111 |
"""
|
|
@@ -113,20 +190,31 @@ def spot_volume_tracker(user_keys, user_id) -> None:
|
|
| 113 |
for i, token in enumerate(hot_tokens):
|
| 114 |
is_lc = token.get('large_cap', False)
|
| 115 |
row_class = "large-cap" if is_lc else ""
|
| 116 |
-
|
|
|
|
| 117 |
sym = token.get('symbol', '???')
|
| 118 |
link = f'<a href="/deep-diver?ticker={sym}" class="ticker-btn">{sym}</a>'
|
|
|
|
| 119 |
html_content += f"""
|
| 120 |
<tr class="{row_class}">
|
| 121 |
-
<td style="text-align:center; color:
|
| 122 |
-
<td
|
| 123 |
-
<td style="padding-left:
|
| 124 |
-
<td style="padding-left:
|
| 125 |
-
<td class="{vol_class}" style="padding-left:
|
| 126 |
</tr>
|
| 127 |
"""
|
| 128 |
|
| 129 |
-
html_content += "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
with open(html_file, "w", encoding="utf-8") as f:
|
| 132 |
f.write(html_content)
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
def create_html_report(hot_tokens: List[Dict[str, Any]]) -> str:
|
| 61 |
+
"""
|
| 62 |
+
Generates a mobile-responsive, QuantVAT-branded HTML report.
|
| 63 |
+
Aligned with Phase 1: 5-column layout + Analysis Engine compatibility.
|
| 64 |
+
"""
|
| 65 |
date_prefix = datetime.datetime.now().strftime("%b-%d-%y_%H-%M")
|
| 66 |
user_dir = get_user_temp_dir(user_id)
|
| 67 |
html_file = user_dir / f"Spot_Analysis_Report_{date_prefix}.html"
|
| 68 |
current_time = now_str("%d-%m-%Y %H:%M:%S")
|
| 69 |
|
| 70 |
+
# Logic for header summary
|
| 71 |
+
max_flip = max((t.get('flipping_multiple', 0) for t in hot_tokens), default=0)
|
| 72 |
+
large_cap_count = len([t for t in hot_tokens if t.get('large_cap')])
|
| 73 |
+
|
| 74 |
html_content = f"""
|
| 75 |
<!DOCTYPE html>
|
| 76 |
+
<html lang="en">
|
| 77 |
<head>
|
|
|
|
| 78 |
<meta charset="UTF-8">
|
| 79 |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
| 80 |
+
<title>Spot Analysis Report</title>
|
| 81 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;800&family=JetBrains+Mono:wght@700&display=swap" rel="stylesheet">
|
| 82 |
<style>
|
| 83 |
+
:root {{
|
| 84 |
+
--bg-dark: #151a1e;
|
| 85 |
+
--bg-card: #1e252a;
|
| 86 |
+
--accent-green: #10b981;
|
| 87 |
+
--text-main: #ffffff;
|
| 88 |
+
--text-dim: #848e9c;
|
| 89 |
+
--border: #2b3139;
|
| 90 |
+
}}
|
| 91 |
+
body {{
|
| 92 |
+
font-family: 'Inter', sans-serif;
|
| 93 |
+
margin: 0;
|
| 94 |
+
background-color: var(--bg-dark);
|
| 95 |
+
color: var(--text-main);
|
| 96 |
+
-webkit-font-smoothing: antialiased;
|
| 97 |
+
}}
|
| 98 |
+
.header {{
|
| 99 |
+
background: linear-gradient(180deg, rgba(16, 185, 129, 0.1) 0%, transparent 100%);
|
| 100 |
+
padding: 40px 20px;
|
| 101 |
+
text-align: center;
|
| 102 |
+
border-bottom: 1px solid var(--border);
|
| 103 |
+
}}
|
| 104 |
+
.header h1 {{ margin: 0; font-size: 1.5rem; color: var(--accent-green); font-weight: 800; text-transform: uppercase; }}
|
| 105 |
+
.header p {{ margin: 10px 0 0; font-size: 0.85rem; color: var(--text-dim); font-family: 'JetBrains Mono', monospace; }}
|
| 106 |
|
| 107 |
+
.summary {{
|
| 108 |
+
background: var(--bg-card);
|
| 109 |
+
padding: 20px;
|
| 110 |
+
margin: 20px;
|
| 111 |
+
border-radius: 12px;
|
| 112 |
+
border: 1px solid var(--border);
|
| 113 |
+
font-size: 0.9rem;
|
| 114 |
+
line-height: 1.6;
|
| 115 |
+
}}
|
| 116 |
+
.summary b {{ color: var(--accent-green); }}
|
| 117 |
+
|
| 118 |
+
.table-container {{
|
| 119 |
+
overflow-x: auto;
|
| 120 |
+
margin: 0 20px;
|
| 121 |
+
border-radius: 12px;
|
| 122 |
+
border: 1px solid var(--border);
|
| 123 |
+
background: var(--bg-card);
|
| 124 |
+
}}
|
| 125 |
+
table {{ width: 100%; border-collapse: collapse; }}
|
| 126 |
+
th {{
|
| 127 |
+
background: rgba(0, 0, 0, 0.2);
|
| 128 |
+
color: var(--text-dim);
|
| 129 |
+
padding: 15px 10px;
|
| 130 |
+
text-align: left;
|
| 131 |
+
font-size: 0.7rem;
|
| 132 |
+
text-transform: uppercase;
|
| 133 |
+
letter-spacing: 1px;
|
| 134 |
+
border-bottom: 1px solid var(--border);
|
| 135 |
+
}}
|
| 136 |
+
td {{
|
| 137 |
+
padding: 0;
|
| 138 |
+
border-bottom: 1px solid #2b3139;
|
| 139 |
+
height: 55px;
|
| 140 |
+
vertical-align: middle;
|
| 141 |
+
font-size: 0.9rem;
|
| 142 |
+
}}
|
| 143 |
+
tr:last-child td {{ border-bottom: none; }}
|
| 144 |
|
| 145 |
+
/* Large Cap Highlight Style */
|
| 146 |
+
tr.large-cap {{ background: rgba(16, 185, 129, 0.03); }}
|
| 147 |
+
tr.large-cap td:first-child {{ border-left: 3px solid var(--accent-green); }}
|
| 148 |
+
|
| 149 |
+
/* Interactive Redirection Link */
|
| 150 |
.ticker-btn {{
|
| 151 |
+
display: block; width: 100%; height: 100%; padding: 15px 10px;
|
| 152 |
+
color: var(--accent-green); text-decoration: none; font-weight: 800;
|
| 153 |
+
box-sizing: border-box; transition: background 0.2s;
|
| 154 |
+
}}
|
| 155 |
+
.ticker-btn:active {{ background: rgba(16, 185, 129, 0.1); }}
|
| 156 |
+
|
| 157 |
+
.mono {{ font-family: 'JetBrains Mono', monospace; }}
|
| 158 |
+
.vol-high {{ color: #ef4444; font-weight: bold; }}
|
| 159 |
+
|
| 160 |
+
.footer {{
|
| 161 |
+
text-align: center;
|
| 162 |
+
padding: 40px 20px;
|
| 163 |
+
font-size: 0.75rem;
|
| 164 |
+
color: var(--text-dim);
|
| 165 |
+
border-top: 1px solid var(--border);
|
| 166 |
+
margin-top: 20px;
|
| 167 |
}}
|
|
|
|
| 168 |
</style>
|
| 169 |
</head>
|
| 170 |
<body>
|
| 171 |
<div class="header">
|
| 172 |
+
<h1>Spot Volume Analysis Report</h1>
|
| 173 |
+
<p>{current_time}</p>
|
| 174 |
</div>
|
| 175 |
|
| 176 |
<div class="summary">
|
| 177 |
+
Found <b>{len(hot_tokens)}</b> tokens. | <b>{large_cap_count}</b> Largecaps found | Highest VTMR <b>{max_flip:.1f}x</b>.
|
| 178 |
</div>
|
| 179 |
|
| 180 |
<div class="table-container">
|
| 181 |
<table>
|
| 182 |
<thead>
|
| 183 |
<tr>
|
| 184 |
+
<th style="width: 45px; text-align:center;">#</th>
|
| 185 |
+
<th>Ticker</th> <th>Market Cap</th> <th>Volume</th> <th>Spot VTMR</th> </tr>
|
| 186 |
</thead>
|
| 187 |
<tbody>
|
| 188 |
"""
|
|
|
|
| 190 |
for i, token in enumerate(hot_tokens):
|
| 191 |
is_lc = token.get('large_cap', False)
|
| 192 |
row_class = "large-cap" if is_lc else ""
|
| 193 |
+
vtmr = token.get('flipping_multiple', 0)
|
| 194 |
+
vol_class = "vol-high" if vtmr >= 2 else ""
|
| 195 |
sym = token.get('symbol', '???')
|
| 196 |
link = f'<a href="/deep-diver?ticker={sym}" class="ticker-btn">{sym}</a>'
|
| 197 |
+
|
| 198 |
html_content += f"""
|
| 199 |
<tr class="{row_class}">
|
| 200 |
+
<td style="text-align:center; color:var(--text-dim);" class="mono">#{i+1}</td>
|
| 201 |
+
<td>{link}</td>
|
| 202 |
+
<td style="padding-left:10px;" class="mono">${short_num(token.get('marketcap', 0))}</td>
|
| 203 |
+
<td style="padding-left:10px;" class="mono">${short_num(token.get('volume', 0))}</td>
|
| 204 |
+
<td class="mono {vol_class}" style="padding-left:10px;">{vtmr:.2f}x</td>
|
| 205 |
</tr>
|
| 206 |
"""
|
| 207 |
|
| 208 |
+
html_content += f"""
|
| 209 |
+
</tbody>
|
| 210 |
+
</table>
|
| 211 |
+
</div>
|
| 212 |
+
<div class="footer">
|
| 213 |
+
Report by QuantVat using SpotVolTracker v2.6
|
| 214 |
+
</div>
|
| 215 |
+
</body>
|
| 216 |
+
</html>
|
| 217 |
+
"""
|
| 218 |
|
| 219 |
with open(html_file, "w", encoding="utf-8") as f:
|
| 220 |
f.write(html_content)
|