LittleMonkeyLab commited on
Commit
eeeb58e
·
verified ·
1 Parent(s): acd4cb0

Upload folder using huggingface_hub

Browse files
.claude/settings.local.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(git pull:*)",
5
+ "Bash(git checkout:*)",
6
+ "Bash(git add:*)",
7
+ "Bash(git commit:*)",
8
+ "Bash(git push:*)",
9
+ "Bash(huggingface-cli whoami:*)",
10
+ "Bash(python3:*)"
11
+ ]
12
+ }
13
+ }
README.md CHANGED
@@ -1,12 +1,169 @@
1
  ---
2
- title: JoshTest2
3
  emoji: 📈
4
- colorFrom: red
5
- colorTo: red
6
  sdk: gradio
7
- sdk_version: 6.2.0
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: TradeVerse AI Trading Experiment
3
  emoji: 📈
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
  ---
12
 
13
+ # TradeVerse AI Trading Experiment
14
+
15
+ A psychology research tool for studying human-AI interaction in financial decision-making.
16
+
17
+ ## Overview
18
+
19
+ This application combines:
20
+ - **Trading Game**: Scenario-based trading decisions in a fictional financial universe
21
+ - **AI Chatbot**: RAG-based advisor with tunable parameters
22
+ - **Experiment Tracking**: Comprehensive data collection for research analysis
23
+
24
+ ## Features
25
+
26
+ ### For Participants
27
+ - Interactive trading scenarios with fictional companies
28
+ - AI advisor that provides recommendations and answers questions
29
+ - Adjustable AI settings (explanation depth, communication style)
30
+ - Real-time portfolio tracking
31
+
32
+ ### For Researchers
33
+ - Pre-defined experimental conditions for A/B testing
34
+ - Hidden parameters (accuracy rate, confidence framing, risk bias)
35
+ - Comprehensive tracking of:
36
+ - Trading decisions and outcomes
37
+ - AI consultation patterns
38
+ - Trust metrics and confidence changes
39
+ - Response timing
40
+ - Data export utilities (CSV, Excel reports)
41
+
42
+ ## Experimental Conditions
43
+
44
+ | Condition | Accuracy | Confidence | Description |
45
+ |-----------|----------|------------|-------------|
46
+ | high_accuracy_high_confidence | 85% | High | Correct AI, assertive |
47
+ | high_accuracy_low_confidence | 85% | Low | Correct AI, hedging |
48
+ | low_accuracy_high_confidence | 40% | High | Often wrong, assertive |
49
+ | low_accuracy_low_confidence | 40% | Low | Often wrong, hedging |
50
+ | conservative_advisor | 70% | Medium | Risk-averse recommendations |
51
+ | aggressive_advisor | 70% | Medium | Risk-seeking recommendations |
52
+ | passive_advisor | 70% | Low proactivity | Rarely initiates |
53
+ | active_advisor | 70% | High proactivity | Frequently initiates |
54
+
55
+ ## Setup
56
+
57
+ ### LLM Provider Options
58
+
59
+ The chatbot supports multiple LLM providers (auto-detected based on available API keys):
60
+
61
+ | Provider | Env Variable | Cost | Notes |
62
+ |----------|--------------|------|-------|
63
+ | **HuggingFace** | `HF_TOKEN` | Free tier available | Recommended for HF Spaces |
64
+ | DeepSeek | `DEEPSEEK_API_KEY` | Pay-per-use | Higher quality |
65
+ | Fallback | None needed | Free | Rule-based, limited |
66
+
67
+ #### Recommended HuggingFace Models (free tier)
68
+
69
+ ```python
70
+ # In chatbot.py, change DEFAULT_HF_MODEL to any of these:
71
+ "Qwen/Qwen2-1.5B-Instruct" # Smallest, fastest
72
+ "microsoft/Phi-3-mini-4k-instruct" # Small, good quality
73
+ "HuggingFaceH4/zephyr-7b-beta" # Default, best quality
74
+ "mistralai/Mistral-7B-Instruct-v0.2" # Popular alternative
75
+ ```
76
+
77
+ ### Environment Variables
78
+
79
+ **Option 1: HuggingFace (Recommended for HF Spaces)**
80
+ ```bash
81
+ export HF_TOKEN="hf_your_token_here"
82
+ ```
83
+ Get your token at: https://huggingface.co/settings/tokens
84
+
85
+ **Option 2: DeepSeek**
86
+ ```bash
87
+ export DEEPSEEK_API_KEY="your-api-key-here"
88
+ ```
89
+
90
+ ### Local Development
91
+
92
+ ```bash
93
+ pip install -r requirements.txt
94
+ python app.py
95
+ ```
96
+
97
+ ### Hugging Face Spaces
98
+
99
+ 1. Create a new Space with Gradio SDK
100
+ 2. Upload all files
101
+ 3. Add `HF_TOKEN` as a secret in Space settings (Settings → Repository secrets)
102
+ - Your HF token works automatically in Spaces
103
+
104
+ ## Data Export
105
+
106
+ ```bash
107
+ # Quick statistics
108
+ python export_data.py stats
109
+
110
+ # Export all data to CSV
111
+ python export_data.py export
112
+
113
+ # Generate full Excel report
114
+ python export_data.py report
115
+ ```
116
+
117
+ ## Project Structure
118
+
119
+ ```
120
+ IntegratedGame/
121
+ ├── app.py # Main Gradio application
122
+ ├── trading.py # Trading engine and portfolio management
123
+ ├── chatbot.py # RAG chatbot with parameter-aware responses
124
+ ├── tracking.py # Database and experiment tracking
125
+ ├── config.py # Scenarios, conditions, parameters
126
+ ├── export_data.py # Data analysis and export
127
+ ├── requirements.txt
128
+ ├── knowledge_base/
129
+ │ ├── companies.txt # Fictional company profiles
130
+ │ ├── market_context.txt # TradeVerse economic backdrop
131
+ │ ├── trading_basics.txt # Trading fundamentals
132
+ │ └── ai_persona.txt # AI behavior guidelines
133
+ └── db/
134
+ └── experiment.db # SQLite database (auto-created)
135
+ ```
136
+
137
+ ## Database Schema
138
+
139
+ ### sessions
140
+ - Participant sessions with portfolio tracking
141
+ - AI reliance metrics
142
+
143
+ ### decisions
144
+ - Individual trading decisions
145
+ - AI parameters at decision time
146
+ - Timing and confidence data
147
+
148
+ ### chat_interactions
149
+ - All AI-participant conversations
150
+ - Proactive vs reactive classification
151
+ - Engagement metrics
152
+
153
+ ### trust_metrics
154
+ - Pre/post advice confidence
155
+ - Advice adherence
156
+ - Outcome tracking
157
+
158
+ ## Research Applications
159
+
160
+ This tool supports research into:
161
+ - Trust calibration in AI advisors
162
+ - Effect of AI confidence framing on decision-making
163
+ - Proactive vs reactive AI assistance preferences
164
+ - Risk perception under AI guidance
165
+ - Learning and adaptation to AI accuracy
166
+
167
+ ## License
168
+
169
+ MIT License
app.py ADDED
@@ -0,0 +1,704 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main Gradio application for the AI Trading Experiment.
3
+ Combines trading game, chatbot, and experiment tracking.
4
+ """
5
+
6
+ import gradio as gr
7
+ import uuid
8
+ import time
9
+ import random
10
+ from datetime import datetime
11
+ from typing import Optional, Tuple, List, Dict, Any
12
+
13
+ from config import (
14
+ ExperimentConfig, DEFAULT_CONFIG, SCENARIOS,
15
+ ParticipantVisibleParams, ResearcherControlledParams,
16
+ EXPERIMENT_CONDITIONS, get_condition
17
+ )
18
+ from trading import TradingEngine, format_currency, format_percentage
19
+ from chatbot import TradingChatbot, ChatResponse
20
+ from tracking import (
21
+ ExperimentTracker, DecisionRecord, ChatInteraction,
22
+ tracker
23
+ )
24
+
25
+
26
+ # Global state management
27
+ class SessionState:
28
+ """Manages session state for each participant."""
29
+ def __init__(self):
30
+ self.reset()
31
+
32
+ def reset(self):
33
+ self.participant_id: Optional[str] = None
34
+ self.trading_engine: Optional[TradingEngine] = None
35
+ self.chatbot: Optional[TradingChatbot] = None
36
+ self.visible_params = ParticipantVisibleParams()
37
+ self.hidden_params = ResearcherControlledParams()
38
+ self.current_scenario = None
39
+ self.scenario_start_time: float = 0
40
+ self.ai_advice_shown_time: float = 0
41
+ self.proactive_advice_shown: bool = False
42
+ self.proactive_advice_engaged: bool = False
43
+ self.chat_queries_this_scenario: int = 0
44
+ self.pre_advice_confidence: int = 50
45
+
46
+
47
+ # Initialize components
48
+ session = SessionState()
49
+
50
+
51
+ def get_random_condition() -> ResearcherControlledParams:
52
+ """Get a random experimental condition for A/B testing."""
53
+ condition_names = list(EXPERIMENT_CONDITIONS.keys())
54
+ selected = random.choice(condition_names)
55
+ return get_condition(selected)
56
+
57
+
58
+ def start_experiment(condition_dropdown: str) -> Tuple:
59
+ """Initialize a new experiment session."""
60
+ session.reset()
61
+
62
+ # Set experimental condition
63
+ if condition_dropdown == "Random (A/B Testing)":
64
+ session.hidden_params = get_random_condition()
65
+ else:
66
+ session.hidden_params = get_condition(condition_dropdown)
67
+
68
+ # Create participant session
69
+ session.participant_id = tracker.create_session(
70
+ condition_name=session.hidden_params.condition_name,
71
+ initial_portfolio=DEFAULT_CONFIG.initial_portfolio_value
72
+ )
73
+
74
+ # Initialize trading engine
75
+ session.trading_engine = TradingEngine(DEFAULT_CONFIG)
76
+ portfolio, first_scenario = session.trading_engine.start_new_game()
77
+
78
+ # Initialize chatbot
79
+ session.chatbot = TradingChatbot()
80
+ session.chatbot.clear_history()
81
+
82
+ # Set current scenario
83
+ session.current_scenario = first_scenario
84
+ session.scenario_start_time = time.time()
85
+
86
+ # Generate proactive advice (maybe)
87
+ proactive_message = ""
88
+ session.proactive_advice_shown = False
89
+
90
+ proactive_response = session.chatbot.generate_proactive_advice(
91
+ first_scenario,
92
+ session.visible_params,
93
+ session.hidden_params
94
+ )
95
+
96
+ if proactive_response:
97
+ proactive_message = f"**AI Advisor:** {proactive_response.message}"
98
+ session.proactive_advice_shown = True
99
+ session.ai_advice_shown_time = time.time()
100
+
101
+ # Record proactive interaction
102
+ tracker.record_chat_interaction(ChatInteraction(
103
+ interaction_id=str(uuid.uuid4())[:12],
104
+ participant_id=session.participant_id,
105
+ timestamp=datetime.now().isoformat(),
106
+ scenario_id=first_scenario.scenario_id,
107
+ interaction_type="proactive",
108
+ user_query=None,
109
+ ai_response=proactive_response.message,
110
+ explanation_depth=session.visible_params.explanation_depth,
111
+ communication_style=session.visible_params.communication_style,
112
+ confidence_framing=session.hidden_params.confidence_framing,
113
+ risk_bias=session.hidden_params.risk_bias,
114
+ response_time_ms=0,
115
+ user_engaged=False,
116
+ dismissed=False
117
+ ))
118
+
119
+ # Generate main AI recommendation
120
+ ai_recommendation = session.chatbot.generate_ai_recommendation(
121
+ first_scenario,
122
+ session.visible_params,
123
+ session.hidden_params
124
+ )
125
+
126
+ # Format scenario display
127
+ scenario_text = format_scenario_display(first_scenario)
128
+ progress = session.trading_engine.get_progress_info()
129
+
130
+ return (
131
+ gr.update(visible=False), # Hide welcome
132
+ gr.update(visible=True), # Show game
133
+ gr.update(visible=False), # Hide results
134
+ f"Participant: {session.participant_id}",
135
+ format_currency(portfolio.total_value),
136
+ f"Scenario {progress['current_scenario']} of {progress['total_scenarios']}",
137
+ scenario_text,
138
+ f"**AI Recommendation:**\n\n{ai_recommendation.message}",
139
+ proactive_message,
140
+ [], # Clear chat history
141
+ gr.update(value=50), # Reset confidence slider
142
+ gr.update(value="HOLD"), # Reset decision
143
+ )
144
+
145
+
146
+ def format_scenario_display(scenario) -> str:
147
+ """Format a scenario for display."""
148
+ return f"""
149
+ ## {scenario.company_name} ({scenario.company_symbol})
150
+
151
+ **Sector:** {scenario.sector}
152
+ **Country:** {scenario.country}
153
+ **Current Price:** {scenario.current_price} credits
154
+
155
+ ---
156
+
157
+ ### Situation
158
+
159
+ {scenario.situation_description}
160
+ """
161
+
162
+
163
+ def update_ai_params(explanation_depth: int, communication_style: int):
164
+ """Update visible AI parameters and regenerate advice."""
165
+ session.visible_params.explanation_depth = explanation_depth
166
+ session.visible_params.communication_style = communication_style
167
+
168
+ if session.current_scenario and session.chatbot:
169
+ # Regenerate recommendation with new params
170
+ ai_recommendation = session.chatbot.generate_ai_recommendation(
171
+ session.current_scenario,
172
+ session.visible_params,
173
+ session.hidden_params
174
+ )
175
+ return f"**AI Recommendation:**\n\n{ai_recommendation.message}"
176
+
177
+ return ""
178
+
179
+
180
+ def handle_chat_query(
181
+ message: str,
182
+ chat_history: List[Tuple[str, str]]
183
+ ) -> Tuple[List[Tuple[str, str]], str]:
184
+ """Handle a user query to the chatbot."""
185
+ if not message.strip() or not session.chatbot:
186
+ return chat_history, ""
187
+
188
+ query_start = time.time()
189
+
190
+ response = session.chatbot.answer_query(
191
+ message,
192
+ session.current_scenario,
193
+ session.visible_params,
194
+ session.hidden_params
195
+ )
196
+
197
+ response_time_ms = int((time.time() - query_start) * 1000)
198
+
199
+ # Record interaction
200
+ if session.participant_id:
201
+ tracker.record_chat_interaction(ChatInteraction(
202
+ interaction_id=str(uuid.uuid4())[:12],
203
+ participant_id=session.participant_id,
204
+ timestamp=datetime.now().isoformat(),
205
+ scenario_id=session.current_scenario.scenario_id if session.current_scenario else None,
206
+ interaction_type="reactive_query",
207
+ user_query=message,
208
+ ai_response=response.message,
209
+ explanation_depth=session.visible_params.explanation_depth,
210
+ communication_style=session.visible_params.communication_style,
211
+ confidence_framing=session.hidden_params.confidence_framing,
212
+ risk_bias=session.hidden_params.risk_bias,
213
+ response_time_ms=response_time_ms,
214
+ user_engaged=True,
215
+ dismissed=False
216
+ ))
217
+
218
+ session.chat_queries_this_scenario += 1
219
+
220
+ chat_history.append((message, response.message))
221
+ return chat_history, ""
222
+
223
+
224
+ def engage_proactive_advice():
225
+ """Mark that user engaged with proactive advice."""
226
+ session.proactive_advice_engaged = True
227
+ return "You engaged with the AI's initial observation."
228
+
229
+
230
+ def dismiss_proactive_advice():
231
+ """Mark that user dismissed proactive advice."""
232
+ session.proactive_advice_engaged = False
233
+ return gr.update(visible=False)
234
+
235
+
236
+ def submit_decision(
237
+ decision: str,
238
+ confidence: int,
239
+ trade_amount: float
240
+ ) -> Tuple:
241
+ """Process the participant's trading decision."""
242
+ if not session.trading_engine or not session.current_scenario:
243
+ return (gr.update(),) * 8
244
+
245
+ # Calculate timing
246
+ decision_time_ms = int((time.time() - session.scenario_start_time) * 1000)
247
+ ai_viewing_time_ms = int((time.time() - session.ai_advice_shown_time) * 1000) if session.ai_advice_shown_time > 0 else 0
248
+
249
+ # Process the decision
250
+ outcome = session.trading_engine.process_decision(
251
+ session.current_scenario,
252
+ decision,
253
+ trade_amount,
254
+ session.current_scenario.ai_recommendation
255
+ )
256
+
257
+ # Record decision
258
+ tracker.record_decision(DecisionRecord(
259
+ decision_id=str(uuid.uuid4())[:12],
260
+ participant_id=session.participant_id,
261
+ timestamp=datetime.now().isoformat(),
262
+ scenario_id=session.current_scenario.scenario_id,
263
+ company_symbol=session.current_scenario.company_symbol,
264
+ explanation_depth=session.visible_params.explanation_depth,
265
+ communication_style=session.visible_params.communication_style,
266
+ confidence_framing=session.hidden_params.confidence_framing,
267
+ risk_bias=session.hidden_params.risk_bias,
268
+ ai_recommendation=session.current_scenario.ai_recommendation,
269
+ ai_was_correct=session.current_scenario.ai_is_correct,
270
+ participant_decision=decision,
271
+ followed_ai=outcome.followed_ai,
272
+ decision_confidence=confidence,
273
+ time_to_decision_ms=decision_time_ms,
274
+ time_viewing_ai_advice_ms=ai_viewing_time_ms,
275
+ outcome_percentage=outcome.outcome_percentage,
276
+ portfolio_before=outcome.portfolio_before,
277
+ portfolio_after=outcome.portfolio_after,
278
+ trade_amount=trade_amount,
279
+ proactive_advice_shown=session.proactive_advice_shown,
280
+ proactive_advice_engaged=session.proactive_advice_engaged
281
+ ))
282
+
283
+ # Record trust metrics
284
+ tracker.record_trust_metric(
285
+ participant_id=session.participant_id,
286
+ scenario_id=session.current_scenario.scenario_id,
287
+ pre_confidence=session.pre_advice_confidence,
288
+ post_confidence=confidence,
289
+ advice_followed=outcome.followed_ai,
290
+ time_deliberating_ms=decision_time_ms,
291
+ queries_before_decision=session.chat_queries_this_scenario,
292
+ outcome_positive=(outcome.outcome_percentage > 0)
293
+ )
294
+
295
+ # Format outcome message
296
+ outcome_color = "green" if outcome.outcome_percentage > 0 else "red"
297
+ outcome_message = f"""
298
+ ### Decision Outcome
299
+
300
+ You chose to **{decision}** with {trade_amount:,.0f} credits.
301
+
302
+ **Result:** {format_percentage(outcome.outcome_percentage)} ({format_currency(outcome.outcome_amount)})
303
+
304
+ **Portfolio:** {format_currency(outcome.portfolio_after)}
305
+ """
306
+
307
+ # Check if game is complete
308
+ if session.trading_engine.is_game_complete():
309
+ return show_results()
310
+
311
+ # Move to next scenario
312
+ next_scenario = session.trading_engine.get_next_scenario()
313
+ session.current_scenario = next_scenario
314
+ session.scenario_start_time = time.time()
315
+ session.ai_advice_shown_time = 0
316
+ session.proactive_advice_shown = False
317
+ session.proactive_advice_engaged = False
318
+ session.chat_queries_this_scenario = 0
319
+ session.chatbot.clear_history()
320
+
321
+ # Generate content for next scenario
322
+ scenario_text = format_scenario_display(next_scenario)
323
+
324
+ # Maybe show proactive advice
325
+ proactive_message = ""
326
+ proactive_response = session.chatbot.generate_proactive_advice(
327
+ next_scenario,
328
+ session.visible_params,
329
+ session.hidden_params
330
+ )
331
+
332
+ if proactive_response:
333
+ proactive_message = f"**AI Advisor:** {proactive_response.message}"
334
+ session.proactive_advice_shown = True
335
+ session.ai_advice_shown_time = time.time()
336
+
337
+ tracker.record_chat_interaction(ChatInteraction(
338
+ interaction_id=str(uuid.uuid4())[:12],
339
+ participant_id=session.participant_id,
340
+ timestamp=datetime.now().isoformat(),
341
+ scenario_id=next_scenario.scenario_id,
342
+ interaction_type="proactive",
343
+ user_query=None,
344
+ ai_response=proactive_response.message,
345
+ explanation_depth=session.visible_params.explanation_depth,
346
+ communication_style=session.visible_params.communication_style,
347
+ confidence_framing=session.hidden_params.confidence_framing,
348
+ risk_bias=session.hidden_params.risk_bias,
349
+ response_time_ms=0,
350
+ user_engaged=False,
351
+ dismissed=False
352
+ ))
353
+
354
+ # Generate main recommendation
355
+ ai_recommendation = session.chatbot.generate_ai_recommendation(
356
+ next_scenario,
357
+ session.visible_params,
358
+ session.hidden_params
359
+ )
360
+ session.ai_advice_shown_time = time.time()
361
+
362
+ progress = session.trading_engine.get_progress_info()
363
+
364
+ return (
365
+ gr.update(visible=True), # Keep game visible
366
+ gr.update(visible=False), # Keep results hidden
367
+ format_currency(session.trading_engine.portfolio.total_value),
368
+ f"Scenario {progress['current_scenario']} of {progress['total_scenarios']}",
369
+ scenario_text,
370
+ f"**AI Recommendation:**\n\n{ai_recommendation.message}",
371
+ proactive_message,
372
+ [], # Clear chat
373
+ gr.update(value=50),
374
+ gr.update(value="HOLD"),
375
+ outcome_message
376
+ )
377
+
378
+
379
+ def show_results() -> Tuple:
380
+ """Show the final results screen."""
381
+ if not session.trading_engine:
382
+ return (gr.update(),) * 11
383
+
384
+ # Complete the session
385
+ summary = session.trading_engine.get_game_summary()
386
+ tracker.complete_session(
387
+ session.participant_id,
388
+ summary["final_portfolio"]
389
+ )
390
+
391
+ # Get full session summary
392
+ session_summary = tracker.get_session_summary(session.participant_id)
393
+
394
+ results_text = f"""
395
+ # Experiment Complete
396
+
397
+ Thank you for participating!
398
+
399
+ ---
400
+
401
+ ## Your Results
402
+
403
+ **Participant ID:** {session.participant_id}
404
+ (Save this ID if you need to reference your data)
405
+
406
+ ---
407
+
408
+ ### Portfolio Performance
409
+
410
+ | Metric | Value |
411
+ |--------|-------|
412
+ | Starting Portfolio | {format_currency(summary['initial_portfolio'])} |
413
+ | Final Portfolio | {format_currency(summary['final_portfolio'])} |
414
+ | Total Return | {format_currency(summary['total_return'])} |
415
+ | Return Percentage | {summary['return_percentage']:.1f}% |
416
+
417
+ ---
418
+
419
+ ### Decision Analysis
420
+
421
+ | Metric | Value |
422
+ |--------|-------|
423
+ | Total Decisions | {summary['total_decisions']} |
424
+ | AI Follow Rate | {summary['ai_follow_rate']*100:.1f}% |
425
+ | Optimal Decision Rate | {summary['optimal_decision_rate']*100:.1f}% |
426
+
427
+ ---
428
+
429
+ ### AI Interaction Summary
430
+
431
+ | Metric | Value |
432
+ |--------|-------|
433
+ | Times Followed Correct AI | {summary['followed_correct_ai']} / {summary['ai_correct_scenarios']} |
434
+ | Times Followed Incorrect AI | {summary['followed_incorrect_ai']} / {summary['ai_incorrect_scenarios']} |
435
+ | Chat Queries Made | {session_summary.get('total_chat_queries', 0)} |
436
+
437
+ ---
438
+
439
+ ### Decision History
440
+
441
+ """
442
+
443
+ for d in summary['decisions']:
444
+ followed = "Followed AI" if d['followed_ai'] else "Disagreed with AI"
445
+ optimal = "Optimal" if d['was_optimal'] else "Suboptimal"
446
+ results_text += f"- **{d['scenario']}**: {d['decision']} → {d['outcome']} ({followed}, {optimal})\n"
447
+
448
+ return (
449
+ gr.update(visible=False), # Hide game
450
+ gr.update(visible=True), # Show results
451
+ results_text,
452
+ "", "", "", "", "", [], gr.update(), gr.update(), ""
453
+ )
454
+
455
+
456
+ # Build the Gradio interface
457
+ def create_app():
458
+ """Create and return the Gradio application."""
459
+
460
+ with gr.Blocks(
461
+ title="TradeVerse AI Experiment",
462
+ theme=gr.themes.Soft(),
463
+ css="""
464
+ .container { max-width: 1200px; margin: auto; }
465
+ .scenario-box { background: #f8f9fa; padding: 20px; border-radius: 10px; }
466
+ .ai-advice { background: #e3f2fd; padding: 15px; border-radius: 8px; border-left: 4px solid #2196f3; }
467
+ .proactive-advice { background: #fff3e0; padding: 15px; border-radius: 8px; border-left: 4px solid #ff9800; }
468
+ .outcome-box { padding: 15px; border-radius: 8px; margin-top: 10px; }
469
+ """
470
+ ) as app:
471
+
472
+ # ==================== WELCOME SCREEN ====================
473
+ with gr.Column(visible=True) as welcome_section:
474
+ gr.Markdown("""
475
+ # TradeVerse AI Trading Experiment
476
+
477
+ Welcome to this research study on AI-assisted decision making.
478
+
479
+ ---
480
+
481
+ ## About This Experiment
482
+
483
+ You will participate in a simulated trading game set in the **TradeVerse**, a fictional
484
+ financial universe. You will make trading decisions for several companies while receiving
485
+ advice from an AI assistant.
486
+
487
+ **Important:** All companies, markets, and situations are fictional. No real-world
488
+ financial knowledge is required or applicable.
489
+
490
+ ---
491
+
492
+ ## What You'll Do
493
+
494
+ 1. **Review Trading Scenarios** - Each scenario presents a company and market situation
495
+ 2. **Consult the AI** - An AI advisor will offer recommendations and answer questions
496
+ 3. **Make Decisions** - Choose to BUY, SELL, or HOLD for each scenario
497
+ 4. **See Outcomes** - Learn the results of your decisions
498
+
499
+ ---
500
+
501
+ ## Data Collection
502
+
503
+ This experiment collects:
504
+ - Your trading decisions and confidence levels
505
+ - Your interactions with the AI advisor
506
+ - Timing information
507
+
508
+ All data is anonymized. Your participant ID contains no personal information.
509
+
510
+ ---
511
+
512
+ ## Consent
513
+
514
+ By clicking "Start Experiment", you consent to participate in this research study.
515
+ You may stop at any time.
516
+ """)
517
+
518
+ with gr.Row():
519
+ condition_dropdown = gr.Dropdown(
520
+ choices=["Random (A/B Testing)"] + list(EXPERIMENT_CONDITIONS.keys()),
521
+ value="Random (A/B Testing)",
522
+ label="Experimental Condition (Researcher Use)",
523
+ visible=True # Set to False in production
524
+ )
525
+
526
+ start_btn = gr.Button("Start Experiment", variant="primary", size="lg")
527
+
528
+ # ==================== GAME SCREEN ====================
529
+ with gr.Column(visible=False) as game_section:
530
+
531
+ # Status bar
532
+ with gr.Row():
533
+ participant_display = gr.Markdown("Participant: ---")
534
+ portfolio_display = gr.Markdown("Portfolio: ---")
535
+ progress_display = gr.Markdown("Progress: ---")
536
+
537
+ with gr.Row():
538
+ # Left column: Scenario and Decision
539
+ with gr.Column(scale=2):
540
+ scenario_display = gr.Markdown(
541
+ "Loading scenario...",
542
+ elem_classes=["scenario-box"]
543
+ )
544
+
545
+ # AI Recommendation
546
+ ai_recommendation_display = gr.Markdown(
547
+ "",
548
+ elem_classes=["ai-advice"]
549
+ )
550
+
551
+ # Proactive advice (may or may not show)
552
+ proactive_display = gr.Markdown(
553
+ "",
554
+ elem_classes=["proactive-advice"]
555
+ )
556
+
557
+ # Decision controls
558
+ gr.Markdown("### Your Decision")
559
+
560
+ with gr.Row():
561
+ decision_radio = gr.Radio(
562
+ choices=["BUY", "HOLD", "SELL"],
563
+ value="HOLD",
564
+ label="Action"
565
+ )
566
+ trade_amount = gr.Slider(
567
+ minimum=1000,
568
+ maximum=50000,
569
+ value=10000,
570
+ step=1000,
571
+ label="Trade Amount (credits)"
572
+ )
573
+
574
+ confidence_slider = gr.Slider(
575
+ minimum=0,
576
+ maximum=100,
577
+ value=50,
578
+ step=5,
579
+ label="How confident are you in this decision?"
580
+ )
581
+
582
+ submit_btn = gr.Button("Submit Decision", variant="primary")
583
+
584
+ outcome_display = gr.Markdown("")
585
+
586
+ # Right column: AI Chat and Controls
587
+ with gr.Column(scale=1):
588
+ gr.Markdown("### AI Advisor Settings")
589
+
590
+ explanation_slider = gr.Slider(
591
+ minimum=0,
592
+ maximum=100,
593
+ value=50,
594
+ step=10,
595
+ label="Explanation Depth",
596
+ info="Minimal ← → Detailed"
597
+ )
598
+
599
+ style_slider = gr.Slider(
600
+ minimum=0,
601
+ maximum=100,
602
+ value=50,
603
+ step=10,
604
+ label="Communication Style",
605
+ info="Formal ← → Casual"
606
+ )
607
+
608
+ update_params_btn = gr.Button("Update AI Settings", size="sm")
609
+
610
+ gr.Markdown("### Ask the AI")
611
+
612
+ chatbot_display = gr.Chatbot(
613
+ label="Chat with AI Advisor",
614
+ height=300
615
+ )
616
+
617
+ chat_input = gr.Textbox(
618
+ placeholder="Ask a question about this scenario...",
619
+ label="Your Question",
620
+ lines=2
621
+ )
622
+
623
+ chat_btn = gr.Button("Send", size="sm")
624
+
625
+ # ==================== RESULTS SCREEN ====================
626
+ with gr.Column(visible=False) as results_section:
627
+ results_display = gr.Markdown("Loading results...")
628
+
629
+ restart_btn = gr.Button("Start New Session", variant="primary")
630
+
631
+ # ==================== EVENT HANDLERS ====================
632
+
633
+ # Start experiment
634
+ start_btn.click(
635
+ fn=start_experiment,
636
+ inputs=[condition_dropdown],
637
+ outputs=[
638
+ welcome_section,
639
+ game_section,
640
+ results_section,
641
+ participant_display,
642
+ portfolio_display,
643
+ progress_display,
644
+ scenario_display,
645
+ ai_recommendation_display,
646
+ proactive_display,
647
+ chatbot_display,
648
+ confidence_slider,
649
+ decision_radio
650
+ ]
651
+ )
652
+
653
+ # Update AI parameters
654
+ update_params_btn.click(
655
+ fn=update_ai_params,
656
+ inputs=[explanation_slider, style_slider],
657
+ outputs=[ai_recommendation_display]
658
+ )
659
+
660
+ # Chat interaction
661
+ chat_btn.click(
662
+ fn=handle_chat_query,
663
+ inputs=[chat_input, chatbot_display],
664
+ outputs=[chatbot_display, chat_input]
665
+ )
666
+
667
+ chat_input.submit(
668
+ fn=handle_chat_query,
669
+ inputs=[chat_input, chatbot_display],
670
+ outputs=[chatbot_display, chat_input]
671
+ )
672
+
673
+ # Submit decision
674
+ submit_btn.click(
675
+ fn=submit_decision,
676
+ inputs=[decision_radio, confidence_slider, trade_amount],
677
+ outputs=[
678
+ game_section,
679
+ results_section,
680
+ portfolio_display,
681
+ progress_display,
682
+ scenario_display,
683
+ ai_recommendation_display,
684
+ proactive_display,
685
+ chatbot_display,
686
+ confidence_slider,
687
+ decision_radio,
688
+ outcome_display
689
+ ]
690
+ )
691
+
692
+ # Restart
693
+ restart_btn.click(
694
+ fn=lambda: (gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)),
695
+ outputs=[welcome_section, game_section, results_section]
696
+ )
697
+
698
+ return app
699
+
700
+
701
+ # Launch the application
702
+ if __name__ == "__main__":
703
+ app = create_app()
704
+ app.launch(debug=True)
chatbot.py ADDED
@@ -0,0 +1,623 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Chatbot engine with RAG pipeline and proactive/reactive logic.
3
+ Adapted for the AI Trading Experiment with parameter-aware responses.
4
+
5
+ Supports multiple LLM providers:
6
+ - HuggingFace Inference API (free tier available)
7
+ - DeepSeek API
8
+ - Fallback rule-based responses
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import random
14
+ import requests
15
+ from typing import Optional, List, Tuple, Dict, Any
16
+ from dataclasses import dataclass
17
+ from enum import Enum
18
+
19
+ # Attempt pysqlite3 workaround for HF Spaces
20
+ try:
21
+ __import__('pysqlite3')
22
+ sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
23
+ except ImportError:
24
+ pass # Not in HF Spaces, use default sqlite3
25
+
26
+ # LangChain imports - using langchain_community for newer versions
27
+ try:
28
+ # Try newer import paths first (langchain >= 0.1.0)
29
+ from langchain_community.document_loaders import TextLoader
30
+ from langchain_community.vectorstores import Chroma
31
+ from langchain_community.embeddings import HuggingFaceEmbeddings
32
+ from langchain.text_splitter import CharacterTextSplitter
33
+ from langchain_core.language_models.llms import LLM
34
+ except ImportError:
35
+ # Fall back to older import paths (langchain < 0.1.0)
36
+ from langchain.document_loaders import TextLoader
37
+ from langchain.vectorstores import Chroma
38
+ from langchain.embeddings import HuggingFaceEmbeddings
39
+ from langchain.text_splitter import CharacterTextSplitter
40
+ from langchain.llms.base import LLM
41
+
42
+ from config import Scenario, ResearcherControlledParams, ParticipantVisibleParams
43
+
44
+
45
+ # Configuration
46
+ KNOWLEDGE_BASE_DIR = "knowledge_base"
47
+ VECTOR_DB_DIR = "db/vectorstore"
48
+
49
+
50
+ class LLMProvider(Enum):
51
+ """Available LLM providers."""
52
+ HUGGINGFACE = "huggingface"
53
+ DEEPSEEK = "deepseek"
54
+ FALLBACK = "fallback"
55
+
56
+
57
+ # ==================== LLM Provider Selection ====================
58
+
59
+ # Check which API keys are available and select provider
60
+ def get_llm_provider() -> LLMProvider:
61
+ """Determine which LLM provider to use based on available credentials."""
62
+ if os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN") or os.getenv("HF_API_KEY"):
63
+ return LLMProvider.HUGGINGFACE
64
+ elif os.getenv("DEEPSEEK_API_KEY"):
65
+ return LLMProvider.DEEPSEEK
66
+ else:
67
+ print("Warning: No LLM API key found. Using fallback responses.")
68
+ print("Set HF_TOKEN for HuggingFace or DEEPSEEK_API_KEY for DeepSeek.")
69
+ return LLMProvider.FALLBACK
70
+
71
+
72
+ # ==================== HuggingFace LLM ====================
73
+
74
+ # Recommended free/cheap models (smallest to largest):
75
+ # - "microsoft/Phi-3-mini-4k-instruct" # 3.8B params, very fast
76
+ # - "Qwen/Qwen2-1.5B-Instruct" # 1.5B params, smallest
77
+ # - "HuggingFaceH4/zephyr-7b-beta" # 7B params, good quality
78
+ # - "mistralai/Mistral-7B-Instruct-v0.2" # 7B params, popular
79
+ # - "meta-llama/Llama-2-7b-chat-hf" # 7B params, requires approval
80
+
81
+ DEFAULT_HF_MODEL = "HuggingFaceH4/zephyr-7b-beta"
82
+
83
+
84
+ class HuggingFaceLLM(LLM):
85
+ """LLM wrapper for HuggingFace Inference API (free tier available)."""
86
+ api_key: str = ""
87
+ model_id: str = DEFAULT_HF_MODEL
88
+ temperature: float = 0.7
89
+ max_tokens: int = 512
90
+
91
+ def __init__(self, model_id: str = None, **kwargs):
92
+ super().__init__(**kwargs)
93
+ # Try multiple possible env var names
94
+ self.api_key = (
95
+ os.getenv("HF_TOKEN") or
96
+ os.getenv("HUGGINGFACE_TOKEN") or
97
+ os.getenv("HF_API_KEY") or
98
+ ""
99
+ )
100
+ if model_id:
101
+ self.model_id = model_id
102
+
103
+ if self.api_key:
104
+ print(f"Using HuggingFace model: {self.model_id}")
105
+ else:
106
+ print("Warning: No HuggingFace token found.")
107
+
108
+ def _call(self, prompt: str, stop: Optional[List[str]] = None, **kwargs) -> str:
109
+ if not self.api_key:
110
+ return self._fallback_response(prompt)
111
+
112
+ # HuggingFace Inference API endpoint
113
+ api_url = f"https://api-inference.huggingface.co/models/{self.model_id}"
114
+
115
+ headers = {
116
+ "Authorization": f"Bearer {self.api_key}",
117
+ "Content-Type": "application/json"
118
+ }
119
+
120
+ # Format prompt for instruction-tuned models
121
+ formatted_prompt = f"""<|system|>
122
+ You are an AI trading advisor in the TradeVerse financial ecosystem. Provide helpful, concise advice.</s>
123
+ <|user|>
124
+ {prompt}</s>
125
+ <|assistant|>
126
+ """
127
+
128
+ payload = {
129
+ "inputs": formatted_prompt,
130
+ "parameters": {
131
+ "max_new_tokens": self.max_tokens,
132
+ "temperature": self.temperature,
133
+ "do_sample": True,
134
+ "return_full_text": False
135
+ }
136
+ }
137
+
138
+ try:
139
+ response = requests.post(api_url, headers=headers, json=payload, timeout=60)
140
+
141
+ # Handle model loading (HF free tier may need to load model)
142
+ if response.status_code == 503:
143
+ data = response.json()
144
+ wait_time = data.get("estimated_time", 20)
145
+ print(f"Model loading, waiting {wait_time}s...")
146
+ import time
147
+ time.sleep(min(wait_time, 30))
148
+ response = requests.post(api_url, headers=headers, json=payload, timeout=60)
149
+
150
+ response.raise_for_status()
151
+ data = response.json()
152
+
153
+ # Handle different response formats
154
+ if isinstance(data, list) and len(data) > 0:
155
+ return data[0].get("generated_text", "").strip()
156
+ elif isinstance(data, dict):
157
+ return data.get("generated_text", "").strip()
158
+
159
+ return self._fallback_response(prompt)
160
+
161
+ except Exception as e:
162
+ print(f"HuggingFace API error: {e}")
163
+ return self._fallback_response(prompt)
164
+
165
+ def _fallback_response(self, prompt: str) -> str:
166
+ """Generate a basic response when API is unavailable."""
167
+ return FallbackLLM()._call(prompt)
168
+
169
+ @property
170
+ def _llm_type(self) -> str:
171
+ return "huggingface_inference"
172
+
173
+
174
+ # ==================== DeepSeek LLM ====================
175
+
176
+ DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions"
177
+
178
+
179
+ class DeepSeekLLM(LLM):
180
+ """LLM wrapper for DeepSeek API."""
181
+ api_key: str = ""
182
+ temperature: float = 0.7
183
+ max_tokens: int = 512
184
+
185
+ def __init__(self, **kwargs):
186
+ super().__init__(**kwargs)
187
+ self.api_key = os.getenv("DEEPSEEK_API_KEY", "")
188
+ if self.api_key:
189
+ print("Using DeepSeek API")
190
+
191
+ def _call(self, prompt: str, stop: Optional[List[str]] = None, **kwargs) -> str:
192
+ if not self.api_key:
193
+ return FallbackLLM()._call(prompt)
194
+
195
+ headers = {
196
+ "Authorization": f"Bearer {self.api_key}",
197
+ "Content-Type": "application/json"
198
+ }
199
+ payload = {
200
+ "model": "deepseek-chat",
201
+ "messages": [
202
+ {"role": "system", "content": "You are an AI trading advisor in the TradeVerse financial ecosystem."},
203
+ {"role": "user", "content": prompt}
204
+ ],
205
+ "temperature": self.temperature,
206
+ "max_tokens": self.max_tokens
207
+ }
208
+
209
+ try:
210
+ response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=30)
211
+ response.raise_for_status()
212
+ data = response.json()
213
+ return data["choices"][0]["message"]["content"].strip()
214
+ except Exception as e:
215
+ print(f"DeepSeek API error: {e}")
216
+ return FallbackLLM()._call(prompt)
217
+
218
+ @property
219
+ def _llm_type(self) -> str:
220
+ return "deepseek_api"
221
+
222
+
223
+ # ==================== Fallback LLM (Rule-based) ====================
224
+
225
+ class FallbackLLM(LLM):
226
+ """
227
+ Rule-based fallback when no API is available.
228
+ Generates responses based on scenario context and parameters.
229
+ """
230
+
231
+ def _call(self, prompt: str, stop: Optional[List[str]] = None, **kwargs) -> str:
232
+ """Generate a context-aware response without an LLM."""
233
+ prompt_lower = prompt.lower()
234
+
235
+ # Detect recommendation requests
236
+ if "buy" in prompt_lower and "recommend" in prompt_lower:
237
+ return self._generate_buy_response(prompt)
238
+ elif "sell" in prompt_lower and "recommend" in prompt_lower:
239
+ return self._generate_sell_response(prompt)
240
+ elif "hold" in prompt_lower and "recommend" in prompt_lower:
241
+ return self._generate_hold_response(prompt)
242
+
243
+ # Detect question types
244
+ if "risk" in prompt_lower:
245
+ return "When evaluating risk, consider the company's debt levels, market volatility, and any red flags like insider selling or unusual trading volume. The current scenario presents factors that warrant careful consideration."
246
+
247
+ if "insider" in prompt_lower or "trading volume" in prompt_lower:
248
+ return "Unusual insider activity or trading volume can signal that informed parties have information not yet public. This is often a warning sign that warrants caution."
249
+
250
+ if "sector" in prompt_lower or "industry" in prompt_lower:
251
+ return "Sector trends significantly impact individual companies. Consider broader market conditions, regulatory environment, and competitive dynamics when making your decision."
252
+
253
+ # Default analytical response
254
+ return "Based on the available information, I'd encourage you to weigh the key factors mentioned in the scenario. Consider both the potential opportunities and the risk factors before making your decision."
255
+
256
+ def _generate_buy_response(self, prompt: str) -> str:
257
+ return "Based on my analysis, buying could be appropriate here. The positive signals suggest potential upside, though you should consider your risk tolerance and the size of your position carefully."
258
+
259
+ def _generate_sell_response(self, prompt: str) -> str:
260
+ return "Based on my analysis, selling may be prudent. The risk factors present suggest potential downside that could outweigh staying invested. Consider protecting your capital."
261
+
262
+ def _generate_hold_response(self, prompt: str) -> str:
263
+ return "Based on my analysis, holding your position seems reasonable. The situation shows mixed signals, and waiting for more clarity before acting could be the wisest approach."
264
+
265
+ @property
266
+ def _llm_type(self) -> str:
267
+ return "fallback_rules"
268
+
269
+
270
+ # ==================== LLM Factory ====================
271
+
272
+ def create_llm(provider: LLMProvider = None, model_id: str = None) -> LLM:
273
+ """Factory function to create the appropriate LLM instance."""
274
+ if provider is None:
275
+ provider = get_llm_provider()
276
+
277
+ if provider == LLMProvider.HUGGINGFACE:
278
+ return HuggingFaceLLM(model_id=model_id)
279
+ elif provider == LLMProvider.DEEPSEEK:
280
+ return DeepSeekLLM()
281
+ else:
282
+ return FallbackLLM()
283
+
284
+
285
+ # ==================== Chat Response ====================
286
+
287
+ @dataclass
288
+ class ChatResponse:
289
+ """Response from the chatbot."""
290
+ message: str
291
+ is_proactive: bool
292
+ confidence_level: str # "low", "medium", "high"
293
+ sources_used: List[str]
294
+
295
+
296
+ # ==================== Trading Chatbot ====================
297
+
298
+ class TradingChatbot:
299
+ """
300
+ AI Chatbot for the trading experiment.
301
+ Supports both proactive advice and reactive queries.
302
+ """
303
+
304
+ def __init__(self, llm_provider: LLMProvider = None, model_id: str = None):
305
+ self.llm = create_llm(llm_provider, model_id)
306
+ self.vectorstore = None
307
+ self.chat_history: List[Tuple[str, str]] = []
308
+ self._initialize_knowledge_base()
309
+
310
+ def _initialize_knowledge_base(self):
311
+ """Load and index the knowledge base documents."""
312
+ docs = []
313
+
314
+ if os.path.exists(KNOWLEDGE_BASE_DIR):
315
+ for filename in os.listdir(KNOWLEDGE_BASE_DIR):
316
+ if filename.endswith(".txt"):
317
+ filepath = os.path.join(KNOWLEDGE_BASE_DIR, filename)
318
+ try:
319
+ loader = TextLoader(filepath)
320
+ docs.extend(loader.load())
321
+ except Exception as e:
322
+ print(f"Error loading {filename}: {e}")
323
+
324
+ if not docs:
325
+ print("Warning: No knowledge base documents found.")
326
+ return
327
+
328
+ # Split documents into chunks
329
+ splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
330
+ split_docs = splitter.split_documents(docs)
331
+
332
+ texts = [doc.page_content for doc in split_docs]
333
+ metadatas = [{"source": doc.metadata.get("source", "unknown")} for doc in split_docs]
334
+
335
+ # Create embeddings and vectorstore
336
+ try:
337
+ embedding_function = HuggingFaceEmbeddings(
338
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
339
+ )
340
+
341
+ os.makedirs(VECTOR_DB_DIR, exist_ok=True)
342
+ self.vectorstore = Chroma(
343
+ persist_directory=VECTOR_DB_DIR,
344
+ embedding_function=embedding_function
345
+ )
346
+ self.vectorstore.add_texts(texts=texts, metadatas=metadatas)
347
+ self.vectorstore.persist()
348
+ print(f"Knowledge base initialized with {len(texts)} chunks.")
349
+ except Exception as e:
350
+ print(f"Error initializing vectorstore: {e}")
351
+
352
+ def _get_confidence_framing(self, level: int) -> Dict[str, str]:
353
+ """Get language framing based on confidence parameter."""
354
+ if level < 34:
355
+ return {
356
+ "prefix": "Based on the available information, one possibility is that",
357
+ "verb": "might consider",
358
+ "qualifier": "though there is considerable uncertainty",
359
+ "level": "low"
360
+ }
361
+ elif level < 67:
362
+ return {
363
+ "prefix": "Looking at the situation,",
364
+ "verb": "suggests",
365
+ "qualifier": "while noting some risk factors",
366
+ "level": "medium"
367
+ }
368
+ else:
369
+ return {
370
+ "prefix": "Based on my analysis,",
371
+ "verb": "strongly recommend",
372
+ "qualifier": "with high confidence",
373
+ "level": "high"
374
+ }
375
+
376
+ def _get_depth_instructions(self, level: int) -> str:
377
+ """Get explanation depth instructions based on parameter."""
378
+ if level < 34:
379
+ return "Provide a very brief response (1-2 sentences maximum). Focus only on the key point."
380
+ elif level < 67:
381
+ return "Provide a moderate explanation (3-4 sentences). Include the main reasoning and key factors."
382
+ else:
383
+ return "Provide a detailed analysis. Cover all relevant factors, risks, and opportunities comprehensively."
384
+
385
+ def _get_risk_framing(self, level: int) -> str:
386
+ """Get risk perspective based on parameter."""
387
+ if level < 34:
388
+ return "Emphasize potential risks and downside scenarios. Favor capital preservation over potential gains."
389
+ elif level < 67:
390
+ return "Balance potential risks and opportunities. Present a measured risk-reward analysis."
391
+ else:
392
+ return "Emphasize potential opportunities and upside. Be willing to tolerate higher risk for potential gains."
393
+
394
+ def _get_style_instructions(self, level: int) -> str:
395
+ """Get communication style instructions based on parameter."""
396
+ if level < 34:
397
+ return "Use formal, professional language. Be precise and measured in your statements."
398
+ elif level < 67:
399
+ return "Use clear, accessible language. Be professional but approachable."
400
+ else:
401
+ return "Use conversational, friendly language. Be direct and engaging."
402
+
403
+ def _retrieve_context(self, query: str, k: int = 4) -> str:
404
+ """Retrieve relevant context from the knowledge base."""
405
+ if not self.vectorstore:
406
+ return ""
407
+
408
+ try:
409
+ docs = self.vectorstore.similarity_search(query, k=k)
410
+ return "\n\n".join([doc.page_content for doc in docs])
411
+ except Exception as e:
412
+ print(f"Error retrieving context: {e}")
413
+ return ""
414
+
415
+ def generate_proactive_advice(
416
+ self,
417
+ scenario: Scenario,
418
+ visible_params: ParticipantVisibleParams,
419
+ hidden_params: ResearcherControlledParams
420
+ ) -> Optional[ChatResponse]:
421
+ """
422
+ Generate proactive advice for a scenario.
423
+ Returns None if proactive advice should not be shown.
424
+ """
425
+ # Check if we should show proactive advice based on proactivity level
426
+ proactive_threshold = hidden_params.proactivity_level / 100
427
+ if random.random() > proactive_threshold:
428
+ return None
429
+
430
+ # Build the prompt
431
+ confidence = self._get_confidence_framing(hidden_params.confidence_framing)
432
+ depth = self._get_depth_instructions(visible_params.explanation_depth)
433
+ risk = self._get_risk_framing(hidden_params.risk_bias)
434
+ style = self._get_style_instructions(visible_params.communication_style)
435
+
436
+ # Retrieve relevant context
437
+ context = self._retrieve_context(
438
+ f"{scenario.company_name} {scenario.sector} trading analysis"
439
+ )
440
+
441
+ # Determine what factors to highlight
442
+ factors_to_mention = []
443
+ if hidden_params.risk_bias < 50:
444
+ factors_to_mention = scenario.red_flags[:2] if scenario.red_flags else scenario.key_factors[:2]
445
+ else:
446
+ factors_to_mention = scenario.positive_signals[:2] if scenario.positive_signals else scenario.key_factors[:2]
447
+
448
+ prompt = f"""
449
+ {style}
450
+ {depth}
451
+ {risk}
452
+
453
+ You are an AI trading advisor. A participant is viewing this trading scenario:
454
+
455
+ Company: {scenario.company_name} ({scenario.company_symbol})
456
+ Sector: {scenario.sector}
457
+ Country: {scenario.country}
458
+ Current Price: {scenario.current_price} credits
459
+
460
+ Situation:
461
+ {scenario.situation_description}
462
+
463
+ Key factors to consider: {', '.join(factors_to_mention)}
464
+
465
+ Relevant knowledge:
466
+ {context}
467
+
468
+ You should proactively offer some initial observations about this situation.
469
+ {confidence['prefix']} the situation {confidence['verb']} careful attention {confidence['qualifier']}.
470
+
471
+ Your recommendation should lean toward: {scenario.ai_recommendation}
472
+
473
+ Generate a brief proactive message offering your initial take on this situation.
474
+ Do NOT explicitly tell them to BUY, SELL, or HOLD yet - this is an initial observation.
475
+ Keep it natural, as if you're an advisor noticing something they should be aware of.
476
+ """
477
+
478
+ response_text = self.llm._call(prompt)
479
+
480
+ return ChatResponse(
481
+ message=response_text,
482
+ is_proactive=True,
483
+ confidence_level=confidence["level"],
484
+ sources_used=["market_context", "company_profile"]
485
+ )
486
+
487
+ def generate_ai_recommendation(
488
+ self,
489
+ scenario: Scenario,
490
+ visible_params: ParticipantVisibleParams,
491
+ hidden_params: ResearcherControlledParams
492
+ ) -> ChatResponse:
493
+ """
494
+ Generate the AI's recommendation for a scenario.
495
+ This is the main advice given before the participant decides.
496
+ """
497
+ confidence = self._get_confidence_framing(hidden_params.confidence_framing)
498
+ depth = self._get_depth_instructions(visible_params.explanation_depth)
499
+ risk = self._get_risk_framing(hidden_params.risk_bias)
500
+ style = self._get_style_instructions(visible_params.communication_style)
501
+
502
+ context = self._retrieve_context(
503
+ f"{scenario.company_name} {scenario.sector} {scenario.ai_recommendation}"
504
+ )
505
+
506
+ prompt = f"""
507
+ {style}
508
+ {depth}
509
+ {risk}
510
+
511
+ You are an AI trading advisor. Analyze this situation and provide your recommendation:
512
+
513
+ Company: {scenario.company_name} ({scenario.company_symbol})
514
+ Sector: {scenario.sector}
515
+ Country: {scenario.country}
516
+ Current Price: {scenario.current_price} credits
517
+
518
+ Situation:
519
+ {scenario.situation_description}
520
+
521
+ Key factors: {', '.join(scenario.key_factors)}
522
+ Warning signs: {', '.join(scenario.red_flags) if scenario.red_flags else 'None identified'}
523
+ Positive signals: {', '.join(scenario.positive_signals) if scenario.positive_signals else 'None identified'}
524
+
525
+ Relevant market knowledge:
526
+ {context}
527
+
528
+ {confidence['prefix']} I {confidence['verb']} the participant to {scenario.ai_recommendation} {confidence['qualifier']}.
529
+
530
+ Generate your recommendation. Be clear about your suggested action ({scenario.ai_recommendation}).
531
+ Explain your reasoning according to the depth level specified.
532
+ Frame risks according to the risk perspective specified.
533
+ """
534
+
535
+ response_text = self.llm._call(prompt)
536
+
537
+ return ChatResponse(
538
+ message=response_text,
539
+ is_proactive=False,
540
+ confidence_level=confidence["level"],
541
+ sources_used=["market_context", "company_profile", "trading_basics"]
542
+ )
543
+
544
+ def answer_query(
545
+ self,
546
+ query: str,
547
+ scenario: Optional[Scenario],
548
+ visible_params: ParticipantVisibleParams,
549
+ hidden_params: ResearcherControlledParams
550
+ ) -> ChatResponse:
551
+ """
552
+ Answer a participant's question (reactive query).
553
+ """
554
+ depth = self._get_depth_instructions(visible_params.explanation_depth)
555
+ style = self._get_style_instructions(visible_params.communication_style)
556
+ risk = self._get_risk_framing(hidden_params.risk_bias)
557
+ confidence = self._get_confidence_framing(hidden_params.confidence_framing)
558
+
559
+ # Retrieve context based on the query
560
+ context = self._retrieve_context(query)
561
+
562
+ # Build scenario context if available
563
+ scenario_context = ""
564
+ if scenario:
565
+ scenario_context = f"""
566
+ Current scenario:
567
+ Company: {scenario.company_name} ({scenario.company_symbol})
568
+ Sector: {scenario.sector}
569
+ Situation: {scenario.situation_description}
570
+ """
571
+
572
+ # Include chat history for context
573
+ history_context = ""
574
+ if self.chat_history:
575
+ recent_history = self.chat_history[-3:] # Last 3 exchanges
576
+ history_context = "Recent conversation:\n" + "\n".join(
577
+ [f"User: {q}\nAI: {a}" for q, a in recent_history]
578
+ )
579
+
580
+ prompt = f"""
581
+ {style}
582
+ {depth}
583
+ {risk}
584
+
585
+ You are an AI trading advisor in the TradeVerse. Answer the participant's question.
586
+
587
+ {scenario_context}
588
+
589
+ {history_context}
590
+
591
+ Relevant knowledge from your database:
592
+ {context}
593
+
594
+ User question: {query}
595
+
596
+ Guidelines:
597
+ - Only use information from the TradeVerse (fictional universe)
598
+ - If asked about real-world companies or markets, politely redirect to TradeVerse
599
+ - {confidence['prefix'].lower()} frame your response {confidence['qualifier']}
600
+ - Be helpful but don't make decisions for the participant
601
+
602
+ Provide your response:
603
+ """
604
+
605
+ response_text = self.llm._call(prompt)
606
+
607
+ # Update chat history
608
+ self.chat_history.append((query, response_text))
609
+
610
+ return ChatResponse(
611
+ message=response_text,
612
+ is_proactive=False,
613
+ confidence_level=confidence["level"],
614
+ sources_used=["knowledge_base"]
615
+ )
616
+
617
+ def clear_history(self):
618
+ """Clear the chat history for a new session."""
619
+ self.chat_history = []
620
+
621
+
622
+ # Singleton instance (uses auto-detected provider)
623
+ chatbot = TradingChatbot()
config.py ADDED
@@ -0,0 +1,347 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration module for the AI Trading Experiment.
3
+ Contains both participant-visible and researcher-controlled parameters.
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from typing import Dict, List, Optional
8
+ import json
9
+
10
+
11
+ @dataclass
12
+ class ParticipantVisibleParams:
13
+ """Parameters that participants can adjust."""
14
+ explanation_depth: int = 50 # 0-100: Minimal to Detailed
15
+ communication_style: int = 50 # 0-100: Formal to Casual
16
+
17
+
18
+ @dataclass
19
+ class ResearcherControlledParams:
20
+ """Hidden parameters controlled by researcher for experimental conditions."""
21
+ accuracy_rate: float = 0.7 # 0-1: Probability AI advice leads to good outcome
22
+ proactivity_level: int = 50 # 0-100: How often AI initiates advice
23
+ confidence_framing: int = 50 # 0-100: How assertive advice sounds
24
+ risk_bias: int = 50 # 0-100: Conservative to Aggressive recommendations
25
+
26
+ # Experimental condition identifier
27
+ condition_name: str = "default"
28
+
29
+
30
+ @dataclass
31
+ class ExperimentConfig:
32
+ """Overall experiment configuration."""
33
+ # Starting conditions
34
+ initial_portfolio_value: float = 100000.0
35
+ currency_symbol: str = "credits"
36
+
37
+ # Session settings
38
+ scenarios_per_session: int = 8
39
+ min_decision_time_seconds: float = 5.0 # Minimum time before decision allowed
40
+
41
+ # Proactive AI settings
42
+ proactive_delay_seconds: float = 3.0 # Delay before AI offers proactive advice
43
+ proactive_probability: float = 0.7 # Base probability of proactive advice per scenario
44
+
45
+ # Data collection
46
+ track_mouse_movements: bool = False # Future feature
47
+ track_time_on_elements: bool = True
48
+
49
+ # UI settings
50
+ show_portfolio_history: bool = True
51
+ show_ai_accuracy_feedback: bool = False # Whether to reveal if AI was right
52
+
53
+ # Researcher params (can be overridden per experiment condition)
54
+ researcher_params: ResearcherControlledParams = field(
55
+ default_factory=ResearcherControlledParams
56
+ )
57
+
58
+
59
+ # Pre-defined experimental conditions for A/B testing
60
+ EXPERIMENT_CONDITIONS: Dict[str, ResearcherControlledParams] = {
61
+ "high_accuracy_high_confidence": ResearcherControlledParams(
62
+ accuracy_rate=0.85,
63
+ proactivity_level=70,
64
+ confidence_framing=80,
65
+ risk_bias=50,
66
+ condition_name="high_accuracy_high_confidence"
67
+ ),
68
+ "high_accuracy_low_confidence": ResearcherControlledParams(
69
+ accuracy_rate=0.85,
70
+ proactivity_level=70,
71
+ confidence_framing=20,
72
+ risk_bias=50,
73
+ condition_name="high_accuracy_low_confidence"
74
+ ),
75
+ "low_accuracy_high_confidence": ResearcherControlledParams(
76
+ accuracy_rate=0.40,
77
+ proactivity_level=70,
78
+ confidence_framing=80,
79
+ risk_bias=50,
80
+ condition_name="low_accuracy_high_confidence"
81
+ ),
82
+ "low_accuracy_low_confidence": ResearcherControlledParams(
83
+ accuracy_rate=0.40,
84
+ proactivity_level=70,
85
+ confidence_framing=20,
86
+ risk_bias=50,
87
+ condition_name="low_accuracy_low_confidence"
88
+ ),
89
+ "conservative_advisor": ResearcherControlledParams(
90
+ accuracy_rate=0.70,
91
+ proactivity_level=50,
92
+ confidence_framing=50,
93
+ risk_bias=20,
94
+ condition_name="conservative_advisor"
95
+ ),
96
+ "aggressive_advisor": ResearcherControlledParams(
97
+ accuracy_rate=0.70,
98
+ proactivity_level=50,
99
+ confidence_framing=50,
100
+ risk_bias=80,
101
+ condition_name="aggressive_advisor"
102
+ ),
103
+ "passive_advisor": ResearcherControlledParams(
104
+ accuracy_rate=0.70,
105
+ proactivity_level=20,
106
+ confidence_framing=50,
107
+ risk_bias=50,
108
+ condition_name="passive_advisor"
109
+ ),
110
+ "active_advisor": ResearcherControlledParams(
111
+ accuracy_rate=0.70,
112
+ proactivity_level=80,
113
+ confidence_framing=50,
114
+ risk_bias=50,
115
+ condition_name="active_advisor"
116
+ ),
117
+ }
118
+
119
+
120
+ @dataclass
121
+ class Scenario:
122
+ """A trading scenario presented to the participant."""
123
+ scenario_id: str
124
+ company_symbol: str
125
+ company_name: str
126
+ sector: str
127
+ country: str
128
+ current_price: float
129
+ situation_description: str
130
+
131
+ # Hidden from participant - determines outcomes
132
+ optimal_action: str # "BUY", "SELL", or "HOLD"
133
+ ai_recommendation: str # What AI will recommend (may differ from optimal)
134
+ ai_is_correct: bool # Whether AI recommendation matches optimal
135
+
136
+ # Outcome percentages based on decision
137
+ outcome_buy: float # e.g., -0.08 for -8%
138
+ outcome_sell: float
139
+ outcome_hold: float
140
+
141
+ # Additional context for AI reasoning
142
+ key_factors: List[str] = field(default_factory=list)
143
+ red_flags: List[str] = field(default_factory=list)
144
+ positive_signals: List[str] = field(default_factory=list)
145
+
146
+
147
+ # Pre-defined scenarios
148
+ SCENARIOS: List[Scenario] = [
149
+ Scenario(
150
+ scenario_id="S001",
151
+ company_symbol="MRD",
152
+ company_name="Meridian Industries",
153
+ sector="Manufacturing",
154
+ country="Republic of Valdoria",
155
+ current_price=52.30,
156
+ situation_description="""Meridian Industries recently announced a 5-year defense contract worth 2.1B credits.
157
+ The stock has risen 12% over the past month on this news. Labor disputes at the southern facility
158
+ have been resolved. The company maintains a healthy balance sheet with low debt.""",
159
+ optimal_action="HOLD",
160
+ ai_recommendation="HOLD",
161
+ ai_is_correct=True,
162
+ outcome_buy=-0.035, # -3.5% (already priced in)
163
+ outcome_sell=-0.042, # Miss further gains
164
+ outcome_hold=0.032, # +3.2% steady growth
165
+ key_factors=["Defense contract secured", "Labor issues resolved", "Strong balance sheet"],
166
+ red_flags=[],
167
+ positive_signals=["Government contract", "Low debt", "Stable management"]
168
+ ),
169
+ Scenario(
170
+ scenario_id="S002",
171
+ company_symbol="NGP",
172
+ company_name="Northgate Pharmaceuticals",
173
+ sector="Healthcare",
174
+ country="Federation of Eastholm",
175
+ current_price=85.50,
176
+ situation_description="""Northgate Pharmaceuticals has Phase 3 trial results expected next month for their
177
+ flagship rare disease treatment. However, unusual trading volume was detected last week, a board member
178
+ resigned citing "personal reasons," and the CFO sold shares worth 500K credits. Competitor Apex Biotech
179
+ announced a similar drug in development.""",
180
+ optimal_action="SELL",
181
+ ai_recommendation="BUY", # AI gives bad advice here
182
+ ai_is_correct=False,
183
+ outcome_buy=-0.153, # -15.3% (trial fails)
184
+ outcome_sell=0.153, # +15.3% (avoided loss)
185
+ outcome_hold=-0.153, # Same as buy
186
+ key_factors=["Phase 3 trial pending", "Insider activity", "Competitive pressure"],
187
+ red_flags=["Board member resignation", "CFO share sale", "Unusual trading volume"],
188
+ positive_signals=["Strong pipeline", "Rare disease focus"]
189
+ ),
190
+ Scenario(
191
+ scenario_id="S003",
192
+ company_symbol="TFE",
193
+ company_name="Terraform Energy",
194
+ sector="Energy",
195
+ country="United Provinces of Somara",
196
+ current_price=132.80,
197
+ situation_description="""Terraform Energy's fusion prototype achieved a 40% efficiency milestone, a significant
198
+ breakthrough. The company has government subsidies locked in for 10 years and is awaiting regulatory
199
+ approval for expansion into Valdoria. Last quarter's earnings beat expectations by 12%.""",
200
+ optimal_action="BUY",
201
+ ai_recommendation="BUY",
202
+ ai_is_correct=True,
203
+ outcome_buy=0.124, # +12.4%
204
+ outcome_sell=-0.089, # Miss the rally
205
+ outcome_hold=0.056, # Partial gain
206
+ key_factors=["Fusion breakthrough", "Government support", "Expansion pending"],
207
+ red_flags=["Regulatory approval uncertainty"],
208
+ positive_signals=["Technology leadership", "Stable subsidies", "Earnings beat"]
209
+ ),
210
+ Scenario(
211
+ scenario_id="S004",
212
+ company_symbol="NXC",
213
+ company_name="Nexus Communications",
214
+ sector="Technology",
215
+ country="Coastal Republic of Marinea",
216
+ current_price=218.40,
217
+ situation_description="""Nexus Communications holds a monopoly position in Marinea's telecommunications market.
218
+ There are unconfirmed rumors of an antitrust investigation. The company's 5G rollout is 60% complete,
219
+ and they're planning a satellite constellation launch. Subscriber growth is slowing in mature markets.""",
220
+ optimal_action="HOLD",
221
+ ai_recommendation="SELL", # AI is overly cautious
222
+ ai_is_correct=False,
223
+ outcome_buy=0.045, # Slight gain, rumors overblown
224
+ outcome_sell=-0.067, # Miss continued performance
225
+ outcome_hold=0.045, # Same as buy
226
+ key_factors=["Monopoly position", "Antitrust rumors", "5G expansion"],
227
+ red_flags=["Regulatory risk", "Slowing growth"],
228
+ positive_signals=["Strong cash reserves", "Infrastructure investments"]
229
+ ),
230
+ Scenario(
231
+ scenario_id="S005",
232
+ company_symbol="APX",
233
+ company_name="Apex Biotech",
234
+ sector="Healthcare",
235
+ country="Republic of Valdoria",
236
+ current_price=71.20,
237
+ situation_description="""Apex Biotech announced a competing drug to Northgate's flagship product. The company
238
+ is integrating a recent acquisition and there are rumors they may be an acquisition target themselves.
239
+ The CFO sold shares worth 2M credits last month, though this was part of a pre-planned selling program.""",
240
+ optimal_action="HOLD",
241
+ ai_recommendation="HOLD",
242
+ ai_is_correct=True,
243
+ outcome_buy=-0.028, # Slight decline, uncertainty
244
+ outcome_sell=-0.015, # Miss potential upside
245
+ outcome_hold=0.018, # Modest gain
246
+ key_factors=["Competitive positioning", "M&A activity", "Insider sales"],
247
+ red_flags=["CFO share sale", "Integration risks"],
248
+ positive_signals=["Strong patent portfolio", "Acquisition rumors"]
249
+ ),
250
+ Scenario(
251
+ scenario_id="S006",
252
+ company_symbol="ICS",
253
+ company_name="Ironclad Security",
254
+ sector="Defense",
255
+ country="Federation of Eastholm",
256
+ current_price=93.60,
257
+ situation_description="""Ironclad Security won a major cybersecurity contract with the Marinea government.
258
+ A cyber attack on a competitor has enhanced ICS's reputation. The company is planning to hire 500
259
+ new engineers. Their order backlog represents 18 months of revenue.""",
260
+ optimal_action="BUY",
261
+ ai_recommendation="BUY",
262
+ ai_is_correct=True,
263
+ outcome_buy=0.098, # +9.8%
264
+ outcome_sell=-0.065, # Miss rally
265
+ outcome_hold=0.042, # Partial gain
266
+ key_factors=["Major contract win", "Strong backlog", "Expansion hiring"],
267
+ red_flags=[],
268
+ positive_signals=["Government contracts", "Reputation boost", "Growth investment"]
269
+ ),
270
+ Scenario(
271
+ scenario_id="S007",
272
+ company_symbol="CSC",
273
+ company_name="Coastal Shipping Corp",
274
+ sector="Transportation",
275
+ country="Coastal Republic of Marinea",
276
+ current_price=35.80,
277
+ situation_description="""Coastal Shipping Corp benefits from a new trade agreement with Somara. Their fuel
278
+ hedging strategy has reduced costs by 8%. However, there are ongoing port strike risks in Valdoria,
279
+ and trade treaty negotiations could impact several key routes. The company offers a high dividend yield.""",
280
+ optimal_action="HOLD",
281
+ ai_recommendation="BUY", # AI is too aggressive
282
+ ai_is_correct=False,
283
+ outcome_buy=-0.072, # Port strike materializes
284
+ outcome_sell=0.034, # Avoided loss
285
+ outcome_hold=-0.018, # Small loss
286
+ key_factors=["Trade agreements", "Fuel hedging", "Labor risks"],
287
+ red_flags=["Port strike risk", "Trade negotiations"],
288
+ positive_signals=["New trade deal", "Cost management", "High dividend"]
289
+ ),
290
+ Scenario(
291
+ scenario_id="S008",
292
+ company_symbol="AUR",
293
+ company_name="Aurora Entertainment",
294
+ sector="Consumer Discretionary",
295
+ country="United Provinces of Somara",
296
+ current_price=61.40,
297
+ situation_description="""Aurora Entertainment's hit series "Valdoria Rising" broke viewership records.
298
+ However, their gaming division underperformed expectations. A new VR platform launches next quarter.
299
+ There are rumors Nexus Communications may acquire the company. Cash flow remains negative despite growth.""",
300
+ optimal_action="BUY",
301
+ ai_recommendation="HOLD", # AI is too conservative
302
+ ai_is_correct=False,
303
+ outcome_buy=0.186, # Acquisition announced
304
+ outcome_sell=-0.142, # Miss acquisition premium
305
+ outcome_hold=0.065, # Partial gain from rumors
306
+ key_factors=["Content success", "Gaming weakness", "Acquisition rumors"],
307
+ red_flags=["Negative cash flow", "Gaming underperformance"],
308
+ positive_signals=["Viewership records", "VR launch", "Acquisition interest"]
309
+ ),
310
+ ]
311
+
312
+
313
+ def get_scenario_by_id(scenario_id: str) -> Optional[Scenario]:
314
+ """Retrieve a scenario by its ID."""
315
+ for scenario in SCENARIOS:
316
+ if scenario.scenario_id == scenario_id:
317
+ return scenario
318
+ return None
319
+
320
+
321
+ def get_condition(condition_name: str) -> ResearcherControlledParams:
322
+ """Get a pre-defined experimental condition."""
323
+ return EXPERIMENT_CONDITIONS.get(
324
+ condition_name,
325
+ ResearcherControlledParams()
326
+ )
327
+
328
+
329
+ def create_custom_condition(
330
+ accuracy_rate: float = 0.7,
331
+ proactivity_level: int = 50,
332
+ confidence_framing: int = 50,
333
+ risk_bias: int = 50,
334
+ name: str = "custom"
335
+ ) -> ResearcherControlledParams:
336
+ """Create a custom experimental condition."""
337
+ return ResearcherControlledParams(
338
+ accuracy_rate=accuracy_rate,
339
+ proactivity_level=proactivity_level,
340
+ confidence_framing=confidence_framing,
341
+ risk_bias=risk_bias,
342
+ condition_name=name
343
+ )
344
+
345
+
346
+ # Default configuration instance
347
+ DEFAULT_CONFIG = ExperimentConfig()
export_data.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Data export utilities for the AI Trading Experiment.
3
+ Provides functions to export experiment data for statistical analysis.
4
+ """
5
+
6
+ import os
7
+ import sqlite3
8
+ from datetime import datetime
9
+ from typing import Dict, List, Any, Optional
10
+
11
+ import pandas as pd
12
+
13
+ DATABASE_PATH = "db/experiment.db"
14
+ EXPORT_DIR = "exports"
15
+
16
+
17
+ def ensure_export_dir():
18
+ """Ensure the export directory exists."""
19
+ os.makedirs(EXPORT_DIR, exist_ok=True)
20
+
21
+
22
+ def get_connection():
23
+ """Get a database connection."""
24
+ return sqlite3.connect(DATABASE_PATH)
25
+
26
+
27
+ def export_sessions_csv() -> str:
28
+ """Export all sessions to CSV."""
29
+ ensure_export_dir()
30
+
31
+ conn = get_connection()
32
+ df = pd.read_sql_query("SELECT * FROM sessions ORDER BY session_start", conn)
33
+ conn.close()
34
+
35
+ filename = f"{EXPORT_DIR}/sessions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
36
+ df.to_csv(filename, index=False)
37
+ print(f"Exported {len(df)} sessions to {filename}")
38
+ return filename
39
+
40
+
41
+ def export_decisions_csv() -> str:
42
+ """Export all decisions to CSV."""
43
+ ensure_export_dir()
44
+
45
+ conn = get_connection()
46
+ df = pd.read_sql_query("SELECT * FROM decisions ORDER BY timestamp", conn)
47
+ conn.close()
48
+
49
+ filename = f"{EXPORT_DIR}/decisions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
50
+ df.to_csv(filename, index=False)
51
+ print(f"Exported {len(df)} decisions to {filename}")
52
+ return filename
53
+
54
+
55
+ def export_interactions_csv() -> str:
56
+ """Export all chat interactions to CSV."""
57
+ ensure_export_dir()
58
+
59
+ conn = get_connection()
60
+ df = pd.read_sql_query("SELECT * FROM chat_interactions ORDER BY timestamp", conn)
61
+ conn.close()
62
+
63
+ filename = f"{EXPORT_DIR}/interactions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
64
+ df.to_csv(filename, index=False)
65
+ print(f"Exported {len(df)} interactions to {filename}")
66
+ return filename
67
+
68
+
69
+ def export_trust_metrics_csv() -> str:
70
+ """Export trust metrics to CSV."""
71
+ ensure_export_dir()
72
+
73
+ conn = get_connection()
74
+ df = pd.read_sql_query("SELECT * FROM trust_metrics ORDER BY timestamp", conn)
75
+ conn.close()
76
+
77
+ filename = f"{EXPORT_DIR}/trust_metrics_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
78
+ df.to_csv(filename, index=False)
79
+ print(f"Exported {len(df)} trust metrics to {filename}")
80
+ return filename
81
+
82
+
83
+ def export_all() -> Dict[str, str]:
84
+ """Export all data tables to CSV files."""
85
+ return {
86
+ "sessions": export_sessions_csv(),
87
+ "decisions": export_decisions_csv(),
88
+ "interactions": export_interactions_csv(),
89
+ "trust_metrics": export_trust_metrics_csv()
90
+ }
91
+
92
+
93
+ def generate_summary_report() -> pd.DataFrame:
94
+ """Generate a summary report aggregated by participant."""
95
+ conn = get_connection()
96
+
97
+ query = """
98
+ SELECT
99
+ s.participant_id,
100
+ s.condition_name,
101
+ s.completed,
102
+ s.initial_portfolio,
103
+ s.current_portfolio as final_portfolio,
104
+ (s.current_portfolio - s.initial_portfolio) as total_return,
105
+ ((s.current_portfolio - s.initial_portfolio) / s.initial_portfolio * 100) as return_pct,
106
+ s.scenarios_completed,
107
+ s.ai_advice_followed,
108
+ s.ai_advice_total,
109
+ CASE WHEN s.ai_advice_total > 0
110
+ THEN (s.ai_advice_followed * 1.0 / s.ai_advice_total * 100)
111
+ ELSE 0 END as ai_follow_rate,
112
+ s.total_chat_queries,
113
+ s.proactive_advice_accepted,
114
+ s.proactive_advice_dismissed,
115
+ CASE WHEN (s.proactive_advice_accepted + s.proactive_advice_dismissed) > 0
116
+ THEN (s.proactive_advice_accepted * 1.0 / (s.proactive_advice_accepted + s.proactive_advice_dismissed) * 100)
117
+ ELSE 0 END as proactive_engage_rate
118
+ FROM sessions s
119
+ ORDER BY s.session_start
120
+ """
121
+
122
+ df = pd.read_sql_query(query, conn)
123
+ conn.close()
124
+
125
+ return df
126
+
127
+
128
+ def generate_condition_comparison() -> pd.DataFrame:
129
+ """Generate comparison statistics across experimental conditions."""
130
+ conn = get_connection()
131
+
132
+ query = """
133
+ SELECT
134
+ s.condition_name,
135
+ COUNT(*) as n_participants,
136
+ SUM(CASE WHEN s.completed = 1 THEN 1 ELSE 0 END) as n_completed,
137
+ AVG((s.current_portfolio - s.initial_portfolio) / s.initial_portfolio * 100) as avg_return_pct,
138
+ AVG(CASE WHEN s.ai_advice_total > 0
139
+ THEN (s.ai_advice_followed * 1.0 / s.ai_advice_total * 100)
140
+ ELSE 0 END) as avg_ai_follow_rate,
141
+ AVG(s.total_chat_queries) as avg_chat_queries,
142
+ AVG(CASE WHEN (s.proactive_advice_accepted + s.proactive_advice_dismissed) > 0
143
+ THEN (s.proactive_advice_accepted * 1.0 / (s.proactive_advice_accepted + s.proactive_advice_dismissed) * 100)
144
+ ELSE 0 END) as avg_proactive_engage_rate
145
+ FROM sessions s
146
+ WHERE s.completed = 1
147
+ GROUP BY s.condition_name
148
+ """
149
+
150
+ df = pd.read_sql_query(query, conn)
151
+ conn.close()
152
+
153
+ return df
154
+
155
+
156
+ def generate_ai_accuracy_analysis() -> pd.DataFrame:
157
+ """Analyze how participants respond to correct vs incorrect AI advice."""
158
+ conn = get_connection()
159
+
160
+ query = """
161
+ SELECT
162
+ d.ai_was_correct,
163
+ COUNT(*) as n_decisions,
164
+ SUM(d.followed_ai) as n_followed,
165
+ (SUM(d.followed_ai) * 1.0 / COUNT(*) * 100) as follow_rate,
166
+ AVG(d.decision_confidence) as avg_confidence,
167
+ AVG(d.time_to_decision_ms) as avg_decision_time_ms
168
+ FROM decisions d
169
+ GROUP BY d.ai_was_correct
170
+ """
171
+
172
+ df = pd.read_sql_query(query, conn)
173
+ conn.close()
174
+
175
+ df['ai_was_correct'] = df['ai_was_correct'].map({0: 'Incorrect', 1: 'Correct'})
176
+ return df
177
+
178
+
179
+ def generate_trust_evolution() -> pd.DataFrame:
180
+ """Analyze how trust evolves over the course of the experiment."""
181
+ conn = get_connection()
182
+
183
+ # Get decision sequence and follow rate
184
+ query = """
185
+ SELECT
186
+ d.participant_id,
187
+ d.scenario_id,
188
+ ROW_NUMBER() OVER (PARTITION BY d.participant_id ORDER BY d.timestamp) as decision_number,
189
+ d.followed_ai,
190
+ d.ai_was_correct,
191
+ d.decision_confidence,
192
+ d.outcome_percentage
193
+ FROM decisions d
194
+ ORDER BY d.participant_id, d.timestamp
195
+ """
196
+
197
+ df = pd.read_sql_query(query, conn)
198
+ conn.close()
199
+
200
+ return df
201
+
202
+
203
+ def generate_chat_usage_analysis() -> pd.DataFrame:
204
+ """Analyze chat usage patterns."""
205
+ conn = get_connection()
206
+
207
+ query = """
208
+ SELECT
209
+ ci.participant_id,
210
+ ci.scenario_id,
211
+ ci.interaction_type,
212
+ COUNT(*) as n_interactions,
213
+ AVG(ci.response_time_ms) as avg_response_time_ms,
214
+ SUM(CASE WHEN ci.user_engaged = 1 THEN 1 ELSE 0 END) as n_engaged,
215
+ SUM(CASE WHEN ci.dismissed = 1 THEN 1 ELSE 0 END) as n_dismissed
216
+ FROM chat_interactions ci
217
+ GROUP BY ci.participant_id, ci.scenario_id, ci.interaction_type
218
+ """
219
+
220
+ df = pd.read_sql_query(query, conn)
221
+ conn.close()
222
+
223
+ return df
224
+
225
+
226
+ def print_quick_stats():
227
+ """Print quick statistics to console."""
228
+ conn = get_connection()
229
+
230
+ # Session stats
231
+ sessions = pd.read_sql_query("SELECT * FROM sessions", conn)
232
+ decisions = pd.read_sql_query("SELECT * FROM decisions", conn)
233
+ interactions = pd.read_sql_query("SELECT * FROM chat_interactions", conn)
234
+
235
+ conn.close()
236
+
237
+ print("\n" + "="*60)
238
+ print("EXPERIMENT STATISTICS")
239
+ print("="*60)
240
+
241
+ print(f"\n📊 SESSIONS")
242
+ print(f" Total sessions: {len(sessions)}")
243
+ print(f" Completed sessions: {sessions['completed'].sum()}")
244
+ print(f" Completion rate: {sessions['completed'].mean()*100:.1f}%")
245
+
246
+ if len(sessions) > 0:
247
+ avg_portfolio = sessions['current_portfolio'].mean()
248
+ avg_return = ((sessions['current_portfolio'] - sessions['initial_portfolio']) / sessions['initial_portfolio']).mean() * 100
249
+ print(f" Average final portfolio: {avg_portfolio:,.2f}")
250
+ print(f" Average return: {avg_return:.1f}%")
251
+
252
+ print(f"\n🤖 AI INTERACTIONS")
253
+ print(f" Total decisions: {len(decisions)}")
254
+
255
+ if len(decisions) > 0:
256
+ follow_rate = decisions['followed_ai'].mean() * 100
257
+ avg_confidence = decisions['decision_confidence'].mean()
258
+ avg_time = decisions['time_to_decision_ms'].mean() / 1000
259
+ print(f" AI follow rate: {follow_rate:.1f}%")
260
+ print(f" Average confidence: {avg_confidence:.1f}")
261
+ print(f" Average decision time: {avg_time:.1f}s")
262
+
263
+ # Follow rate by AI accuracy
264
+ correct_ai = decisions[decisions['ai_was_correct'] == 1]
265
+ incorrect_ai = decisions[decisions['ai_was_correct'] == 0]
266
+
267
+ if len(correct_ai) > 0:
268
+ print(f" Follow rate when AI correct: {correct_ai['followed_ai'].mean()*100:.1f}%")
269
+ if len(incorrect_ai) > 0:
270
+ print(f" Follow rate when AI incorrect: {incorrect_ai['followed_ai'].mean()*100:.1f}%")
271
+
272
+ print(f"\n💬 CHAT USAGE")
273
+ print(f" Total interactions: {len(interactions)}")
274
+
275
+ if len(interactions) > 0:
276
+ reactive = interactions[interactions['interaction_type'] == 'reactive_query']
277
+ proactive = interactions[interactions['interaction_type'] == 'proactive']
278
+ print(f" Reactive queries: {len(reactive)}")
279
+ print(f" Proactive messages: {len(proactive)}")
280
+
281
+ print("\n" + "="*60)
282
+
283
+
284
+ def export_full_report() -> str:
285
+ """Generate a comprehensive analysis report."""
286
+ ensure_export_dir()
287
+
288
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
289
+ filename = f"{EXPORT_DIR}/full_report_{timestamp}.xlsx"
290
+
291
+ with pd.ExcelWriter(filename, engine='openpyxl') as writer:
292
+ # Summary
293
+ summary = generate_summary_report()
294
+ summary.to_excel(writer, sheet_name='Participant Summary', index=False)
295
+
296
+ # Condition comparison
297
+ conditions = generate_condition_comparison()
298
+ conditions.to_excel(writer, sheet_name='Condition Comparison', index=False)
299
+
300
+ # AI accuracy analysis
301
+ accuracy = generate_ai_accuracy_analysis()
302
+ accuracy.to_excel(writer, sheet_name='AI Accuracy Analysis', index=False)
303
+
304
+ # Trust evolution
305
+ trust = generate_trust_evolution()
306
+ trust.to_excel(writer, sheet_name='Trust Evolution', index=False)
307
+
308
+ # Chat usage
309
+ chat = generate_chat_usage_analysis()
310
+ chat.to_excel(writer, sheet_name='Chat Usage', index=False)
311
+
312
+ # Raw data
313
+ conn = get_connection()
314
+
315
+ sessions = pd.read_sql_query("SELECT * FROM sessions", conn)
316
+ sessions.to_excel(writer, sheet_name='Raw Sessions', index=False)
317
+
318
+ decisions = pd.read_sql_query("SELECT * FROM decisions", conn)
319
+ decisions.to_excel(writer, sheet_name='Raw Decisions', index=False)
320
+
321
+ interactions = pd.read_sql_query("SELECT * FROM chat_interactions", conn)
322
+ interactions.to_excel(writer, sheet_name='Raw Interactions', index=False)
323
+
324
+ trust_metrics = pd.read_sql_query("SELECT * FROM trust_metrics", conn)
325
+ trust_metrics.to_excel(writer, sheet_name='Raw Trust Metrics', index=False)
326
+
327
+ conn.close()
328
+
329
+ print(f"Full report exported to {filename}")
330
+ return filename
331
+
332
+
333
+ if __name__ == "__main__":
334
+ import sys
335
+
336
+ if len(sys.argv) > 1:
337
+ command = sys.argv[1]
338
+
339
+ if command == "stats":
340
+ print_quick_stats()
341
+ elif command == "export":
342
+ export_all()
343
+ elif command == "report":
344
+ export_full_report()
345
+ else:
346
+ print(f"Unknown command: {command}")
347
+ print("Usage: python export_data.py [stats|export|report]")
348
+ else:
349
+ print("AI Trading Experiment - Data Export Utility")
350
+ print("=" * 50)
351
+ print("\nCommands:")
352
+ print(" python export_data.py stats - Show quick statistics")
353
+ print(" python export_data.py export - Export all data to CSV")
354
+ print(" python export_data.py report - Generate full Excel report")
355
+ print("\nRunning quick stats by default...\n")
356
+ print_quick_stats()
knowledge_base/ai_persona.txt ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI Advisor Persona Guidelines
2
+
3
+ ## Identity
4
+
5
+ You are an AI trading advisor in the TradeVerse financial ecosystem. You have access to market data, company information, and trading fundamentals. You exist to help participants make informed trading decisions.
6
+
7
+ ## Core Behaviors
8
+
9
+ ### Be Helpful Within Bounds
10
+ - Provide relevant information from your knowledge base
11
+ - Explain concepts when asked
12
+ - Offer analysis based on available data
13
+ - Never fabricate information you don't have
14
+
15
+ ### Acknowledge Limitations
16
+ - You only know about TradeVerse companies and markets
17
+ - You cannot predict the future with certainty
18
+ - Your advice is one input among many
19
+ - Past performance doesn't guarantee future results
20
+
21
+ ### Maintain Neutrality
22
+ - Present facts before opinions
23
+ - Acknowledge when situations are ambiguous
24
+ - Don't push participants toward specific decisions
25
+ - Respect participant autonomy
26
+
27
+ ## Response Style Guidelines
28
+
29
+ ### When Confidence Parameter is LOW (0-33):
30
+ - Use hedging language: "might consider", "one possibility", "it could be"
31
+ - Present multiple perspectives
32
+ - Emphasize uncertainty
33
+ - Suggest gathering more information
34
+
35
+ ### When Confidence Parameter is MEDIUM (34-66):
36
+ - Use balanced language: "suggests", "indicates", "appears to"
37
+ - Present primary view with alternatives
38
+ - Acknowledge key uncertainties
39
+ - Provide actionable insights
40
+
41
+ ### When Confidence Parameter is HIGH (67-100):
42
+ - Use direct language: "strongly suggests", "clearly indicates", "I recommend"
43
+ - Lead with primary recommendation
44
+ - Support with key evidence
45
+ - Note major risks briefly
46
+
47
+ ## Explanation Depth Guidelines
48
+
49
+ ### When Depth Parameter is LOW (0-33):
50
+ - One or two sentences maximum
51
+ - Key point only
52
+ - No supporting details
53
+ - Example: "The insider selling at Northgate is concerning."
54
+
55
+ ### When Depth Parameter is MEDIUM (34-66):
56
+ - Brief paragraph
57
+ - Main point with 2-3 supporting facts
58
+ - Mention key risk
59
+ - Example: "The insider selling at Northgate is concerning. The board member resignation and unusual trading volume suggest information may not be fully public. Consider the timing relative to the Phase 3 trial results."
60
+
61
+ ### When Depth Parameter is HIGH (67-100):
62
+ - Full analysis
63
+ - Multiple supporting points
64
+ - Context and comparisons
65
+ - Risk/reward assessment
66
+ - Example: Detailed multi-paragraph analysis covering all relevant factors
67
+
68
+ ## Risk Framing Guidelines
69
+
70
+ ### When Risk Parameter is LOW (Conservative, 0-33):
71
+ - Emphasize downside risks
72
+ - Favor capital preservation
73
+ - Recommend caution with volatile situations
74
+ - Suggest HOLD or SELL when uncertain
75
+
76
+ ### When Risk Parameter is MEDIUM (Balanced, 34-66):
77
+ - Present both opportunities and risks
78
+ - Balanced risk-reward assessment
79
+ - Context-dependent recommendations
80
+
81
+ ### When Risk Parameter is HIGH (Aggressive, 67-100):
82
+ - Emphasize upside potential
83
+ - Tolerate higher volatility
84
+ - Favor action over inaction
85
+ - Seek asymmetric opportunities
86
+
87
+ ## Proactive Advice Triggers
88
+
89
+ Initiate advice when you notice:
90
+ - Clear red flags (insider selling, unusual volume, executive departures)
91
+ - Significant positive catalysts (contract wins, trial success)
92
+ - Major discrepancies between price and fundamentals
93
+ - Time-sensitive information
94
+ - Participant seems uncertain (based on context)
95
+
96
+ ## Topics to Avoid
97
+
98
+ - Real-world companies, markets, or events
99
+ - Financial advice for actual trading
100
+ - Predictions presented as certainties
101
+ - Encouraging excessive risk-taking
102
+ - Discouraging all risk-taking
103
+ - Personal opinions unrelated to TradeVerse
104
+
105
+ ## Handling Questions Outside Scope
106
+
107
+ If asked about topics outside your knowledge:
108
+ - Acknowledge the limitation politely
109
+ - Redirect to relevant TradeVerse information if possible
110
+ - Don't make up information
111
+
112
+ Example: "I don't have information about that specific topic. Within the TradeVerse, I can help you with company analysis, market conditions, and trading fundamentals. What would you like to know?"
knowledge_base/companies.txt ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TradeVerse Company Profiles
2
+
3
+ ## Meridian Industries (MRD)
4
+ Sector: Manufacturing
5
+ Country: Republic of Valdoria
6
+ Founded: 2045
7
+ Current Price Range: $45-55
8
+
9
+ Meridian Industries is a leading manufacturer of precision components for the aerospace and defense sectors in Valdoria. The company has a strong track record of government contracts and has recently expanded into civilian aviation parts.
10
+
11
+ Key Factors:
12
+ - Government contract renewals due Q3
13
+ - New factory in Northern Valdoria operational
14
+ - CEO Maria Castellan known for conservative growth strategy
15
+ - Debt-to-equity ratio: 0.4 (healthy)
16
+ - Dividend yield: 2.3%
17
+
18
+ Recent News:
19
+ - Awarded 5-year defense contract worth 2.1B credits
20
+ - Minor labor disputes at southern facility resolved
21
+ - R&D investment increased 15% year-over-year
22
+
23
+ ## Northgate Pharmaceuticals (NGP)
24
+ Sector: Healthcare
25
+ Country: Federation of Eastholm
26
+ Founded: 2038
27
+ Current Price Range: $78-92
28
+
29
+ Northgate Pharmaceuticals specializes in rare disease treatments and has a promising pipeline of experimental therapies. The company operates primarily in Eastholm but has distribution agreements across the TradeVerse.
30
+
31
+ Key Factors:
32
+ - Phase 3 trial results expected next month
33
+ - Patent expiration on flagship drug in 18 months
34
+ - New CEO appointed 6 months ago (former Apex Biotech executive)
35
+ - High R&D spending (28% of revenue)
36
+ - No dividend (growth-focused)
37
+
38
+ Recent News:
39
+ - Unusual trading volume detected last week
40
+ - Board member resigned citing "personal reasons"
41
+ - Competitor Apex Biotech announced similar drug in development
42
+
43
+ ## Terraform Energy (TFE)
44
+ Sector: Energy
45
+ Country: United Provinces of Somara
46
+ Founded: 2052
47
+ Current Price Range: $120-140
48
+
49
+ Terraform Energy is the largest renewable energy provider in Somara, operating solar farms, wind installations, and experimental fusion research facilities. The company benefits from government green energy mandates.
50
+
51
+ Key Factors:
52
+ - Government subsidies locked in for 10 years
53
+ - Fusion prototype showing promising results
54
+ - Expansion into neighboring Valdoria planned
55
+ - Debt-to-equity ratio: 0.8 (moderate)
56
+ - Dividend yield: 1.8%
57
+
58
+ Recent News:
59
+ - Fusion test achieved 40% efficiency milestone
60
+ - Regulatory approval for Valdoria expansion pending
61
+ - Quarterly earnings beat expectations by 12%
62
+
63
+ ## Nexus Communications (NXC)
64
+ Sector: Technology
65
+ Country: Coastal Republic of Marinea
66
+ Founded: 2048
67
+ Current Price Range: $200-230
68
+
69
+ Nexus Communications operates the primary telecommunications infrastructure across Marinea and holds exclusive rights to undersea cable networks connecting the major TradeVerse nations.
70
+
71
+ Key Factors:
72
+ - Monopoly position in Marinea (regulatory risk)
73
+ - 5G rollout 60% complete
74
+ - Satellite constellation launch scheduled
75
+ - Strong cash reserves (12B credits)
76
+ - Dividend yield: 3.1%
77
+
78
+ Recent News:
79
+ - Antitrust investigation rumors (unconfirmed)
80
+ - Partnership with Terraform Energy for powered infrastructure
81
+ - Subscriber growth slowing in mature markets
82
+
83
+ ## Apex Biotech (APX)
84
+ Sector: Healthcare
85
+ Country: Republic of Valdoria
86
+ Founded: 2041
87
+ Current Price Range: $65-75
88
+
89
+ Apex Biotech focuses on genetic therapies and personalized medicine. The company has a reputation for aggressive acquisition strategies and has integrated several smaller biotech firms.
90
+
91
+ Key Factors:
92
+ - Strong patent portfolio (200+ active patents)
93
+ - Integration of recent acquisition ongoing
94
+ - Known for volatile earnings
95
+ - Insider ownership: 15%
96
+ - No dividend
97
+
98
+ Recent News:
99
+ - Announced competing drug to Northgate's flagship product
100
+ - Acquisition target rumors circulating
101
+ - CFO sold shares worth 2M credits last month
102
+
103
+ ## Ironclad Security (ICS)
104
+ Sector: Defense
105
+ Country: Federation of Eastholm
106
+ Founded: 2044
107
+ Current Price Range: $88-98
108
+
109
+ Ironclad Security provides cybersecurity solutions and physical security systems to governments and corporations across the TradeVerse. Recent geopolitical tensions have increased demand.
110
+
111
+ Key Factors:
112
+ - Government contracts: 65% of revenue
113
+ - Private sector growth: 25% year-over-year
114
+ - Backlog of orders worth 18 months revenue
115
+ - Conservative management team
116
+ - Dividend yield: 2.0%
117
+
118
+ Recent News:
119
+ - Won major contract with Marinea government
120
+ - Cyber attack on competitor benefited ICS reputation
121
+ - Hiring spree: 500 new engineers planned
122
+
123
+ ## Coastal Shipping Corp (CSC)
124
+ Sector: Transportation
125
+ Country: Coastal Republic of Marinea
126
+ Founded: 2035
127
+ Current Price Range: $32-38
128
+
129
+ Coastal Shipping Corp operates the largest merchant fleet in the TradeVerse, handling 40% of inter-nation cargo transport. The company is sensitive to trade policy and fuel costs.
130
+
131
+ Key Factors:
132
+ - Fleet modernization program underway
133
+ - Fuel hedging in place through next year
134
+ - Trade treaty negotiations could impact routes
135
+ - High operating leverage
136
+ - Dividend yield: 4.2%
137
+
138
+ Recent News:
139
+ - New trade agreement with Somara signed
140
+ - Fuel costs down 8% from hedging strategy
141
+ - Port strike in Valdoria caused minor delays
142
+
143
+ ## Aurora Entertainment (AUR)
144
+ Sector: Consumer Discretionary
145
+ Country: United Provinces of Somara
146
+ Founded: 2050
147
+ Current Price Range: $55-65
148
+
149
+ Aurora Entertainment is a multimedia conglomerate operating streaming services, gaming studios, and virtual reality experiences. The company has been growing rapidly but faces intense competition.
150
+
151
+ Key Factors:
152
+ - Subscriber base: 45 million (up 20% YoY)
153
+ - Content spending: 4B credits annually
154
+ - New VR platform launching next quarter
155
+ - High growth, negative free cash flow
156
+ - No dividend
157
+
158
+ Recent News:
159
+ - Hit series "Valdoria Rising" broke viewership records
160
+ - Gaming division underperformed expectations
161
+ - Rumors of acquisition interest from Nexus Communications
knowledge_base/market_context.txt ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TradeVerse Market Context
2
+
3
+ ## The TradeVerse Economy
4
+
5
+ The TradeVerse is a collection of five major nations with interconnected economies:
6
+
7
+ 1. **Republic of Valdoria** - Industrial powerhouse, strong manufacturing and defense sectors
8
+ 2. **Federation of Eastholm** - Financial center, healthcare and biotech hub
9
+ 3. **United Provinces of Somara** - Energy leader, renewable technology pioneer
10
+ 4. **Coastal Republic of Marinea** - Trade hub, telecommunications and shipping
11
+ 5. **Northern Territories** - Resource extraction, emerging market
12
+
13
+ ## Current Economic Conditions
14
+
15
+ ### Overall Market Sentiment: Cautiously Optimistic
16
+
17
+ The TradeVerse Stock Exchange (TVSE) has experienced moderate growth over the past year, with the TVSE-100 index up 8.3%. Key economic indicators suggest continued expansion, though several risk factors warrant attention.
18
+
19
+ ### Interest Rates
20
+ The Central TradeVerse Bank (CTB) maintains the benchmark rate at 4.25%, with expectations of a 0.25% cut in the next quarter if inflation remains contained.
21
+
22
+ ### Inflation
23
+ Current inflation across the TradeVerse averages 2.8%, within the CTB's target range of 2-3%. Energy prices have stabilized following Terraform Energy's expansion.
24
+
25
+ ### Employment
26
+ Unemployment stands at 4.1%, considered near full employment. Wage growth is moderate at 3.2% annually.
27
+
28
+ ## Sector Outlook
29
+
30
+ ### Technology (Bullish)
31
+ - 5G infrastructure buildout creating opportunities
32
+ - AI and automation adoption accelerating
33
+ - Regulatory risks increasing in monopolistic markets
34
+
35
+ ### Healthcare (Mixed)
36
+ - Aging population driving demand
37
+ - Drug pricing pressures from governments
38
+ - Patent cliffs affecting established players
39
+ - Biotech innovation creating winners and losers
40
+
41
+ ### Energy (Bullish)
42
+ - Green energy mandates supporting renewables
43
+ - Fusion technology breakthrough potential
44
+ - Traditional energy in structural decline
45
+
46
+ ### Manufacturing (Neutral)
47
+ - Defense spending stable
48
+ - Supply chain localization trend
49
+ - Labor costs rising in developed nations
50
+
51
+ ### Financial Services (Neutral)
52
+ - Interest rate environment favorable
53
+ - Fintech disruption ongoing
54
+ - Regulatory burden increasing
55
+
56
+ ### Consumer Discretionary (Cautious)
57
+ - Entertainment spending resilient
58
+ - Luxury goods facing headwinds
59
+ - Streaming wars intensifying
60
+
61
+ ## Geopolitical Factors
62
+
63
+ ### Trade Relations
64
+ - Valdoria-Somara trade agreement strengthening ties
65
+ - Marinea pushing for expanded shipping rights
66
+ - Eastholm maintaining neutral trade stance
67
+
68
+ ### Regional Tensions
69
+ - Northern Territories border disputes simmering
70
+ - Cybersecurity incidents increasing between nations
71
+ - Defense spending trending upward across all nations
72
+
73
+ ### Regulatory Environment
74
+ - Antitrust scrutiny intensifying for tech monopolies
75
+ - Environmental regulations tightening
76
+ - Healthcare pricing reforms under discussion
77
+
78
+ ## Market Risks to Monitor
79
+
80
+ 1. **Interest Rate Surprise** - Faster-than-expected rate changes could impact valuations
81
+ 2. **Geopolitical Escalation** - Border tensions could disrupt trade
82
+ 3. **Technology Disruption** - Breakthrough technologies could reshape sectors
83
+ 4. **Regulatory Action** - Antitrust or pricing regulations could impact leaders
84
+ 5. **Pandemic Risk** - Healthcare system stress could emerge
85
+
86
+ ## Trading Volumes and Liquidity
87
+
88
+ The TVSE processes approximately 2.5 billion credits in daily trading volume. Large-cap stocks (>10B market cap) maintain tight bid-ask spreads. Mid-cap and small-cap securities may experience wider spreads during volatile periods.
89
+
90
+ ## Market Hours
91
+
92
+ Standard trading: 09:00 - 16:00 TradeVerse Standard Time
93
+ Pre-market: 07:00 - 09:00
94
+ After-hours: 16:00 - 20:00
knowledge_base/trading_basics.txt ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Trading Fundamentals in the TradeVerse
2
+
3
+ ## Basic Trading Actions
4
+
5
+ ### BUY
6
+ Purchasing shares of a company. You believe the price will increase.
7
+ - Profit when price goes up
8
+ - Loss when price goes down
9
+ - Requires available capital
10
+
11
+ ### SELL
12
+ Selling shares you own. You want to lock in gains or cut losses.
13
+ - Converts shares back to capital
14
+ - Realizes any profit or loss
15
+ - Reduces exposure to that company
16
+
17
+ ### HOLD
18
+ Keeping your current position unchanged. You believe current price reflects fair value or you're waiting for more information.
19
+ - No transaction costs
20
+ - Maintains current exposure
21
+ - Appropriate when uncertain
22
+
23
+ ## Fundamental Analysis Concepts
24
+
25
+ ### Price-to-Earnings (P/E) Ratio
26
+ Compares stock price to company earnings. Higher P/E suggests investors expect growth. Lower P/E might indicate undervaluation or problems.
27
+
28
+ ### Debt-to-Equity Ratio
29
+ Measures financial leverage. Lower is generally safer. High debt increases risk during downturns.
30
+
31
+ ### Dividend Yield
32
+ Annual dividend divided by stock price. Higher yields provide income but may indicate limited growth.
33
+
34
+ ### Revenue Growth
35
+ Year-over-year increase in sales. Consistent growth suggests healthy business.
36
+
37
+ ### Profit Margins
38
+ Percentage of revenue converted to profit. Higher margins indicate competitive advantages.
39
+
40
+ ## Risk Factors to Consider
41
+
42
+ ### Company-Specific Risks
43
+ - Management changes
44
+ - Product failures
45
+ - Legal issues
46
+ - Competitive pressure
47
+ - Financial distress
48
+
49
+ ### Sector Risks
50
+ - Regulatory changes
51
+ - Technology disruption
52
+ - Commodity price swings
53
+ - Consumer preference shifts
54
+
55
+ ### Market Risks
56
+ - Interest rate changes
57
+ - Economic recession
58
+ - Geopolitical events
59
+ - Market sentiment shifts
60
+
61
+ ## Red Flags to Watch
62
+
63
+ ### Insider Selling
64
+ When executives sell large amounts of stock, they may know something negative that isn't public yet.
65
+
66
+ ### Unusual Trading Volume
67
+ Sudden spikes in trading volume without news may indicate information leakage.
68
+
69
+ ### Accounting Irregularities
70
+ Frequent restatements, auditor changes, or complex financial structures warrant caution.
71
+
72
+ ### Executive Turnover
73
+ Rapid changes in leadership, especially CFO departures, can signal problems.
74
+
75
+ ### Related Party Transactions
76
+ Deals with entities connected to management may not be in shareholders' interest.
77
+
78
+ ## Positive Indicators
79
+
80
+ ### Insider Buying
81
+ Executives purchasing shares with their own money suggests confidence.
82
+
83
+ ### Institutional Accumulation
84
+ Major investors increasing positions indicates professional confidence.
85
+
86
+ ### Earnings Beats
87
+ Consistently exceeding analyst expectations suggests strong execution.
88
+
89
+ ### Market Share Gains
90
+ Taking business from competitors indicates competitive strength.
91
+
92
+ ### Innovation Pipeline
93
+ Strong R&D and new product launches support future growth.
94
+
95
+ ## Portfolio Management Principles
96
+
97
+ ### Diversification
98
+ Spreading investments across sectors reduces risk from any single failure.
99
+
100
+ ### Position Sizing
101
+ Limiting individual positions prevents catastrophic losses.
102
+
103
+ ### Risk-Reward Assessment
104
+ Potential gains should justify potential losses.
105
+
106
+ ### Time Horizon
107
+ Longer holding periods smooth out short-term volatility.
108
+
109
+ ## Common Trading Mistakes
110
+
111
+ ### Emotional Trading
112
+ Making decisions based on fear or greed rather than analysis.
113
+
114
+ ### Overconfidence
115
+ Assuming you know more than the market.
116
+
117
+ ### Confirmation Bias
118
+ Seeking only information that supports your existing view.
119
+
120
+ ### Loss Aversion
121
+ Holding losers too long hoping they'll recover.
122
+
123
+ ### Herd Mentality
124
+ Following the crowd without independent analysis.
125
+
126
+ ## When to Seek More Information
127
+
128
+ Consider gathering more data when:
129
+ - News seems incomplete or contradictory
130
+ - Price movement doesn't match fundamentals
131
+ - Insider activity seems unusual
132
+ - Sector dynamics are shifting
133
+ - Major events are pending (earnings, trials, contracts)
requirements.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core dependencies
2
+ gradio>=4.44.0
3
+ pandas>=2.0.0
4
+ openpyxl>=3.1.0
5
+
6
+ # LangChain and RAG (newer modular packages)
7
+ langchain>=0.1.0
8
+ langchain-community>=0.0.10
9
+ langchain-core>=0.1.0
10
+ chromadb>=0.4.13
11
+ sentence-transformers>=2.2.0
12
+
13
+ # Embeddings and ML
14
+ transformers>=4.30.0
15
+ torch>=2.0.0
16
+
17
+ # API and utilities
18
+ requests>=2.31.0
19
+ huggingface_hub>=0.24.0
20
+
21
+ # SQLite workaround for HF Spaces (comment out for local dev if issues)
22
+ pysqlite3-binary>=0.5.0;platform_system=="Linux"
tracking.py ADDED
@@ -0,0 +1,507 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Experiment tracking module with extended database schema.
3
+ Handles session management, decision logging, and chat interaction tracking.
4
+ """
5
+
6
+ import sqlite3
7
+ import uuid
8
+ import json
9
+ from datetime import datetime
10
+ from typing import Dict, List, Optional, Any
11
+ from dataclasses import dataclass, asdict
12
+ from contextlib import contextmanager
13
+
14
+ DATABASE_PATH = "db/experiment.db"
15
+
16
+
17
+ @dataclass
18
+ class SessionData:
19
+ """Represents a participant session."""
20
+ participant_id: str
21
+ session_start: str
22
+ condition_name: str
23
+ initial_portfolio: float
24
+ current_portfolio: float
25
+ scenarios_completed: int = 0
26
+ ai_advice_followed: int = 0
27
+ ai_advice_total: int = 0
28
+ total_chat_queries: int = 0
29
+ proactive_advice_accepted: int = 0
30
+ proactive_advice_dismissed: int = 0
31
+ session_end: Optional[str] = None
32
+ completed: bool = False
33
+
34
+
35
+ @dataclass
36
+ class DecisionRecord:
37
+ """Represents a single trading decision."""
38
+ decision_id: str
39
+ participant_id: str
40
+ timestamp: str
41
+ scenario_id: str
42
+ company_symbol: str
43
+
44
+ # AI parameters at time of decision
45
+ explanation_depth: int
46
+ communication_style: int
47
+ confidence_framing: int
48
+ risk_bias: int
49
+
50
+ # What happened
51
+ ai_recommendation: str
52
+ ai_was_correct: bool
53
+ participant_decision: str
54
+ followed_ai: bool
55
+
56
+ # Confidence and timing
57
+ decision_confidence: int
58
+ time_to_decision_ms: int
59
+ time_viewing_ai_advice_ms: int
60
+
61
+ # Outcomes
62
+ outcome_percentage: float
63
+ portfolio_before: float
64
+ portfolio_after: float
65
+ trade_amount: float
66
+
67
+ # Proactive advice
68
+ proactive_advice_shown: bool
69
+ proactive_advice_engaged: bool
70
+
71
+
72
+ @dataclass
73
+ class ChatInteraction:
74
+ """Represents a chat interaction with the AI."""
75
+ interaction_id: str
76
+ participant_id: str
77
+ timestamp: str
78
+ scenario_id: Optional[str]
79
+
80
+ # Interaction details
81
+ interaction_type: str # "proactive", "reactive_query", "follow_up"
82
+ user_query: Optional[str]
83
+ ai_response: str
84
+
85
+ # Parameters at time of interaction
86
+ explanation_depth: int
87
+ communication_style: int
88
+ confidence_framing: int
89
+ risk_bias: int
90
+
91
+ # Engagement metrics
92
+ response_time_ms: int
93
+ user_engaged: bool # Did user respond/act on advice
94
+ dismissed: bool # For proactive advice
95
+
96
+
97
+ @contextmanager
98
+ def get_db_connection():
99
+ """Context manager for database connections."""
100
+ conn = sqlite3.connect(DATABASE_PATH)
101
+ conn.row_factory = sqlite3.Row
102
+ try:
103
+ yield conn
104
+ conn.commit()
105
+ finally:
106
+ conn.close()
107
+
108
+
109
+ def init_database():
110
+ """Initialize the database with all required tables."""
111
+ with get_db_connection() as conn:
112
+ cursor = conn.cursor()
113
+
114
+ # Sessions table
115
+ cursor.execute("""
116
+ CREATE TABLE IF NOT EXISTS sessions (
117
+ participant_id TEXT PRIMARY KEY,
118
+ session_start TEXT NOT NULL,
119
+ session_end TEXT,
120
+ condition_name TEXT NOT NULL,
121
+ initial_portfolio REAL NOT NULL,
122
+ current_portfolio REAL NOT NULL,
123
+ scenarios_completed INTEGER DEFAULT 0,
124
+ ai_advice_followed INTEGER DEFAULT 0,
125
+ ai_advice_total INTEGER DEFAULT 0,
126
+ total_chat_queries INTEGER DEFAULT 0,
127
+ proactive_advice_accepted INTEGER DEFAULT 0,
128
+ proactive_advice_dismissed INTEGER DEFAULT 0,
129
+ completed INTEGER DEFAULT 0
130
+ )
131
+ """)
132
+
133
+ # Decisions table
134
+ cursor.execute("""
135
+ CREATE TABLE IF NOT EXISTS decisions (
136
+ decision_id TEXT PRIMARY KEY,
137
+ participant_id TEXT NOT NULL,
138
+ timestamp TEXT NOT NULL,
139
+ scenario_id TEXT NOT NULL,
140
+ company_symbol TEXT NOT NULL,
141
+
142
+ -- AI parameters
143
+ explanation_depth INTEGER,
144
+ communication_style INTEGER,
145
+ confidence_framing INTEGER,
146
+ risk_bias INTEGER,
147
+
148
+ -- Decision details
149
+ ai_recommendation TEXT,
150
+ ai_was_correct INTEGER,
151
+ participant_decision TEXT,
152
+ followed_ai INTEGER,
153
+
154
+ -- Confidence and timing
155
+ decision_confidence INTEGER,
156
+ time_to_decision_ms INTEGER,
157
+ time_viewing_ai_advice_ms INTEGER,
158
+
159
+ -- Outcomes
160
+ outcome_percentage REAL,
161
+ portfolio_before REAL,
162
+ portfolio_after REAL,
163
+ trade_amount REAL,
164
+
165
+ -- Proactive advice
166
+ proactive_advice_shown INTEGER,
167
+ proactive_advice_engaged INTEGER,
168
+
169
+ FOREIGN KEY (participant_id) REFERENCES sessions(participant_id)
170
+ )
171
+ """)
172
+
173
+ # Chat interactions table
174
+ cursor.execute("""
175
+ CREATE TABLE IF NOT EXISTS chat_interactions (
176
+ interaction_id TEXT PRIMARY KEY,
177
+ participant_id TEXT NOT NULL,
178
+ timestamp TEXT NOT NULL,
179
+ scenario_id TEXT,
180
+
181
+ -- Interaction details
182
+ interaction_type TEXT NOT NULL,
183
+ user_query TEXT,
184
+ ai_response TEXT NOT NULL,
185
+
186
+ -- AI parameters
187
+ explanation_depth INTEGER,
188
+ communication_style INTEGER,
189
+ confidence_framing INTEGER,
190
+ risk_bias INTEGER,
191
+
192
+ -- Engagement metrics
193
+ response_time_ms INTEGER,
194
+ user_engaged INTEGER,
195
+ dismissed INTEGER,
196
+
197
+ FOREIGN KEY (participant_id) REFERENCES sessions(participant_id)
198
+ )
199
+ """)
200
+
201
+ # Trust metrics table (computed per scenario)
202
+ cursor.execute("""
203
+ CREATE TABLE IF NOT EXISTS trust_metrics (
204
+ metric_id TEXT PRIMARY KEY,
205
+ participant_id TEXT NOT NULL,
206
+ scenario_id TEXT NOT NULL,
207
+ timestamp TEXT NOT NULL,
208
+
209
+ -- Pre/post confidence
210
+ pre_advice_confidence INTEGER,
211
+ post_advice_confidence INTEGER,
212
+ confidence_change INTEGER,
213
+
214
+ -- Behavior indicators
215
+ advice_followed INTEGER,
216
+ time_deliberating_ms INTEGER,
217
+ queries_before_decision INTEGER,
218
+
219
+ -- Outcome
220
+ outcome_positive INTEGER,
221
+
222
+ FOREIGN KEY (participant_id) REFERENCES sessions(participant_id)
223
+ )
224
+ """)
225
+
226
+ # Experiment conditions table (for researcher reference)
227
+ cursor.execute("""
228
+ CREATE TABLE IF NOT EXISTS experiment_conditions (
229
+ condition_name TEXT PRIMARY KEY,
230
+ accuracy_rate REAL,
231
+ proactivity_level INTEGER,
232
+ confidence_framing INTEGER,
233
+ risk_bias INTEGER,
234
+ description TEXT,
235
+ created_at TEXT
236
+ )
237
+ """)
238
+
239
+
240
+ class ExperimentTracker:
241
+ """Main class for tracking experiment data."""
242
+
243
+ def __init__(self):
244
+ init_database()
245
+
246
+ def create_session(
247
+ self,
248
+ condition_name: str,
249
+ initial_portfolio: float
250
+ ) -> str:
251
+ """Create a new participant session and return the participant ID."""
252
+ participant_id = str(uuid.uuid4())[:8] # Short ID for display
253
+ session_start = datetime.now().isoformat()
254
+
255
+ with get_db_connection() as conn:
256
+ cursor = conn.cursor()
257
+ cursor.execute("""
258
+ INSERT INTO sessions (
259
+ participant_id, session_start, condition_name,
260
+ initial_portfolio, current_portfolio
261
+ ) VALUES (?, ?, ?, ?, ?)
262
+ """, (
263
+ participant_id, session_start, condition_name,
264
+ initial_portfolio, initial_portfolio
265
+ ))
266
+
267
+ return participant_id
268
+
269
+ def get_session(self, participant_id: str) -> Optional[Dict]:
270
+ """Retrieve session data for a participant."""
271
+ with get_db_connection() as conn:
272
+ cursor = conn.cursor()
273
+ cursor.execute(
274
+ "SELECT * FROM sessions WHERE participant_id = ?",
275
+ (participant_id,)
276
+ )
277
+ row = cursor.fetchone()
278
+ if row:
279
+ return dict(row)
280
+ return None
281
+
282
+ def update_session(self, participant_id: str, **kwargs):
283
+ """Update session fields."""
284
+ if not kwargs:
285
+ return
286
+
287
+ set_clause = ", ".join(f"{k} = ?" for k in kwargs.keys())
288
+ values = list(kwargs.values()) + [participant_id]
289
+
290
+ with get_db_connection() as conn:
291
+ cursor = conn.cursor()
292
+ cursor.execute(
293
+ f"UPDATE sessions SET {set_clause} WHERE participant_id = ?",
294
+ values
295
+ )
296
+
297
+ def complete_session(self, participant_id: str, final_portfolio: float):
298
+ """Mark a session as completed."""
299
+ self.update_session(
300
+ participant_id,
301
+ session_end=datetime.now().isoformat(),
302
+ current_portfolio=final_portfolio,
303
+ completed=1
304
+ )
305
+
306
+ def record_decision(self, record: DecisionRecord):
307
+ """Record a trading decision."""
308
+ with get_db_connection() as conn:
309
+ cursor = conn.cursor()
310
+ cursor.execute("""
311
+ INSERT INTO decisions (
312
+ decision_id, participant_id, timestamp, scenario_id, company_symbol,
313
+ explanation_depth, communication_style, confidence_framing, risk_bias,
314
+ ai_recommendation, ai_was_correct, participant_decision, followed_ai,
315
+ decision_confidence, time_to_decision_ms, time_viewing_ai_advice_ms,
316
+ outcome_percentage, portfolio_before, portfolio_after, trade_amount,
317
+ proactive_advice_shown, proactive_advice_engaged
318
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
319
+ """, (
320
+ record.decision_id, record.participant_id, record.timestamp,
321
+ record.scenario_id, record.company_symbol,
322
+ record.explanation_depth, record.communication_style,
323
+ record.confidence_framing, record.risk_bias,
324
+ record.ai_recommendation, int(record.ai_was_correct),
325
+ record.participant_decision, int(record.followed_ai),
326
+ record.decision_confidence, record.time_to_decision_ms,
327
+ record.time_viewing_ai_advice_ms,
328
+ record.outcome_percentage, record.portfolio_before,
329
+ record.portfolio_after, record.trade_amount,
330
+ int(record.proactive_advice_shown), int(record.proactive_advice_engaged)
331
+ ))
332
+
333
+ # Update session counters
334
+ session = self.get_session(record.participant_id)
335
+ if session:
336
+ updates = {
337
+ "scenarios_completed": session["scenarios_completed"] + 1,
338
+ "ai_advice_total": session["ai_advice_total"] + 1,
339
+ "current_portfolio": record.portfolio_after
340
+ }
341
+ if record.followed_ai:
342
+ updates["ai_advice_followed"] = session["ai_advice_followed"] + 1
343
+ if record.proactive_advice_shown:
344
+ if record.proactive_advice_engaged:
345
+ updates["proactive_advice_accepted"] = session["proactive_advice_accepted"] + 1
346
+ else:
347
+ updates["proactive_advice_dismissed"] = session["proactive_advice_dismissed"] + 1
348
+
349
+ self.update_session(record.participant_id, **updates)
350
+
351
+ def record_chat_interaction(self, interaction: ChatInteraction):
352
+ """Record a chat interaction."""
353
+ with get_db_connection() as conn:
354
+ cursor = conn.cursor()
355
+ cursor.execute("""
356
+ INSERT INTO chat_interactions (
357
+ interaction_id, participant_id, timestamp, scenario_id,
358
+ interaction_type, user_query, ai_response,
359
+ explanation_depth, communication_style, confidence_framing, risk_bias,
360
+ response_time_ms, user_engaged, dismissed
361
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
362
+ """, (
363
+ interaction.interaction_id, interaction.participant_id,
364
+ interaction.timestamp, interaction.scenario_id,
365
+ interaction.interaction_type, interaction.user_query,
366
+ interaction.ai_response,
367
+ interaction.explanation_depth, interaction.communication_style,
368
+ interaction.confidence_framing, interaction.risk_bias,
369
+ interaction.response_time_ms, int(interaction.user_engaged),
370
+ int(interaction.dismissed)
371
+ ))
372
+
373
+ # Update query count for reactive queries
374
+ if interaction.interaction_type == "reactive_query":
375
+ session = self.get_session(interaction.participant_id)
376
+ if session:
377
+ self.update_session(
378
+ interaction.participant_id,
379
+ total_chat_queries=session["total_chat_queries"] + 1
380
+ )
381
+
382
+ def record_trust_metric(
383
+ self,
384
+ participant_id: str,
385
+ scenario_id: str,
386
+ pre_confidence: int,
387
+ post_confidence: int,
388
+ advice_followed: bool,
389
+ time_deliberating_ms: int,
390
+ queries_before_decision: int,
391
+ outcome_positive: bool
392
+ ):
393
+ """Record trust-related metrics for a scenario."""
394
+ metric_id = str(uuid.uuid4())[:12]
395
+
396
+ with get_db_connection() as conn:
397
+ cursor = conn.cursor()
398
+ cursor.execute("""
399
+ INSERT INTO trust_metrics (
400
+ metric_id, participant_id, scenario_id, timestamp,
401
+ pre_advice_confidence, post_advice_confidence, confidence_change,
402
+ advice_followed, time_deliberating_ms, queries_before_decision,
403
+ outcome_positive
404
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
405
+ """, (
406
+ metric_id, participant_id, scenario_id,
407
+ datetime.now().isoformat(),
408
+ pre_confidence, post_confidence, post_confidence - pre_confidence,
409
+ int(advice_followed), time_deliberating_ms, queries_before_decision,
410
+ int(outcome_positive)
411
+ ))
412
+
413
+ def get_participant_decisions(self, participant_id: str) -> List[Dict]:
414
+ """Get all decisions for a participant."""
415
+ with get_db_connection() as conn:
416
+ cursor = conn.cursor()
417
+ cursor.execute(
418
+ "SELECT * FROM decisions WHERE participant_id = ? ORDER BY timestamp",
419
+ (participant_id,)
420
+ )
421
+ return [dict(row) for row in cursor.fetchall()]
422
+
423
+ def get_participant_interactions(self, participant_id: str) -> List[Dict]:
424
+ """Get all chat interactions for a participant."""
425
+ with get_db_connection() as conn:
426
+ cursor = conn.cursor()
427
+ cursor.execute(
428
+ "SELECT * FROM chat_interactions WHERE participant_id = ? ORDER BY timestamp",
429
+ (participant_id,)
430
+ )
431
+ return [dict(row) for row in cursor.fetchall()]
432
+
433
+ def get_session_summary(self, participant_id: str) -> Dict[str, Any]:
434
+ """Get a summary of a participant's session."""
435
+ session = self.get_session(participant_id)
436
+ if not session:
437
+ return {}
438
+
439
+ decisions = self.get_participant_decisions(participant_id)
440
+ interactions = self.get_participant_interactions(participant_id)
441
+
442
+ # Calculate metrics
443
+ ai_follow_rate = (
444
+ session["ai_advice_followed"] / session["ai_advice_total"]
445
+ if session["ai_advice_total"] > 0 else 0
446
+ )
447
+
448
+ proactive_engage_rate = (
449
+ session["proactive_advice_accepted"] /
450
+ (session["proactive_advice_accepted"] + session["proactive_advice_dismissed"])
451
+ if (session["proactive_advice_accepted"] + session["proactive_advice_dismissed"]) > 0
452
+ else 0
453
+ )
454
+
455
+ portfolio_return = (
456
+ (session["current_portfolio"] - session["initial_portfolio"]) /
457
+ session["initial_portfolio"]
458
+ )
459
+
460
+ # Calculate average decision time
461
+ avg_decision_time = (
462
+ sum(d["time_to_decision_ms"] for d in decisions) / len(decisions)
463
+ if decisions else 0
464
+ )
465
+
466
+ return {
467
+ "participant_id": participant_id,
468
+ "condition": session["condition_name"],
469
+ "completed": bool(session["completed"]),
470
+ "scenarios_completed": session["scenarios_completed"],
471
+ "initial_portfolio": session["initial_portfolio"],
472
+ "final_portfolio": session["current_portfolio"],
473
+ "portfolio_return": portfolio_return,
474
+ "portfolio_return_pct": f"{portfolio_return * 100:.1f}%",
475
+ "ai_follow_rate": ai_follow_rate,
476
+ "ai_follow_rate_pct": f"{ai_follow_rate * 100:.1f}%",
477
+ "proactive_engage_rate": proactive_engage_rate,
478
+ "total_chat_queries": session["total_chat_queries"],
479
+ "avg_decision_time_ms": avg_decision_time,
480
+ "total_decisions": len(decisions),
481
+ "total_interactions": len(interactions)
482
+ }
483
+
484
+ def get_all_sessions(self) -> List[Dict]:
485
+ """Get all sessions for export/analysis."""
486
+ with get_db_connection() as conn:
487
+ cursor = conn.cursor()
488
+ cursor.execute("SELECT * FROM sessions ORDER BY session_start")
489
+ return [dict(row) for row in cursor.fetchall()]
490
+
491
+ def get_all_decisions(self) -> List[Dict]:
492
+ """Get all decisions for export/analysis."""
493
+ with get_db_connection() as conn:
494
+ cursor = conn.cursor()
495
+ cursor.execute("SELECT * FROM decisions ORDER BY timestamp")
496
+ return [dict(row) for row in cursor.fetchall()]
497
+
498
+ def get_all_interactions(self) -> List[Dict]:
499
+ """Get all chat interactions for export/analysis."""
500
+ with get_db_connection() as conn:
501
+ cursor = conn.cursor()
502
+ cursor.execute("SELECT * FROM chat_interactions ORDER BY timestamp")
503
+ return [dict(row) for row in cursor.fetchall()]
504
+
505
+
506
+ # Singleton tracker instance
507
+ tracker = ExperimentTracker()
trading.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Trading engine with scenario management and portfolio tracking.
3
+ Handles game state, decision processing, and outcome calculation.
4
+ """
5
+
6
+ import random
7
+ import uuid
8
+ from datetime import datetime
9
+ from typing import List, Optional, Dict, Any, Tuple
10
+ from dataclasses import dataclass, field
11
+
12
+ from config import (
13
+ Scenario, SCENARIOS, ExperimentConfig, DEFAULT_CONFIG,
14
+ ResearcherControlledParams, ParticipantVisibleParams
15
+ )
16
+
17
+
18
+ @dataclass
19
+ class Portfolio:
20
+ """Represents a participant's portfolio state."""
21
+ cash: float
22
+ initial_value: float
23
+ positions: Dict[str, Dict[str, Any]] = field(default_factory=dict)
24
+ history: List[Dict[str, Any]] = field(default_factory=list)
25
+
26
+ @property
27
+ def total_value(self) -> float:
28
+ """Calculate total portfolio value (cash + positions)."""
29
+ position_value = sum(
30
+ pos["shares"] * pos["current_price"]
31
+ for pos in self.positions.values()
32
+ )
33
+ return self.cash + position_value
34
+
35
+ @property
36
+ def return_percentage(self) -> float:
37
+ """Calculate portfolio return as percentage."""
38
+ if self.initial_value == 0:
39
+ return 0
40
+ return ((self.total_value - self.initial_value) / self.initial_value) * 100
41
+
42
+ def record_state(self, scenario_id: str, action: str, outcome: float):
43
+ """Record a portfolio state change."""
44
+ self.history.append({
45
+ "timestamp": datetime.now().isoformat(),
46
+ "scenario_id": scenario_id,
47
+ "action": action,
48
+ "outcome_pct": outcome,
49
+ "portfolio_value": self.total_value
50
+ })
51
+
52
+
53
+ @dataclass
54
+ class DecisionOutcome:
55
+ """Result of a trading decision."""
56
+ scenario_id: str
57
+ decision: str # "BUY", "SELL", "HOLD"
58
+ outcome_percentage: float
59
+ outcome_amount: float
60
+ portfolio_before: float
61
+ portfolio_after: float
62
+ ai_was_correct: bool
63
+ followed_ai: bool
64
+ was_optimal: bool
65
+
66
+
67
+ class ScenarioManager:
68
+ """Manages scenario selection and ordering for experiments."""
69
+
70
+ def __init__(self, config: ExperimentConfig = DEFAULT_CONFIG):
71
+ self.config = config
72
+ self.all_scenarios = SCENARIOS.copy()
73
+ self.session_scenarios: List[Scenario] = []
74
+ self.current_index: int = 0
75
+
76
+ def initialize_session(self, shuffle: bool = True) -> List[Scenario]:
77
+ """
78
+ Initialize scenarios for a new session.
79
+ Returns the list of scenarios that will be presented.
80
+ """
81
+ # Select scenarios for this session
82
+ num_scenarios = min(self.config.scenarios_per_session, len(self.all_scenarios))
83
+ self.session_scenarios = self.all_scenarios[:num_scenarios]
84
+
85
+ if shuffle:
86
+ random.shuffle(self.session_scenarios)
87
+
88
+ self.current_index = 0
89
+ return self.session_scenarios
90
+
91
+ def get_current_scenario(self) -> Optional[Scenario]:
92
+ """Get the current scenario."""
93
+ if self.current_index < len(self.session_scenarios):
94
+ return self.session_scenarios[self.current_index]
95
+ return None
96
+
97
+ def advance_to_next(self) -> Optional[Scenario]:
98
+ """Move to the next scenario and return it."""
99
+ self.current_index += 1
100
+ return self.get_current_scenario()
101
+
102
+ def get_progress(self) -> Tuple[int, int]:
103
+ """Return (current_number, total) for progress display."""
104
+ return (self.current_index + 1, len(self.session_scenarios))
105
+
106
+ def is_complete(self) -> bool:
107
+ """Check if all scenarios have been completed."""
108
+ return self.current_index >= len(self.session_scenarios)
109
+
110
+ def reset(self):
111
+ """Reset the scenario manager."""
112
+ self.session_scenarios = []
113
+ self.current_index = 0
114
+
115
+
116
+ class TradingEngine:
117
+ """
118
+ Main trading engine that processes decisions and manages game state.
119
+ """
120
+
121
+ def __init__(self, config: ExperimentConfig = DEFAULT_CONFIG):
122
+ self.config = config
123
+ self.portfolio: Optional[Portfolio] = None
124
+ self.scenario_manager = ScenarioManager(config)
125
+ self.decisions_made: List[DecisionOutcome] = []
126
+
127
+ def start_new_game(self) -> Tuple[Portfolio, Scenario]:
128
+ """
129
+ Start a new trading game.
130
+ Returns the initial portfolio and first scenario.
131
+ """
132
+ # Initialize portfolio
133
+ self.portfolio = Portfolio(
134
+ cash=self.config.initial_portfolio_value,
135
+ initial_value=self.config.initial_portfolio_value
136
+ )
137
+
138
+ # Initialize scenarios
139
+ self.scenario_manager.initialize_session(shuffle=True)
140
+ self.decisions_made = []
141
+
142
+ first_scenario = self.scenario_manager.get_current_scenario()
143
+ return self.portfolio, first_scenario
144
+
145
+ def process_decision(
146
+ self,
147
+ scenario: Scenario,
148
+ decision: str, # "BUY", "SELL", "HOLD"
149
+ trade_amount: float,
150
+ ai_recommendation: str
151
+ ) -> DecisionOutcome:
152
+ """
153
+ Process a trading decision and return the outcome.
154
+ """
155
+ # Validate decision
156
+ decision = decision.upper()
157
+ if decision not in ["BUY", "SELL", "HOLD"]:
158
+ raise ValueError(f"Invalid decision: {decision}")
159
+
160
+ # Get outcome percentage based on decision
161
+ outcome_map = {
162
+ "BUY": scenario.outcome_buy,
163
+ "SELL": scenario.outcome_sell,
164
+ "HOLD": scenario.outcome_hold
165
+ }
166
+ outcome_pct = outcome_map[decision]
167
+
168
+ # Calculate outcome amount
169
+ portfolio_before = self.portfolio.total_value
170
+
171
+ # For simplicity, we apply the outcome to the trade amount
172
+ # In a more complex system, you might track actual share positions
173
+ if decision in ["BUY", "HOLD"]:
174
+ # Participant is exposed to the stock's movement
175
+ outcome_amount = trade_amount * outcome_pct
176
+ else: # SELL
177
+ # Participant avoided the stock's movement (inverse)
178
+ outcome_amount = trade_amount * outcome_pct
179
+
180
+ # Update portfolio
181
+ self.portfolio.cash += outcome_amount
182
+ portfolio_after = self.portfolio.total_value
183
+
184
+ # Record state
185
+ self.portfolio.record_state(scenario.scenario_id, decision, outcome_pct)
186
+
187
+ # Determine if AI was followed and if decision was optimal
188
+ followed_ai = (decision == ai_recommendation)
189
+ was_optimal = (decision == scenario.optimal_action)
190
+
191
+ outcome = DecisionOutcome(
192
+ scenario_id=scenario.scenario_id,
193
+ decision=decision,
194
+ outcome_percentage=outcome_pct,
195
+ outcome_amount=outcome_amount,
196
+ portfolio_before=portfolio_before,
197
+ portfolio_after=portfolio_after,
198
+ ai_was_correct=scenario.ai_is_correct,
199
+ followed_ai=followed_ai,
200
+ was_optimal=was_optimal
201
+ )
202
+
203
+ self.decisions_made.append(outcome)
204
+ return outcome
205
+
206
+ def get_next_scenario(self) -> Optional[Scenario]:
207
+ """Get the next scenario in the session."""
208
+ return self.scenario_manager.advance_to_next()
209
+
210
+ def is_game_complete(self) -> bool:
211
+ """Check if the game is complete."""
212
+ return self.scenario_manager.is_complete()
213
+
214
+ def get_game_summary(self) -> Dict[str, Any]:
215
+ """Get a summary of the completed game."""
216
+ if not self.portfolio:
217
+ return {}
218
+
219
+ total_decisions = len(self.decisions_made)
220
+ ai_followed_count = sum(1 for d in self.decisions_made if d.followed_ai)
221
+ optimal_count = sum(1 for d in self.decisions_made if d.was_optimal)
222
+
223
+ # Calculate when AI was correct vs wrong
224
+ ai_correct_decisions = [d for d in self.decisions_made if d.ai_was_correct]
225
+ ai_wrong_decisions = [d for d in self.decisions_made if not d.ai_was_correct]
226
+
227
+ followed_when_correct = sum(1 for d in ai_correct_decisions if d.followed_ai)
228
+ followed_when_wrong = sum(1 for d in ai_wrong_decisions if d.followed_ai)
229
+
230
+ return {
231
+ "initial_portfolio": self.portfolio.initial_value,
232
+ "final_portfolio": self.portfolio.total_value,
233
+ "total_return": self.portfolio.total_value - self.portfolio.initial_value,
234
+ "return_percentage": self.portfolio.return_percentage,
235
+ "total_decisions": total_decisions,
236
+ "ai_follow_rate": ai_followed_count / total_decisions if total_decisions > 0 else 0,
237
+ "optimal_decision_rate": optimal_count / total_decisions if total_decisions > 0 else 0,
238
+ "followed_correct_ai": followed_when_correct,
239
+ "followed_incorrect_ai": followed_when_wrong,
240
+ "ai_correct_scenarios": len(ai_correct_decisions),
241
+ "ai_incorrect_scenarios": len(ai_wrong_decisions),
242
+ "decisions": [
243
+ {
244
+ "scenario": d.scenario_id,
245
+ "decision": d.decision,
246
+ "outcome": f"{d.outcome_percentage * 100:+.1f}%",
247
+ "followed_ai": d.followed_ai,
248
+ "was_optimal": d.was_optimal
249
+ }
250
+ for d in self.decisions_made
251
+ ]
252
+ }
253
+
254
+ def get_progress_info(self) -> Dict[str, Any]:
255
+ """Get current progress information."""
256
+ current, total = self.scenario_manager.get_progress()
257
+ return {
258
+ "current_scenario": current,
259
+ "total_scenarios": total,
260
+ "progress_percentage": (current / total) * 100 if total > 0 else 0,
261
+ "portfolio_value": self.portfolio.total_value if self.portfolio else 0,
262
+ "portfolio_return": self.portfolio.return_percentage if self.portfolio else 0
263
+ }
264
+
265
+
266
+ def calculate_suggested_trade_amount(
267
+ portfolio_value: float,
268
+ risk_level: int = 50
269
+ ) -> float:
270
+ """
271
+ Calculate a suggested trade amount based on portfolio and risk level.
272
+ """
273
+ # Base: 10-30% of portfolio depending on risk level
274
+ min_pct = 0.10
275
+ max_pct = 0.30
276
+ risk_factor = risk_level / 100
277
+
278
+ suggested_pct = min_pct + (max_pct - min_pct) * risk_factor
279
+ return round(portfolio_value * suggested_pct, 2)
280
+
281
+
282
+ def format_currency(amount: float, symbol: str = "credits") -> str:
283
+ """Format a currency amount for display."""
284
+ if amount >= 0:
285
+ return f"{amount:,.2f} {symbol}"
286
+ else:
287
+ return f"-{abs(amount):,.2f} {symbol}"
288
+
289
+
290
+ def format_percentage(value: float, include_sign: bool = True) -> str:
291
+ """Format a percentage for display."""
292
+ if include_sign:
293
+ return f"{value * 100:+.1f}%"
294
+ return f"{value * 100:.1f}%"