-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathapp.py
More file actions
498 lines (416 loc) · 19.2 KB
/
Copy pathapp.py
File metadata and controls
498 lines (416 loc) · 19.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
"""
QuantCoach - AI Trading Education Platform
Copyright (c) 2025 Rob Costa
"""
import streamlit as st
import os
import json
from groq import Groq
import time
# Configure Streamlit page
st.set_page_config(
page_title="QuantCoach",
page_icon="📈",
layout="wide",
initial_sidebar_state="expanded"
)
# Add custom CSS for mobile-friendly design
st.markdown("""
<style>
/* Ensure chat messages have proper spacing and don't get cut off */
.stChatMessage {
margin-bottom: 0.5rem !important;
padding: 0.75rem !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
}
/* Ensure text containers have proper height */
.stMarkdown {
max-width: 100% !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
}
/* Desktop: Prevent text cutoff and add space for disclaimer bar */
.main .block-container {
padding-bottom: 5rem !important;
}
/* Ensure sidebar doesn't overlap content */
.main {
margin-left: 0 !important;
}
/* Reduce spacing between elements */
.element-container {
margin-bottom: 0.5rem !important;
}
/* Desktop: Fixed disclaimer bar at bottom */
.disclaimer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #1e1e1e;
border-top: 1px solid #333;
padding: 6px 12px;
text-align: center;
font-size: 0.75em;
color: #ffa500;
z-index: 999;
box-shadow: 0 -2px 10px rgba(0,0,0,0.3);
line-height: 1.3;
}
/* Desktop: Adjust main content margin */
.stApp > div {
margin-bottom: 60px !important;
}
/* Mobile-specific adjustments */
@media (max-width: 768px) {
.disclaimer-bar {
position: relative !important;
bottom: auto !important;
margin-top: 1rem;
margin-bottom: 0;
font-size: 0.65em;
padding: 4px 8px;
z-index: 1;
}
.main .block-container {
padding-bottom: 2rem !important;
}
.stApp > div {
margin-bottom: 0 !important;
}
/* Ensure chat input has proper spacing on mobile */
.stChatInput {
margin-bottom: 1rem !important;
}
/* Mobile sidebar adjustments */
.css-1d391kg {
padding-top: 1rem !important;
}
}
/* Extra small mobile devices */
@media (max-width: 480px) {
.disclaimer-bar {
font-size: 0.6em;
padding: 3px 6px;
}
.stChatMessage {
padding: 0.5rem !important;
margin-bottom: 0.3rem !important;
}
}
</style>
<!-- Disclaimer -->
<div class="disclaimer-bar">
⚠️ Disclaimer: This platform is intended solely for educational and informational purposes. Nothing presented here constitutes financial, investment, or trading advice. Users should always seek guidance from licensed financial professionals before any investment or trading decisions.
</div>
""", unsafe_allow_html=True)
# Initialize Grok client
@st.cache_resource
def init_grok_client():
return Groq(api_key=st.secrets["grok"]["api_key"])
client = init_grok_client()
# Load learning paths data from JSON file
@st.cache_data
def load_learning_paths():
with open('learning_paths.json', 'r') as f:
return json.load(f)
def get_ai_response(message, context="", selected_path="",
selected_topic="", chat_history=[], is_continuation=False):
"""Get response from Grok API"""
try:
# Build detailed context for Grok
context_info = ""
if context:
context_info += f"Current learning context: {context}\n"
if selected_path and selected_path != "Select a path...":
context_info += f"User selected path: {selected_path}\n"
if selected_topic and selected_topic != "Select a topic...":
context_info += f"User focusing on: {selected_topic}\n"
continuation_text = ""
if is_continuation:
continuation_text = ("CRITICAL: Continue from where you left off. "
"Complete the sentence and WRAP UP with a clear "
"conclusion. DO NOT end with '...'")
system_prompt = f"""You are QuantCoach, an AI educational assistant specializing in quantitative trading and finance education.
Your role is to help users LEARN ABOUT quantitative trading concepts, NOT to provide trading advice or perform analysis.
You can help users with:
- Understanding quantitative trading concepts and terminology
- Explaining statistical and mathematical concepts used in trading
- Teaching about machine learning applications in finance
- Educational content about market structures and derivatives
- Explaining backtesting and strategy development concepts
You CANNOT and DO NOT:
- Provide actual trading recommendations or investment advice
- Perform market analysis or predict market movements
- Give specific stock picks or trading signals
- Access real market data or execute trades
{context_info}
{continuation_text}
Keep responses CONSISTENT and moderately sized - aim for exactly 2-3 complete sentences. CRITICALLY IMPORTANT: If you have more to say, you MUST end your response with "..." to indicate continuation is available. Always finish complete sentences - never cut off mid-sentence. Be conversational and informative. Do not add quotations around your responses or repeat yourself."""
# Build message history for context
messages = [{"role": "system", "content": system_prompt}]
# Add recent chat history (last 4 messages for context)
recent_history = (chat_history[-4:] if len(chat_history) > 4
else chat_history)
messages.extend(recent_history)
# Add current message
messages.append({"role": "user", "content": message})
chat_completion = client.chat.completions.create(
messages=messages,
model="llama-3.1-8b-instant",
temperature=0.3,
max_tokens=80,
)
return chat_completion.choices[0].message.content
except Exception as e:
return f"Error: Unable to get AI response. {str(e)}"
# Main app
def main():
# Header
st.title("🎓 QuantCoach: Master Algorithmic Trading with AI Guidance")
st.markdown("*Learn the science behind profitable trading strategies "
"through interactive lessons, real market data, and "
"personalized AI support.*")
# Sidebar
st.sidebar.title("📚 Learning Paths")
learning_paths = load_learning_paths()
# Learning path selection
path_options = ["Select a path..."] + [
path["name"] for path in learning_paths["learning_paths"]
]
selected_path = st.sidebar.selectbox(
"Choose a learning path:", path_options
)
# Track previous selection to auto-trigger explanation
if "previous_topic" not in st.session_state:
st.session_state.previous_topic = "Select a topic..."
# Quick topic selection in sidebar
if selected_path != "Select a path...":
# Find the selected path
current_path = None
for path in learning_paths["learning_paths"]:
if path["name"] == selected_path:
current_path = path
break
if current_path:
st.sidebar.markdown("---")
st.sidebar.subheader("🎯 Topic Explorer")
topic_options = ["Select a topic..."] + [
topic["name"] for topic in current_path["topics"]
]
selected_topic = st.sidebar.selectbox(
"Choose a topic to explore:", topic_options
)
# Auto-explain when topic is selected (different from previous)
if (selected_topic != "Select a topic..." and
selected_topic != st.session_state.previous_topic):
question = (f"Please briefly explain {selected_topic} "
f"in the context of {selected_path}.")
# Add to chat
st.session_state.messages.append({
"role": "user", "content": question
})
# Get AI response with detailed context and chat history
context = (f"Learning path: {selected_path}, "
f"Specific topic: {selected_topic}")
response = get_ai_response(
question, context, selected_path, selected_topic,
st.session_state.messages, False
)
st.session_state.messages.append({
"role": "assistant", "content": response
})
# Update previous topic
st.session_state.previous_topic = selected_topic
# Rerun to update chat
st.rerun()
# Learning buttons in sidebar
if selected_topic != "Select a topic...":
st.sidebar.markdown("**Continue Learning:**")
# Teach me more button
if st.sidebar.button(f"📚 Learn More", key="learn_more"):
more_question = (f"Can you teach me more advanced "
f"concepts about {selected_topic}? "
f"Give me deeper insights and practical "
f"applications.")
# Add to chat
st.session_state.messages.append({
"role": "user", "content": more_question
})
# Get AI response
context = (f"Learning path: {selected_path}, "
f"Advanced topic: {selected_topic}")
response = get_ai_response(
more_question, context, selected_path,
selected_topic, st.session_state.messages, False
)
st.session_state.messages.append({
"role": "assistant", "content": response
})
# Rerun to update chat
st.rerun()
# Quiz section with header
st.sidebar.markdown("**Quiz Me:**")
# Quiz buttons - separate rows
if st.sidebar.button("📝 Multiple Choice", key="quiz_mc"):
quiz_question = (f"Give me a multiple choice quiz "
f"question about {selected_topic} in the "
f"context of {selected_path}. Provide one "
f"question with 4 options (A, B, C, D).")
# Add to chat
st.session_state.messages.append({
"role": "user", "content": quiz_question
})
# Get AI response
context = (f"Learning path: {selected_path}, "
f"Quiz topic: {selected_topic}")
response = get_ai_response(
quiz_question, context, selected_path,
selected_topic, st.session_state.messages, False
)
st.session_state.messages.append({
"role": "assistant", "content": response
})
# Rerun to update chat
st.rerun()
if st.sidebar.button("✍️ Short Answer", key="quiz_sa"):
quiz_question = (f"Give me a short answer quiz question "
f"about {selected_topic} in the context of "
f"{selected_path}. Ask one thought-provoking "
f"question that requires a brief "
f"explanation.")
# Add to chat
st.session_state.messages.append({
"role": "user", "content": quiz_question
})
# Get AI response
context = (f"Learning path: {selected_path}, "
f"Quiz topic: {selected_topic}")
response = get_ai_response(
quiz_question, context, selected_path,
selected_topic, st.session_state.messages, False
)
st.session_state.messages.append({
"role": "assistant", "content": response
})
# Rerun to update chat
st.rerun()
# Add Clear Chat button at bottom of sidebar
st.sidebar.markdown("---")
if st.sidebar.button("🗑️ Clear Chat"):
st.session_state.messages = []
st.rerun()
# Main chat area - full width
st.header("🤖 AI Assistant")
# Chat interface
if "messages" not in st.session_state:
st.session_state.messages = []
# Track continuation count to prevent endless loops
if "continuation_count" not in st.session_state:
st.session_state.continuation_count = 0
# Create a scrollable container for chat messages
chat_container = st.container()
with chat_container:
# Display chat messages (oldest first, newest last)
for i, message in enumerate(st.session_state.messages):
with st.chat_message(message["role"]):
# Add a unique key to prevent rendering issues
st.markdown(message["content"], unsafe_allow_html=False)
# Add "Continue" button for incomplete assistant messages
is_incomplete = (
message["content"].endswith("...") or
message["content"].endswith('"') or # Unclosed quote
message["content"].endswith("(") or # Unclosed parenthesis
not any(message["content"].rstrip().endswith(punct)
for punct in ['.', '!', '?', ':', ';'])
)
if (message["role"] == "assistant" and
is_incomplete and
i == len(st.session_state.messages) - 1): # Last msg only
if st.button("📖 Continue", key=f"continue_{i}"):
# Increment continuation count
st.session_state.continuation_count += 1
# Get the incomplete message content
incomplete_message = message["content"]
# Get continuation
context = ""
if selected_path != "Select a path...":
for path in learning_paths["learning_paths"]:
if path["name"] == selected_path:
topics = [topic["name"]
for topic in path["topics"]]
context = (f"Learning path: {selected_path}. "
f"Available topics: "
f"{', '.join(topics)}")
break
# Progressive wrap-up encouragement without forcing
if st.session_state.continuation_count >= 4:
continuation_prompt = (f"You've been explaining this for "
f"a while. Please complete your "
f"thought and provide a clear "
f"conclusion: {incomplete_message}")
instruction = "Please wrap up with a clear conclusion."
elif st.session_state.continuation_count >= 2:
continuation_prompt = (f"Continue from where you left "
f"off and start bringing this to "
f"a conclusion: {incomplete_message}")
instruction = "Continue and start concluding."
else:
continuation_prompt = (f"Continue from exactly where "
f"this text left off: "
f"{incomplete_message}")
instruction = "Continue your previous response."
# Add explicit instruction to wrap up
temp_messages = st.session_state.messages.copy()
temp_messages.append({
"role": "user",
"content": instruction
})
# Get continuation response
with st.spinner("Continuing..."):
continuation = get_ai_response(
continuation_prompt,
context,
selected_path,
"",
temp_messages,
is_continuation=True
)
# Add continuation to chat
st.session_state.messages.append({
"role": "assistant", "content": continuation
})
st.rerun()
# Chat input at bottom
prompt = st.chat_input("Ask me about quantitative trading concepts...")
if prompt:
# Reset continuation count for new conversations
st.session_state.continuation_count = 0
# Add user message to chat history
st.session_state.messages.append({
"role": "user", "content": prompt
})
# Get context from selected learning path
context = ""
if selected_path != "Select a path...":
for path in learning_paths["learning_paths"]:
if path["name"] == selected_path:
topics = [topic["name"] for topic in path["topics"]]
context = (f"Learning path: {selected_path}. "
f"Available topics: {', '.join(topics)}")
break
# Get AI response with chat history
with st.spinner("Thinking..."):
response = get_ai_response(
prompt, context, selected_path, "",
st.session_state.messages, False
)
# Add assistant response to chat history
st.session_state.messages.append({
"role": "assistant", "content": response
})
# Rerun to show new messages
st.rerun()
if __name__ == "__main__":
main()