Skip to content

Commit

Permalink
Organized project
Browse files Browse the repository at this point in the history
  • Loading branch information
slowpoke111 committed Nov 10, 2024
1 parent 55bb032 commit 3cfb1ee
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 71 deletions.
15 changes: 7 additions & 8 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
calculateSharpeRatio,
calculateVolatility,
calculateBeta,
calculateReturnStats,
detectCrossover,
detectCrossunder,
getCrossSignals
calculateReturnStats
)
from .commissions import calculate_commission
from .orders import cancel_order, submit_gtc_order

__version__ = "1.0.1"
__version__ = "1.1.3"
__author__ = "Ben Bell"

__all__ = [
Expand All @@ -37,7 +36,7 @@
"calculateVolatility",
"calculateBeta",
"calculateReturnStats",
"detectCrossover",
"detectCrossunder",
"getCrossSignals"
"calculate_commission",
"cancel_order",
"submit_gtc_order"
]
47 changes: 7 additions & 40 deletions backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from pandas.tseries.offsets import DateOffset
from typing import TYPE_CHECKING
from pyBacktest.trades import execute_buy, execute_sell, execute_market_buy, execute_market_sell, execute_short_sell, execute_short_cover
from pyBacktest.tradeTypes import TradeType, Holding, Transaction, Order, InvalidCommissionTypeError, InvalidOrderError
from pyBacktest.tradeTypes import TradeType, Holding, Transaction, Order, InvalidOrderError
from pyBacktest.commissions import calculate_commission
from pyBacktest.orders import cancel_order, submit_gtc_order
from enum import Enum

if TYPE_CHECKING:
Expand Down Expand Up @@ -74,48 +76,13 @@ def formatDate(self, date: datetime) -> pd.Timestamp:
return self.getValidDate(date)

def calculateCommision(self, price: float, numShares: int) -> float:
if self.commisionType == "FLAT":
return self.commision
elif self.commisionType == "PERCENTAGE":
return price * self.commision * numShares
elif self.commisionType == "PERCENTAGE_PER_SHARE":
return self.commision * numShares
elif self.commisionType == "PER_SHARE":
return self.commision * numShares
else:
raise InvalidCommissionTypeError(f"Invalid commission type: {self.commisionType}")
return calculate_commission(self.commisionType, self.commision, price, numShares)

def cancelOrder(self, order_index: int) -> bool:
if 0 <= order_index < len(self.pending_orders):
order = self.pending_orders[order_index]
order.active = False
self.transactions.append(
Transaction(
tradeType=TradeType.Cancel,
ticker=order.ticker,
commission=0,
executedSuccessfully=True,
numShares=order.numShares,
pricePerShare=order.targetPrice,
totalCost=0,
date=self.date,
notes="Order canceled"
)
)
return True
return False
return cancel_order(self, order_index)

def submitGTCOrder(self, tradeType: TradeType, numShares: int, targetPrice: float) -> Order:
order = Order(
tradeType=tradeType,
ticker=self.ticker,
numShares=numShares,
targetPrice=targetPrice,
duration='GTC',
orderDate=self.date
)
self.pending_orders.append(order)
return order
return submit_gtc_order(self, tradeType, numShares, targetPrice)

def _check_pending_orders(self, current_price: float):
for order in self.pending_orders[:]: # Use slice copy to modify safely
Expand Down Expand Up @@ -163,7 +130,7 @@ def run(self) -> Dict[str, Any]:
return {
"final_value": self.totalValue(),
"transactions": self.transactions,
"strategy": self.strategy # Add strategy to results
"strategy": self.strategy
}

def _execute_buy(self, price: float, numShares: int, valid_date: pd.Timestamp, trade_type: TradeType = TradeType.BUY) -> Holding:
Expand Down
13 changes: 13 additions & 0 deletions commissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pyBacktest.tradeTypes import InvalidCommissionTypeError

