Merge pull request #2 from kevin-bruton/add-frontend

feat: factor in user's current position and SL/TP orders
This commit is contained in:
kevin-bruton 2025-09-30 01:19:05 +02:00 committed by GitHub
commit 0980630e1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 137 additions and 14 deletions

View File

@ -159,6 +159,16 @@ An interface will appear showing results as they load, letting you track the age
In addition to the CLI, a new web-based frontend is available to visualize the agent communication process in real-time. It allows you to set configuration parameters, start the trading analysis, and observe the step-by-step execution of agents and tools, including their outputs and any errors.
### New: User Position Awareness
You can now optionally specify your current position (None / Long / Short) along with existing stop-loss and take-profit levels. These inputs are incorporated by the Trade Planner and Risk / Portfolio Manager to:
- Decide whether to maintain, adjust, or close the current position
- Recommend flipping (e.g., long -> short) only when risk/reward justifies transaction costs
- Adjust existing stop-loss / take-profit levels (tighten, widen, trail, move to breakeven)
- Avoid unnecessary churn when changes would not exceed the trading cost threshold
If you leave these fields blank or select "No Open Position", the system will generate fresh trade planning levels as usual.
### Running the Web Frontend
1. Ensure you have installed all dependencies using `uv sync`.
@ -169,6 +179,7 @@ In addition to the CLI, a new web-based frontend is available to visualize the a
```
4. Open your web browser and go to `http://127.0.0.1:8000`.
5. Enter a company symbol (e.g., `AAPL`) in the configuration form and click "Start Process" to begin the analysis.
6. (Optional) If you have an open position, select Long/Short and enter existing stop-loss / take-profit so the final decision can include management guidance.
### Rendered Reports (Markdown Support)

Binary file not shown.

19
test_position_flow.py Normal file
View File

@ -0,0 +1,19 @@
import pytest
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG
@pytest.mark.skip(reason="Integration style test requires API keys and network access")
def test_initial_state_includes_position():
cfg = DEFAULT_CONFIG.copy()
cfg["llm_provider"] = "openai" # or mock provider if available
cfg["quick_think_llm"] = cfg.get("quick_think_llm", "gpt-4o-mini")
cfg["deep_think_llm"] = cfg.get("deep_think_llm", "gpt-4o")
graph = TradingAgentsGraph(config=cfg)
# We won't actually run LLM calls; just inspect initial state creation
init_state = graph.propagator.create_initial_state(
"AAPL", "2025-09-30", user_position="long", cost_per_trade=2.0, initial_stop_loss=150.0, initial_take_profit=175.0
)
assert init_state["user_position"] == "long"
assert init_state["current_position_stop_loss"] == 150.0
assert init_state["current_position_take_profit"] == 175.0

View File

