This Python code defines a modular trading bot capable of executing buy/sell operations based on trading signals from a strategy. It manages balance, inventory, stop-loss, take-profit, and logs detailed history for analysis.
High-Level Purpose
This trading bot:
- Uses signals (BUY, SELL, HOLD) from a strategy.
- Manages an account balance and inventory.
- Executes trades while considering fees, minimum purchase quantity, stop-loss, and take-profit mechanisms.
- Keeps a detailed log of each trade and overall performance.
Key Components
1. BotAction Enum
Defines constant labels for different actions the bot can perform. This makes the logic clear and avoids “magic strings” in the code.
class BotAction(Enum):
BUY = "Buy"
SELL = "Sell"
HOLD = "Hold"
InsufficientBalance = "Insufficient Balance"
NothingToSell = "Nothing to Sell"
StopLoss = "Stop Loss"
TakeProfit = "Take Profit"
CannotBuyMultiple = "Cannot Buy Multiple"2. Inventory Class
Manages the quantity and cost of the assets held.
Logic:
- Tracks total quantity and buy cost.
- Computes average cost per unit.
- Updates inventory after buys and sells.
class Inventory:
def __init__(self):
self.quantity = 0
self.buy_cost = 0
@property
def average_cost(self):
return self.buy_cost / self.quantity
def add_buy(self, buy_cost, quantiy):
self.quantity += quantiy
self.buy_cost += buy_cost
def add_sell(self, quantiy):
self.quantity -= quantiy
if self.quantity > 0:
self.buy_cost = self.average_cost * self.quantity
else:
self.buy_cost = 03. TradingBot Class
Handles:
- Account balance.
- Trading strategy.
- Trade execution.
- Profit/Loss tracking.
- Logging all activities.
class TradingBot:
"""Trading Bot that executes orders based on a given strategy."""
def __init__(
self,
initial_balance: int,
strategy: TradingStrategy,
trading_fees: float = 0.001,
minimum_quantity_purchaseable: float = 0.01,
maximum_quantity_purchaseable: str = "all",
stop_loss_percentage=10,
take_profit_percentage=10,
allow_multiple_buy=False,
):
self.strategy = strategy
self.inventory = Inventory()
self.trading_fees = trading_fees
self.minimum_quantity_purchaseable = minimum_quantity_purchaseable
self.initial_balance = initial_balance
self.balance = initial_balance
self.stop_loss_percentage = stop_loss_percentage / 100
self.take_profit_percentage = take_profit_percentage / 100
self.dynamic_stop_loss_value = None
self.allow_multiple_buy = allow_multiple_buy
self.net_profit_loss = 0
self.order_history = []
self.history = {
"Datetime": [],
"Close": [],
"Balance": [],
"worth": [],
"Inventory_qty": [],
"Inventory_amount": [],
"Stop_Loss": [],
"Signal": [],
"Action": [],
"Profit_Loss": [],
"Profit_Loss_Percentage": [],
"Net Profit_Loss": [],
"ActionBuy": [],
"ActionSell": [],
}
def set_strategy(self, strategy: TradingStrategy):
"""Allows switching trading strategies dynamically."""
self.strategy = strategy
def execute_trade(self, data):
"""Executes trade based on strategy signal."""
for index, row in data.iterrows():
signal = self.strategy.generate_signal(row)
self.history["Datetime"].append(row["Datetime"])
self.history["Close"].append(row["Close"])
self.history["Balance"].append(round(self.balance, 2))
self.history["Signal"].append(signal.name)
self.place_order(signal, row["Close"])
def buy_cost(self, price, quantity):
buy_cost = (quantity * price) * (1 + self.trading_fees)
return buy_cost
def sell_cost(self, price, quantity):
sell_cost = (quantity * price) * (1 - self.trading_fees)
return sell_cost
def purchaseable_quantity(self, price):
return self.balance / price
def buy(self, price, quantity):
buy_cost = self.buy_cost(price, quantity)
self.balance -= buy_cost
self.inventory.add_buy(buy_cost, quantity)
self.history["Inventory_qty"].append(self.inventory.quantity)
self.history["Inventory_amount"].append(self.inventory.buy_cost)
self.history["Action"].append(BotAction.BUY.value)
self.history["Profit_Loss"].append(0)
self.history["Profit_Loss_Percentage"].append(0)
self.history["Net Profit_Loss"].append(self.net_profit_loss)
self.history["ActionBuy"].append(price)
self.history["ActionSell"].append(0)
stop_loss_value = self.inventory.buy_cost * (1 - self.stop_loss_percentage)
self.dynamic_stop_loss_value = stop_loss_value
def sell(self, price, quantity, reason):
sell_cost = self.sell_cost(price, quantity)
profit_loss = sell_cost - self.inventory.buy_cost
profit_loss_percentage = (profit_loss / self.inventory.buy_cost) * 100
self.net_profit_loss += profit_loss
self.balance += sell_cost
self.inventory.add_sell(quantity)
self.dynamic_stop_loss_value = None
self.history["Inventory_qty"].append(self.inventory.quantity)
self.history["Inventory_amount"].append(self.inventory.buy_cost)
self.history["Action"].append(reason)
self.history["Profit_Loss"].append(profit_loss)
self.history["Profit_Loss_Percentage"].append(profit_loss_percentage)
self.history["Net Profit_Loss"].append(self.net_profit_loss)
self.history["ActionBuy"].append(0)
self.history["ActionSell"].append(price)
def do_nothing(self, action):
self.history["Inventory_qty"].append(self.inventory.quantity)
self.history["Inventory_amount"].append(self.inventory.buy_cost)
self.history["Action"].append(action)
self.history["Profit_Loss"].append(0)
self.history["Profit_Loss_Percentage"].append(0)
self.history["Net Profit_Loss"].append(self.net_profit_loss)
self.history["ActionBuy"].append(0)
self.history["ActionSell"].append(0)
def place_order(self, order_type: str, price: float):
"""Simulates placing an order."""
buy_quantity = self.minimum_quantity_purchaseable
minimum_buy_cost = self.buy_cost(
price,
buy_quantity
)
sell_quantity = self.inventory.quantity
sell_cost = self.sell_cost(price, sell_quantity)
self.history["worth"].append(sell_cost)
profit_loss = sell_cost - self.inventory.buy_cost
if self.inventory.quantity > 0:
stop_loss_value = sell_cost * (1 - self.stop_loss_percentage)
if stop_loss_value > self.dynamic_stop_loss_value:
self.dynamic_stop_loss_value = stop_loss_value
self.history["Stop_Loss"].append(f"{self.dynamic_stop_loss_value}")
# Stop Loss
if (self.stop_loss_percentage != 0) and (
sell_cost < self.dynamic_stop_loss_value
) and (self.inventory.quantity > 0):
self.sell(price, sell_quantity, BotAction.StopLoss.value)
return
# Take Profit
if (self.take_profit_percentage != 0) and (
profit_loss > (self.inventory.buy_cost * self.take_profit_percentage)
) and (self.inventory.quantity > 0):
self.sell(price, sell_quantity, BotAction.TakeProfit.value)
return
else:
self.history["Stop_Loss"].append(0)
if order_type == TradingSignal.HOLD:
self.do_nothing(BotAction.HOLD.value)
elif order_type == TradingSignal.BUY:
if self.balance < minimum_buy_cost:
self.do_nothing(BotAction.InsufficientBalance.value)
elif not self.allow_multiple_buy:
if self.inventory.quantity == 0:
self.buy(price, buy_quantity)
else:
self.do_nothing(BotAction.CannotBuyMultiple.value)
else:
self.buy(price, buy_quantity)
elif order_type == TradingSignal.SELL:
if self.inventory.quantity > 0:
self.sell(price, sell_quantity, BotAction.SELL.value)
else:
self.do_nothing(BotAction.SELL.NothingToSell.value)Key Parameters of TradingBot:
Parameter Purpose initial_balance Starting cash balance. strategy External strategy that provides signals. trading_fees Fee percentage per trade. minimum_quantity_purchaseable Smallest allowable purchase. stop_loss_percentage Auto-sell to limit losses. take_profit_percentage Auto-sell to secure profits. allow_multiple_buy If False, prevents buying when already holding stock.
How the Bot Works (Step-by-Step):
1. Signal Execution
In execute_trade(), the bot processes each row (candlestick or price data):
for index, row in data.iterrows():
signal = self.strategy.generate_signal(row)
self.place_order(signal, row["Close"])2. Order Placement
In place_order():
- The bot checks:
- Should we BUY, SELL, or HOLD?
- Are we in a stop-loss or take-profit scenario?
- Do we have enough balance or stock?
Stop-Loss Logic:
If the total value of the held stock drops below the dynamic stop-loss value, the bot sells to minimize losses.
if sell_cost < self.dynamic_stop_loss_value:
self.sell(price, sell_quantity, BotAction.StopLoss.value)Take-Profit Logic:
If the profit exceeds a defined percentage, the bot locks in profits by selling.
if profit_loss > (self.inventory.buy_cost * self.take_profit_percentage):
self.sell(price, sell_quantity, BotAction.TakeProfit.value)BUY Logic:
- Check balance.
- Check whether multiple buys are allowed.
- Execute the purchase if valid.
if self.balance < minimum_buy_cost:
self.do_nothing(BotAction.InsufficientBalance.value)
elif not self.allow_multiple_buy:
if self.inventory.quantity == 0:
self.buy(price, buy_quantity)
else:
self.do_nothing(BotAction.CannotBuyMultiple.value)
else:
self.buy(price, buy_quantity)SELL Logic:
- If we have stock, sell all.
- If we don’t, log “Nothing to Sell”.
if self.inventory.quantity > 0:
self.sell(price, sell_quantity, BotAction.SELL.value)
else:
self.do_nothing(BotAction.SELL.NothingToSell.value)3. Cost Calculations
Includes trading fees:
def buy_cost(self, price, quantity):
return (quantity * price) * (1 + self.trading_fees)Similarly for sell costs.
def sell_cost(self, price, quantity):
return (quantity * price) * (1 - self.trading_fees)4. History Logging
Everything (actions, profits, inventory levels) is logged in self.history for further analysis or visualization.
5. Dynamic Stop-Loss Update
After buying or during price increases, the stop-loss value is adjusted upwards to protect profits:
if stop_loss_value > self.dynamic_stop_loss_value:
self.dynamic_stop_loss_value = stop_loss_valueWhy This Design?
Feature Reasoning Enums for Actions Clean and readable code. Separate Inventory Class Isolates stock handling logic. Dynamic Stop-Loss Protects against large losses while allowing gains. Strategy Injection Supports different strategies without modifying the bot. Full History Logging For audit, debugging, and performance analysis.
Summary of Flow:
- For each market tick: Get signal → place corresponding order.
- Before acting: Check for stop-loss or take-profit conditions.
- Execute BUY/SELL/HOLD as appropriate.
- Update balance, inventory, profits.
- Log all actions and results.
What Makes This Bot Solid?
- Separation of concerns (strategy vs. execution).
- Extendable (plug different strategies easily).
- Protects capital (stop-loss, take-profit).
- Balances multiple conditions (balance check, multiple buys).
- Performance tracking (history dictionary).
Final Thoughts
This is a well-structured bot for backtesting and simulation purposes. With minor tweaks, it could integrate with real trading APIs like Binance or KuCoin.
It ensures:
- Smart decision-making.
- Risk management.
- Data tracking.
Would you like me to show how to plug this bot into a real exchange or backtest on a dataset?
No comments:
Post a Comment