def calculate_commission(commisionType: str, commision: float, price: float, numShares: int) -> float:
if commisionType == "FLAT":
return commision
elif commisionType == "PERCENTAGE":
return price * commision * numShares
elif commisionType == "PERCENTAGE_PER_SHARE":
return commision * numShares
elif commisionType == "PER_SHARE":
return commision * numShares
else:
raise InvalidCommissionTypeError(f"Invalid commission type: {commisionType}")
34 changes: 34 additions & 0 deletions orders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from pyBacktest.tradeTypes import TradeType, Order, Transaction
from datetime import datetime

def cancel_order(backtest, order_index: int) -> bool:
if 0 <= order_index < len(backtest.pending_orders):
order = backtest.pending_orders[order_index]
order.active = False
backtest.transactions.append(
Transaction(
tradeType=TradeType.Cancel,
ticker=order.ticker,
commission=0,
executedSuccessfully=True,
numShares=order.numShares,
pricePerShare=order.targetPrice,
totalCost=0,
date=backtest.date,
notes="Order canceled"
)
)
return True
return False

def submit_gtc_order(backtest, tradeType: TradeType, numShares: int, targetPrice: float) -> Order:
order = Order(
tradeType=tradeType,
ticker=backtest.ticker,
numShares=numShares,
targetPrice=targetPrice,
duration='GTC',
orderDate=backtest.date
)
backtest.pending_orders.append(order)
return order
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ authors = [
]
dependencies = [
"pandas",
"numpy"
"numpy",
"yfinance"
]
requires-python = ">=3.8"
license = { text = "LGPL-3.0-only" }
Expand All @@ -23,4 +24,4 @@ classifiers = [
readme = "readme.md"

[project.urls]
Home = "https://github.com/yourusername/pyBacktest"
Home = "https://github.com/slowpoke111/pyBacktest"
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
description="A backtesting framework for stock price prediction strategies",
long_description=open('readme.md').read(),
long_description_content_type='text/markdown',
url="https://github.com/slowpoke111/pyBacktest",
url="https://github.com/slowpoke111/pyBacktest",
packages=find_packages(include=['pyBacktest', 'pyBacktest.*']),
install_requires=[
"pandas",
"numpy",
"yfinance"
],
classifiers=[
"Programming Language :: Python :: 3",
Expand Down
4 changes: 2 additions & 2 deletions trades.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def execute_buy(backtest: Backtest, price: float, numShares: int, valid_date: pd
numShares,
numShares * price,
shortPosition=False,
entryPrice=price # Set entry price
entryPrice=price
)
backtest.holdings.append(holding)

Expand Down Expand Up @@ -199,7 +199,7 @@ def execute_short_sell(backtest: Backtest, price: float, numShares: int, valid_d
numShares=numShares,
totalCost=numShares * price,
shortPosition=True,
entryPrice=price # Set entry price
entryPrice=price
)
backtest.holdings.append(holding)

Expand Down
18 changes: 0 additions & 18 deletions utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,3 @@ def calculateReturnStats(returns: pd.Series) -> dict:
"sharpeRatio": calculateSharpeRatio(returns),
"maxDrawdown": calculateDrawdown(returns)[0]
}

def detectCrossover(series1: pd.Series, series2: pd.Series) -> pd.Series:
previousDiff = (series1 - series2).shift(1)
currentDiff = series1 - series2
return (previousDiff < 0) & (currentDiff > 0)

def detectCrossunder(series1: pd.Series, series2: pd.Series) -> pd.Series:
previousDiff = (series1 - series2).shift(1)
currentDiff = series1 - series2
return (previousDiff > 0) & (currentDiff < 0)

def getCrossSignals(fastMA: pd.Series, slowMA: pd.Series) -> pd.Series:
crossovers = detectCrossover(fastMA, slowMA)
crossunders = detectCrossunder(fastMA, slowMA)
signals = pd.Series(0, index=fastMA.index)
signals[crossovers] = 1
signals[crossunders] = -1
return signals

0 comments on commit 3cfb1ee

Please sign in to comment.