Introduction
Welcome back to this series on building an AI-powered stock analysis agent. I’ve previously covered fundamental and technical analysis, sentiment analysis, relative strength rankings, chart-based analysis, and universe scanning.
This time, I’ll focus on risk management, a crucial aspect of trading. Proper risk management is essential for long-term success in the markets. It’s important regardless of the sophistication of one’s analysis tools or the accuracy of one’s predictions.
I’ll discuss two key components: stop placement and position sizing. I’ll explore the importance of placing stops at predefined technical levels and emphasize that position sizing is a personal decision based on individual risk preferences and trading styles. I’ll also introduce R multiples and demonstrate how traders can use them to set profit targets and stops.
By the end of this article, you’ll have a deeper understanding of the basics of risk management principles and learn how to integrate them into one’s trading plan. I’ll also discuss incorporating these concepts into the AI-powered stock analysis agent. Here’s the updated repo. Let’s dive in!
The Importance of Using Stops
Stops are essential components of a trader’s risk management strategy. A stop is a predetermined price level at which a trader will exit a position to limit potential losses. The primary purpose of a stop is to protect trading capital and ensure that losses are kept within acceptable limits.
One key benefit of using stops is the psychological comfort they provide. With a predefined exit point, traders can manage their emotions more effectively, avoiding the temptation to hold onto losing positions in the hope of a rebound. This emotional discipline is crucial for maintaining a clear mind and rational trading decisions.
Moreover, stops help preserve trading capital by limiting downside risk. By capping potential losses on each trade, traders can protect their account balances and ensure that they have sufficient capital to continue trading in the future. This is particularly important for traders with smaller accounts, as a few significant losses can quickly deplete their trading funds.
It’s important to note that stops should be placed at levels where the trade thesis is invalidated. This means that if the price reaches the stop level, the trader has been proven wrong, and it’s time to exit the position. By adhering to this principle, traders can avoid the common mistake of moving their stops further away from their entry point as the price moves against them, which can lead to larger losses.
Traders can use various order types, such as stop orders or stop-limit orders, to automate the process of exiting a position when the price reaches the predetermined stop level. However, it’s crucial to understand that these orders are not guaranteed to be filled at the exact stop price, particularly in fast-moving or gap-down markets. In the following sections, I will explore two ways to calculate stops.
Placing Stops at Technical Levels
When placing stops, traders should aim to set them at levels where the trade thesis is invalidated. One effective approach uses predefined technical levels, such as support, resistance, trendlines, or moving averages. By placing stops at these key levels, traders can ensure that they exit positions when the market signals a change in direction or momentum.
Support and resistance levels, often called pivots, are points where the market has previously shown a tendency to reverse or stall due to supply and demand dynamics. When a stock is trading above a support level, it is generally considered bullish, and a break below this level could indicate a shift in sentiment. Conversely, when a stock is trading below a resistance level, it is generally considered bearish, and a break above this level could signal a change in momentum. By placing stops just below support levels or just above resistance levels, traders can exit positions when the market invalidates their trade thesis.
Trendlines are another useful tool for stop placement. As I’ve highlighted before, a trendline is a straight line that connects a series of price points, representing the overall direction of the market. In an uptrend, a trendline is drawn by connecting the low points of the price action, while in a downtrend, a trendline is drawn by connecting the high points. Traders can place stops just below an uptrend line or just above a downtrend line, allowing them to ride the trend until it is broken.
Moving averages, such as the 20-day, 50-day, or 200-day simple moving average (SMA), can also serve as dynamic support and resistance levels. When a stock is trading above a key moving average, it is generally considered bullish, and a break below the moving average could signal a change in trend. By placing stops just below relevant moving averages, traders can exit positions when the stock shows weakness relative to its average price over a given period.
To identify these technical levels on a chart, traders can use various tools and techniques:
- Horizontal line tool: Draw horizontal lines at key support and resistance levels, connecting previous price points where the stock has reversed or consolidated.
- Trendline tool: Draw trendlines by connecting a series of low points in an uptrend or high points in a downtrend. Extend the trendline into the future to identify potential stop levels.
- Moving average overlay: Add relevant moving averages to the price chart, such as the 20-day, 50-day, or 200-day SMA. Observe where the price trades relative to these averages to determine potential stop levels.
To identify these technical levels on a chart and place stops accordingly, I can extend the AI agent with the following code:
from datetime import date, timedelta
import pandas_ta as ta
from langchain.agents import tool
from app.tools.utils import fetch_stock_data
from app.tools.types import StockStatsInput
def calculate_technical_levels(df):
df["SMA_20"] = ta.sma(df["close"], length=20)
df["SMA_50"] = ta.sma(df["close"], length=50)
df["SMA_200"] = ta.sma(df["close"], length=200)
support = df["low"].rolling(window=20).min().iloc[-1]
resistance = df["high"].rolling(window=20).max().iloc[-1]
return (
support,
resistance,
df["SMA_20"].iloc[-1],
df["SMA_50"].iloc[-1],
df["SMA_200"].iloc[-1],
)
@tool(args_schema=StockStatsInput)
def calculate_technical_stops(symbol: str) -> str:
"""Place stops at key technical levels for a given stock."""
try:
end_date = date.today()
start_date = end_date - timedelta(days=365)
df = fetch_stock_data(symbol, start_date, end_date)
support, resistance, sma_20, sma_50, sma_200 = calculate_technical_levels(df)
stop_levels = [
("Support", support),
("Resistance", resistance),
("20-day SMA", sma_20),
("50-day SMA", sma_50),
("200-day SMA", sma_200),
]
stop_levels_str = "\n".join(
[f"{level[0]}: {level[1]:.2f}" for level in stop_levels]
)
return f"\n<observation>\nPotential stop levels for {symbol}:\n{stop_levels_str}\n</observation>\n"
except Exception as e:
return f"\n<observation>\nError: {e}\n</observation>\n"
In this code:
- The
calculate_technical_levels
function calculates support, resistance, and moving average levels usingpandas_ta
. - The
calculate_technical_stops
tool is created using the@tool
decorator. It fetches the stock data, calculates the technical levels, and returns a formatted string with potential stop levels.
Using R Multiples for Incremental Gains and Stops
R multiples are a powerful concept in trading that allows traders to set profit targets and stops based on their initial risk. The “R” in R multiple stands for “risk,” representing the amount a trader is willing to lose on a single trade. By using R multiples, traders can define their potential gains and losses in terms of their initial risk, making it easier to maintain a consistent risk management strategy.
For example, if a trader buys a stock at $50 with a stop at $49, their initial risk is $1 per share. Setting a profit target at $52 would be a 2R gain (2 times their initial risk). Similarly, if the stock falls to $48, that would be a 2R loss.
To demonstrate how R multiples can be incorporated into the AI agent, let’s create a new tool that calculates potential profit targets and stop-losses based on the initial risk:
from langchain.agents import tool
from langchain_core.pydantic_v1 import BaseModel, Field
class RMultipleInput(BaseModel):
symbol: str = Field(..., description="The stock symbol to analyze")
entry_price: float = Field(..., description="The entry price for the trade")
stop_price: float = Field(..., description="The stop price for the trade")
risk_multiple: int = Field(..., description="The R multiple for the profit target")
@tool(args_schema=RMultipleInput)
def calculate_r_multiples(symbol: str, entry_price: float, stop_price: float, risk_multiple: int) -> str:
"""Calculate potential profit targets and stop-losses based on R multiples."""
try:
initial_risk = abs(entry_price - stop_price)
profit_target = entry_price + (initial_risk * risk_multiple)
stop = entry_price - initial_risk
return f"\n<observation>\nFor {symbol}:\nEntry Price: {entry_price:.2f}\nInitial Risk: {initial_risk:.2f}\nProfit Target ({risk_multiple}R): {profit_target:.2f}\nStop-Loss: {stop:.2f}\n</observation>\n"
except Exception as e:
return f"\n<observation>\nError: {e}\n</observation>\n"
In this code:
- I define an
RMultipleInput
schema that includes the stock symbol, entry price, stop price, and the desired R multiple for the profit target. - The
calculate_r_multiples
tool is created using the@tool
decorator. It calculates the initial risk, profit target, and stop-loss based on the provided inputs and returns a formatted string with the results.
To use this tool, traders would provide the entry price, stop price, and their desired R multiple for the profit target to the AI agent. The tool then calculates the initial risk, profit target, and stop, allowing traders to visualize their potential gains and losses in terms of R multiples when plotted.
By incorporating this tool into the AI agent, traders can quickly determine their profit targets and stop based on their initial risk, making it easier to maintain a consistent risk management strategy.
Depending on their trading style and market conditions, traders can use different R multiples for their profit targets. For example, a trader might set a 1R profit target for a portion of their position, a 2R target for another portion, and let the remainder ride until a trailing stop is hit. This approach allows traders to take incremental gains while allowing for larger profits if the trade continues to move in their favor. This is often referred to as “taking partials”.
Position Sizing: A Personal Decision
Position sizing refers to the number of shares or contracts a trader buys or sells in a single trade. Risk management is a crucial aspect, as it directly impacts a trade’s potential profit or loss. Proper position sizing helps traders maintain a balanced portfolio and ensures that no single trade can cause significant damage to their accounts.
The key factors that influence position sizing include:
- Account size: Larger accounts can generally afford to take on larger positions, while smaller accounts should be more conservative.
- Risk tolerance: Traders with a higher risk tolerance may be comfortable taking more significant positions, while more risk-averse traders prefer smaller positions.
- Trading style: Day traders and scalpers may take on larger positions for shorter periods, while swing traders and investors may hold smaller positions for medium-term and longer durations.
- Volatility: In more volatile markets or with more volatile stocks, traders may opt for smaller position sizes to minimize risk. Some may only buy pilot positions to test the waters.
It’s essential to understand that there is no one-size-fits-all approach to position sizing. Each trader must determine their comfort level and risk tolerance based on their circumstances and trading goals. To demonstrate how position sizing can be incorporated into the AI agent, let’s create a new tool that calculates the optimal position size based on a trader’s account size and risk tolerance:
from langchain.agents import tool
from langchain_core.pydantic_v1 import BaseModel, Field
class PositionSizingInput(BaseModel):
symbol: str = Field(..., description="The stock symbol to analyze")
account_size: float = Field(..., description="The total account size in dollars")
risk_percent: float = Field(..., description="The percentage of the account to risk on the trade")
entry_price: float = Field(..., description="The entry price for the trade")
stop_price: float = Field(..., description="The stop price for the trade")
@tool(args_schema=PositionSizingInput)
def calculate_position_size(symbol: str, account_size: float, risk_percent: float, entry_price: float, stop_price: float) -> str:
"""Calculate the optimal position size for a trade based on account size and risk tolerance."""
try:
risk_amount = account_size * (risk_percent / 100)
potential_loss = abs(entry_price - stop_price)
position_size = risk_amount / potential_loss
return f"\n<observation>\nOptimal position size for {symbol}: {position_size:.2f} shares\nEntry Price: {entry_price:.2f}\nStop Price: {stop_price:.2f}\nPotential Loss Per Share: {potential_loss:.2f}\n</observation>\n"
except Exception as e:
return f"\n<observation>\nError: {e}\n</observation>\n"
In this code:
- I define a
PositionSizingInput
schema that includes the stock symbol, account size, risk percentage, and stop distance. - The
calculate_position_size
tool is created using the@tool
decorator. It calculates the optimal position size based on the inputs and returns the result as a formatted string.
To use this tool, traders must provide their account size, risk percentage (e.g., 1% or 2% of their account), the entry price, and the stop price. The tool then calculates the appropriate position size, ensuring the potential loss falls within the trader’s risk tolerance.
By incorporating this tool into the AI agent, traders can receive personalized recommendations for position sizing based on their preferences and risk management strategy. It’s important to note that this is a simplified example, and traders should always consider additional factors, such as liquidity and overall market conditions when determining their position size. Moreover, consistently applying a well-defined position sizing strategy across all trades is crucial for long-term market success.
Conclusion
In this article, I’ve explored the crucial role of risk management in trading and how it can be integrated into the AI-powered stock analysis agent. By focusing on stop placement, position sizing, and R multiples, I’ve demonstrated how traders can protect their capital, limit potential losses, and maintain a consistent risk management strategy.
I’ve explained how the AI agent can analyze technical levels and suggest appropriate stop placements for a given stock. Furthermore, I’ve shown how it can provide personalized recommendations for position sizing based on a trader’s preferences and risk tolerance. Finally, I’ve introduced the concept of R multiples and how they can be used to set profit targets and stops based on the initial risk.
I’ve come a long way in this series of articles. Of course, one can enhance this AI agent further in many ways. Some may want optimal portfolio construction to hold for longer periods. Some may want to build in ML techniques or custom indicators. My suggestion is to make this AI agent your own. Experiment with additional data from OpenBB, add your own risk management strategy and tweak the prompt to your liking. Learn by doing.
Now that I’ve got this AI agent in a functional state, it’s time to add some targeted FSA finesse. And that’s where LangGraph comes in. In the next article, I will pivot (no pun intended) from using the AgentExecutor LangChain class to a more powerful abstraction using LangGraph that allows us to represent these stateful chains as independent actors, each with their own independent operations (nodes) and best of all, direction (edges). So, refresh that graph theory you thought you forgot in CS 4500 because it’s time to continue swapping symbols.
Leave a Reply