@ -4,7 +4,6 @@ import json
def create_risk_manager(llm, memory):
def risk_manager_node(state) -> dict:
company_name = state["company_of_interest"]
history = state["risk_debate_state"]["history"]
@ -16,6 +15,8 @@ def create_risk_manager(llm, memory):
trader_plan = state["investment_plan"]
stop_loss = state.get("stop_loss")
take_profit = state.get("take_profit")
existing_sl = state.get("current_position_stop_loss")
existing_tp = state.get("current_position_take_profit")
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}"
past_memories = memory.get_memories(curr_situation, n_matches=2)
@ -41,10 +42,11 @@ Guidelines for Decision-Making:
2. **Provide Rationale**: Support your recommendation with direct quotes and counterarguments from the debate.
3. **Refine the Trader's Plan**: Start with the trader's original plan, **{trader_plan}**, and adjust it based on the analysts' insights.
4. **Incorporate Technical Analysis**: The Trade Planner has proposed a stop-loss of **{stop_loss}** and a take-profit of **{take_profit}**. You must consider these levels in your final recommendation.
5. **Existing Position Levels (if any)**: The user's current position management levels are stop-loss **{existing_sl}** and take-profit **{existing_tp}**. If the user already has levels and they materially differ from new recommendations, clearly state whether to adjust them (tighten, loosen, move to breakeven, trail, etc.) and why.
5. **Learn from Past Mistakes**: Use lessons from **{past_memory_str}** to address prior misjudgments and improve the decision you are making now to make sure you don't make a wrong decision that loses money.
Deliverables:
- A clear and actionable recommendation.
- A clear and actionable recommendation explicitly stating: maintain/close/flip position (or open new), any adjustments to stop-loss / take-profit (include both old and new if changed), and risk management rationale.
- Detailed reasoning anchored in the debate and past reflections.
---
@ -55,9 +57,8 @@ Deliverables:
---
Focus on actionable insights and continuous improvement. Build on past lessons, critically evaluate all perspectives, and ensure each decision advances better outcomes."""
response = llm.invoke(prompt)
final_decision_content = response.content
new_risk_debate_state = {
"judge_decision": response.content,

View File

@ -16,6 +16,10 @@ def create_trade_planner_agent(llm, toolkit):
toolkit.get_stockstats_indicators_report,
]
existing_sl = state.get("current_position_stop_loss")
existing_tp = state.get("current_position_take_profit")
user_position = state.get("user_position", "none")
prompt = f'''You are a trade planner. Your role is to determine the stop-loss and take-profit levels for a given investment plan.
Analyze the following market report and investment plan to determine the optimal stop-loss and take-profit levels. You should use the available tools to get the latest market data and calculate technical indicators.
@ -28,6 +32,8 @@ Analyze the following market report and investment plan to determine the optimal
Use technical indicators such as Pivots, ATR, support and resistance levels, Donchian Channels, SuperTrend, etc., as well as risk factors to determine the stop-loss and take-profit levels.
If the user already has an open position (user_position = {user_position}) and existing management levels (stop_loss={existing_sl}, take_profit={existing_tp}), evaluate whether to keep, tighten, widen, trail, or remove them. Only change existing levels if your analysis strongly supports it. If no position is open, propose initial levels suitable for a new trade.
Based on your analysis, provide the stop-loss and take-profit levels in a JSON format. For example:
{{
"stop_loss": 150.00,
@ -39,7 +45,7 @@ Do not provide any other information or explanation.
'''
response = llm.invoke(prompt)
try:
levels = json.loads(response.content)
stop_loss = levels.get("stop_loss")

View File

@ -16,7 +16,13 @@ class Propagator:
self.max_recur_limit = max_recur_limit
def create_initial_state(
self, company_name: str, trade_date: str, user_position: str = "none", cost_per_trade: float = 0.0
self,
company_name: str,
trade_date: str,
user_position: str = "none",
cost_per_trade: float = 0.0,
initial_stop_loss=None,
initial_take_profit=None,
) -> Dict[str, Any]:
"""Create the initial state for the agent graph."""
return {
@ -25,6 +31,9 @@ class Propagator:
"trade_date": str(trade_date),
"user_position": user_position,
"cost_per_trade": cost_per_trade,
# User-provided existing position management levels (may be None)
"current_position_stop_loss": initial_stop_loss,
"current_position_take_profit": initial_take_profit,
"investment_debate_state": InvestDebateState(
{"history": "", "current_response": "", "count": 0}
),

View File

@ -229,14 +229,19 @@ class TradingAgentsGraph:
),
}
def propagate(self, company_name, trade_date, user_position="none", cost_per_trade=0.0, on_step_callback=None):
def propagate(self, company_name, trade_date, user_position="none", cost_per_trade=0.0, on_step_callback=None, initial_stop_loss=None, initial_take_profit=None):
"""Run the trading agents graph for a company on a specific date."""
self.ticker = company_name
# Initialize state
init_agent_state = self.propagator.create_initial_state(
company_name, trade_date, user_position, cost_per_trade
company_name,
trade_date,
user_position,
cost_per_trade,
initial_stop_loss=initial_stop_loss,
initial_take_profit=initial_take_profit,
)
args = self.propagator.get_graph_args()

View File

@ -267,7 +267,7 @@ def create_agent_node(agent_id: str, agent_name: str):
"children": [
{
"id": f"{agent_id}_messages",
"name": "<EFBFBD> Messages",
"name": "💬 Messages",
"status": "pending",
"content": "No messages yet",
"children": [],
@ -275,7 +275,7 @@ def create_agent_node(agent_id: str, agent_name: str):
},
{
"id": f"{agent_id}_report",
"name": "<EFBFBD> Report",
"name": "📄 Report",
"status": "pending",
"content": "Report not yet generated",
"children": [],
@ -520,8 +520,21 @@ def run_trading_process(company_symbol: str, config: Dict[str, Any]):
analysis_date = config["analysis_date"] # Use user-selected date
print(f"🔄 Starting propagation for {company_symbol} on {analysis_date}")
# The propagate method now accepts the callback and trade_date
final_state, processed_signal = graph.propagate(company_symbol, trade_date=analysis_date, on_step_callback=update_execution_state)
# Include user position context
user_position = config.get("user_position", "none")
init_sl = config.get("initial_stop_loss")
init_tp = config.get("initial_take_profit")
# The propagate method now accepts the callback and trade_date and we will inject user position.
final_state, processed_signal = graph.propagate(
company_symbol,
trade_date=analysis_date,
user_position=user_position,
cost_per_trade=config.get("cost_per_trade", 0.0),
on_step_callback=update_execution_state,
initial_stop_loss=init_sl,
initial_take_profit=init_tp,
)
print(f"✅ Propagation completed for {company_symbol}")
with app_state_lock:
@ -571,7 +584,10 @@ async def start_process(
deep_think_llm: str = Form(...),
max_debate_rounds: int = Form(...),
cost_per_trade: float = Form(...),
analysis_date: str = Form(...)
analysis_date: str = Form(...),
position_status: str = Form("none"),
current_stop_loss: str | None = Form(None),
current_take_profit: str | None = Form(None)
):
# Check if all required environment variables are set
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
@ -600,13 +616,40 @@ async def start_process(
app_state["overall_progress"] = 5 # Show initial progress
# Store all configuration parameters
# Validate and normalize position inputs
position_status = (position_status or "none").lower()
if position_status not in ("none", "long", "short"):
position_status = "none"
def _parse_level(val: str | None):
if val is None or val == "":
return None
try:
f = float(val)
if f <= 0:
return None
return f
except ValueError:
return None
initial_stop_loss = _parse_level(current_stop_loss)
initial_take_profit = _parse_level(current_take_profit)
# If no open position, ignore provided levels
if position_status == "none":
initial_stop_loss = None
initial_take_profit = None
app_state["config"] = {
"llm_provider": llm_provider,
"quick_think_llm": quick_think_llm,
"deep_think_llm": deep_think_llm,
"max_debate_rounds": max_debate_rounds,
"cost_per_trade": cost_per_trade,
"analysis_date": analysis_date
"analysis_date": analysis_date,
"user_position": position_status,
"initial_stop_loss": initial_stop_loss,
"initial_take_profit": initial_take_profit
}
# Initialize execution tree with complete structure

View File

@ -64,6 +64,23 @@
<label for="analysis_date">Analysis Date:</label>
<input type="date" id="analysis_date" name="analysis_date" value="{{ default_date }}" required>
<fieldset style="margin-top:1rem; border:1px solid #444; padding:0.75rem;">
<legend>Current Position (Optional)</legend>
<label for="position_status">Position Status:</label>
<select id="position_status" name="position_status" onchange="togglePositionFields()">
<option value="none" selected>No Open Position</option>
<option value="long">Long Position</option>
<option value="short">Short Position</option>
</select>
<div id="position-details" style="display:none; margin-top:0.5rem;">
<label for="current_stop_loss">Existing Stop Loss:</label>
<input type="number" step="0.01" min="0" id="current_stop_loss" name="current_stop_loss" placeholder="e.g. 150.25">
<label for="current_take_profit">Existing Take Profit:</label>
<input type="number" step="0.01" min="0" id="current_take_profit" name="current_take_profit" placeholder="e.g. 180.00">
<small>If left blank, no current levels will be assumed. New levels may be suggested.</small>
</div>
</fieldset>
<button type="submit">Start Process</button>
<div id="loading" class="htmx-indicator">Starting process...</div>
@ -184,6 +201,18 @@
}
}
function togglePositionFields() {
const status = document.getElementById('position_status').value;
const details = document.getElementById('position-details');
if (status === 'long' || status === 'short') {
details.style.display = 'block';
} else {
details.style.display = 'none';
document.getElementById('current_stop_loss').value = '';
document.getElementById('current_take_profit').value = '';
}
}
// Set current date as default for analysis_date
function setCurrentDate() {
const el = document.getElementById('analysis_date');