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:
commit
0980630e1e
11
README.md
11
README.md
|
|
@ -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.
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
Loading…
Reference in New Issue