Skip to content

Commit

Permalink
refactoring of trade class
Browse files Browse the repository at this point in the history
  • Loading branch information
97gamjak committed Apr 2, 2024
1 parent fac6c1c commit fa0ea58
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 66 deletions.
56 changes: 56 additions & 0 deletions notebooks/profile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
100360685 function calls (98707238 primitive calls) in 114.329 seconds

Ordered by: cumulative time
List reduced from 4390 to 50 due to restriction <50>

ncalls tottime percall cumtime percall filename:lineno(function)
222/1 0.001 0.000 114.335 114.335 {built-in method builtins.exec}
1072/1 0.002 0.000 114.108 114.108 decorator.py:229(fun)
1 0.000 0.000 114.108 114.108 _common.py:7(finalize)
1 0.025 0.025 109.966 109.966 strategy.py:325(screen)
504 0.032 0.000 92.678 0.184 strategy.py:344(populate_pricing_data)
504 0.006 0.000 70.988 0.141 _common.py:55(get_daily_candle_range)
2020/504 0.039 0.000 70.763 0.140 utils.py:98(wrapper)
504 0.241 0.000 70.717 0.140 base.py:84(history)
504 0.009 0.000 61.634 0.122 data.py:332(get)
508 0.003 0.000 61.501 0.121 sessions.py:593(get)
508 0.006 0.000 61.498 0.121 sessions.py:502(request)
509/508 0.011 0.000 60.400 0.119 sessions.py:673(send)
4821 0.011 0.000 58.939 0.012 socket.py:692(readinto)
4821 0.015 0.000 58.922 0.012 ssl.py:1299(recv_into)
4821 0.007 0.000 58.906 0.012 ssl.py:1157(read)
4821 58.899 0.012 58.899 0.012 {method 'read' of '_ssl._SSLSocket' objects}
11180 0.009 0.000 53.715 0.005 {method 'readline' of '_io.BufferedReader' objects}
509 0.008 0.000 52.808 0.104 adapters.py:434(send)
509 0.013 0.000 52.604 0.103 connectionpool.py:534(urlopen)
509 0.012 0.000 52.420 0.103 connectionpool.py:379(_make_request)
509 0.003 0.000 51.809 0.102 client.py:1346(getresponse)
509 0.009 0.000 51.795 0.102 client.py:318(begin)
509 0.009 0.000 51.629 0.101 client.py:285(_read_status)
504 0.013 0.000 18.724 0.037 fibStrategy.py:22(calculate_indicators)
504 0.003 0.000 18.664 0.037 frame.py:9411(apply)
504 0.059 0.000 18.656 0.037 apply.py:731(apply)
502 0.002 0.000 18.594 0.037 apply.py:890(apply_standard)
504 0.279 0.001 15.120 0.030 strategy.py:385(_screen_single_ticker)
502 0.156 0.000 13.537 0.027 apply.py:896(apply_series_generator)
297645 0.331 0.000 11.825 0.000 indexing.py:1059(__getitem__)
294779 0.658 0.000 10.726 0.000 indexing.py:1592(_getitem_axis)
78294 0.114 0.000 10.311 0.000 fibStrategy.py:23(apply_candle_body_outside_range)
26516 0.027 0.000 10.178 0.000 strategy.py:409(<listcomp>)
53032 0.089 0.000 10.151 0.000 _base.py:36(evaluate_rules)
354465/351502 0.888 0.000 9.489 0.000 series.py:342(__init__)
92751 0.324 0.000 7.958 0.000 frame.py:3703(_ixs)
2529 0.003 0.000 7.558 0.003 models.py:887(content)
5602 0.007 0.000 7.556 0.001 {method 'join' of 'bytes' objects}
1926 0.003 0.000 7.549 0.004 models.py:812(generate)
1926 0.002 0.000 7.546 0.004 response.py:607(stream)
1913 0.012 0.000 7.543 0.004 response.py:789(read_chunked)
14825 0.044 0.000 7.155 0.000 frame.py:609(__init__)
3075 0.034 0.000 6.816 0.002 construction.py:423(dict_to_mgr)
3520 0.011 0.000 5.613 0.002 construction.py:100(arrays_to_mgr)
1914 0.004 0.000 5.247 0.003 response.py:767(_handle_chunk)
2937 0.004 0.000 5.243 0.002 client.py:631(_safe_read)
3630 0.008 0.000 5.241 0.001 {method 'read' of '_io.BufferedReader' objects}
53032 0.112 0.000 5.142 0.000 _base.py:51(_evaluate_bullish_rules)
502 0.003 0.000 5.056 0.010 apply.py:915(wrap_results)
502 0.001 0.000 5.046 0.010 apply.py:1050(wrap_results_for_axis)
105 changes: 104 additions & 1 deletion stockMarket/technicalAnalysis/strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,44 @@ def __init__(self,
init_from_json: bool = False,
**kwargs
) -> None:
"""
The strategy class is used to screen a list of tickers for trades based on a list of strategy objects. The strategy objects are used to evaluate the rules for each ticker and the trades are executed based on the outcome of the rules. The trades are stored in a dictionary with the ticker as the key and a list of trades as the value. The trades are then written to an xlsx file.
In general, the strategy class can be initialized in two ways:
1. By providing all the necessary parameters to the __init__ method.
2. By providing the dir_path to the directory where the json files are stored. The json files are used to initialize the strategy class. (This setting is useful when the screening was already performed and the trades are to be analyzed or the screening is to be continued.)
Parameters
----------
strategy_objects : List[StrategyObject]
A list of strategy objects that are used to evaluate the rules for each ticker.
start_date : str
The start date for the screening in the format "dd.mm.yyyy".
end_date : str
The end date for the screening in the format "dd.mm.yyyy".
rule_enums : List[RuleEnum], optional
A list of rule enums that are used to evaluate the rules for each ticker, by default [RuleEnum.BULLISH]
num_batches : int, optional
The number of batches to split the screening into, by default 1
batch_size : Optional[pd.Timedelta], optional
The size of each batch, by default None
If None, the batch size is calculated based on the number of batches and the start and end date. If both num_batches and batch_size are provided, num_batches will be ignored.
trade_settings : Optional[TradeSettings], optional
The trade settings that are used to execute the trades, by default None
candle_period : str | Period | None, optional
The candle period for the pricing data, by default None (daily candle period is used)
use_earnings_dates : bool, optional
A boolean indicating whether the earnings dates should be used to filter the trades, by default False
finalize_commands : Optional[List[str]], optional
A list of shell commands that are executed after the screening is finished, by default None
init_from_json : bool, optional
A boolean indicating whether the strategy class should be initialized from a json file, by default False
Raises
------
ValueError
init_from_json is True and neither dir_path nor the combination of dir_name and base_path is provided in kwargs.
"""

if not init_from_json:
self.__clean_init__(
Expand Down Expand Up @@ -79,6 +117,33 @@ def __clean_init__(self,
finalize_commands: Optional[List[str]] = None,
**kwargs
) -> None:
"""
Initialize the strategy class by providing all the necessary parameters.
Parameters
----------
strategy_objects : List[StrategyObject]
A list of strategy objects that are used to evaluate the rules for each ticker.
start_date : str
The start date for the screening in the format "dd.mm.yyyy".
end_date : str
The end date for the screening in the format "dd.mm.yyyy".
rule_enums : List[RuleEnum], optional
A list of rule enums that are used to evaluate the rules for each ticker, by default [RuleEnum.BULLISH]
num_batches : int, optional
The number of batches to split the screening into, by default 1
batch_size : Optional[pd.Timedelta], optional
The size of each batch, by default None
If None, the batch size is calculated based on the number of batches and the start and end date. If both num_batches and batch_size are provided, num_batches will be ignored.
trade_settings : Optional[TradeSettings], optional
The trade settings that are used to execute the trades, by default None
candle_period : str | Period | None, optional
The candle period for the pricing data, by default None (daily candle period is used)
use_earnings_dates : bool, optional
A boolean indicating whether the earnings dates should be used to filter the trades, by default False
finalize_commands : Optional[List[str]], optional
A list of shell commands that are executed after the screening is finished, by default None
"""

self.strategy_objects = strategy_objects
self.rule_enums = rule_enums
Expand All @@ -89,6 +154,8 @@ def __clean_init__(self,
self.earnings_calendar: Dict[str, List[dt.date]] = {}
self.file_settings = None

# setup files including creating the directory
# all input parameters used for the setup have to be given as kwargs
self.setup_files(
self.strategy_objects,
self.rule_enums,
Expand Down Expand Up @@ -128,7 +195,15 @@ def __clean_init__(self,
dir_path=self.dir_path,
)

def init_from_json(self, dir_path: Path):
def __init_from_json__(self, dir_path: Path):
"""
Initialize the strategy class from a json file.
Parameters
----------
dir_path : Path
The directory path where the json file(s) is/are stored.
"""
StrategyJSON.read(dir_path)

self.file_settings = StrategyJSON.file_settings
Expand Down Expand Up @@ -180,6 +255,26 @@ def setup_dates(self,
num_batches: int,
batch_size: Optional[pd.Timedelta] = None,
):
"""
Setup the dates for the screening.
Parameters
----------
start_date : str
start date in the format "dd.mm.yyyy"
end_date : str
end date in the format "dd.mm.yyyy"
candle_period : str | Period | None
candle period for the pricing data, by default None (daily candle period is used)
num_batches : int
number of batches to split the screening into
batch_size : Optional[pd.Timedelta], optional
size of each batch, by default None (calculated based on the number of batches and the start and end date). If both num_batches and batch_size are provided, num_batches will be ignored.
Warnings
--------
If both num_batches and batch_size are set, num_batches will be ignored.
"""
if candle_period is None:
self.candle_period = Period('daily')
else:
Expand All @@ -198,6 +293,14 @@ def setup_dates(self,
"Both num_batches and batch_size are set. num_batches will be ignored")

def setup_trade_settings(self, trade_settings: Optional[TradeSettings] = None):
"""
Setup the trade settings for the trades.
Parameters
----------
trade_settings : Optional[TradeSettings], optional
trade settings for the trades, by default None
"""
self.trade_settings = trade_settings if trade_settings is not None else TradeSettings()

def get_earnings_dates(self):
Expand Down
47 changes: 1 addition & 46 deletions stockMarket/technicalAnalysis/trade/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import numpy as np

from stockMarket.yfinance._common import get_daily_candle_range
from .enums import TradeStatus, TradeOutcome


def calc_highest_body_price(candle):
Expand All @@ -12,52 +13,6 @@ def calc_lowest_body_price(candle):
return min(candle.open, candle.close)


def find_daily_candle(ticker,
pricing: pd.DataFrame,
candle_index: int,
target_price: float,
mode=np.greater_equal,
min_date=None,
):

start_date = pricing.index[candle_index].date()
if candle_index == len(pricing)-1:
end_date = None
else:
# end date can be set to the next candle date for all intervals
# e.g if interval is weekly than end date is the next week but
# yf will not include the first day of the next week
end_date = pricing.index[candle_index + 1].date()
end_date = str(end_date)

min_date = min_date if min_date is not None and min_date > start_date else start_date

if end_date is not None and min_date == end_date:
return None, None

pricing_daily = get_daily_candle_range(ticker, min_date, end_date)

candle = None

for i in range(len(pricing_daily)):
candle = pricing_daily.iloc[i]

check_price = candle.high if mode == np.greater_equal else candle.low

if mode(candle.open, target_price):
target_price = candle.open
break
elif mode(check_price, target_price):
break

candle = None

if candle is None:
return None, None
else:
return candle, target_price


def find_last_high(
pricing: pd.DataFrame,
ref_candle_index: int,
Expand Down
32 changes: 32 additions & 0 deletions stockMarket/technicalAnalysis/trade/enums_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from beartype.typing import Optional

from .enums import TradeOutcome, TradeStatus


def check_PL_ratio(
PL: float,
min_PL: Optional[float] = None,
max_PL: Optional[float] = None,
):
if min_PL is not None and PL < min_PL:
return TradeStatus.PL_TOO_SMALL

if max_PL is not None and PL > max_PL:
return TradeStatus.PL_TOO_LARGE

return TradeStatus.UNKNOWN


def determine_outcome_status(
trade_status: TradeStatus,
ENTRY: float,
EXIT: float,
) -> TradeOutcome:

if trade_status == TradeStatus.CLOSED:
if EXIT > ENTRY:
return TradeOutcome.WIN

return TradeOutcome.LOSS

return TradeOutcome.NONE
32 changes: 13 additions & 19 deletions stockMarket/technicalAnalysis/trade/trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from beartype.typing import Optional

from .enums import ChartEnum, TradeStatus, TradeOutcome, AttachedOrderType
from .enums_api import determine_outcome_status, check_PL_ratio

from .tradeSettings import TradeSettings
from .decorators import ignore_trade_exceptions, check_trade_status
from .common import (
Expand All @@ -38,7 +40,7 @@ class Trade:
def __init__(self,
ticker: str,
trigger_candle: pd.DataFrame,
settings=None,
settings: Optional[TradeSettings] = None,
) -> None:

self.ticker = ticker
Expand All @@ -52,8 +54,6 @@ def __init__(self,
self.EXIT: Optional[float] = None
self.TP: Optional[float] = None

self.condition = None

self.trade_status = TradeStatus.UNKNOWN
self.settings = settings if settings is not None else TradeSettings()

Expand Down Expand Up @@ -89,25 +89,19 @@ def execute_trade(self,

self.calc_EXIT(pricing_daily=pricing_daily)

self.determine_trade_outcome()

def determine_trade_outcome(self):
if self.trade_status == TradeStatus.CLOSED:
if self.EXIT > self.R_ENTRY:
self.outcome_status = TradeOutcome.WIN
elif self.EXIT < self.R_ENTRY:
self.outcome_status = TradeOutcome.LOSS
else:
self.outcome_status = TradeOutcome.NONE
self.outcome_status = determine_outcome_status(
self.trade_status,
self.R_ENTRY,
self.EXIT
)

@check_trade_status
def check_PL_RATIOS(self):

if self.settings.min_PL is not None and self.PL < self.settings.min_PL:
self.trade_status = TradeStatus.PL_TOO_SMALL

if self.settings.max_PL is not None and self.PL > self.settings.max_PL:
self.trade_status = TradeStatus.PL_TOO_LARGE
self.trade_status = check_PL_ratio(
PL=self.PL,
min_PL=self.settings.min_PL,
max_PL=self.settings.max_PL
)

@check_trade_status
def setup_TP(self,
Expand Down

0 comments on commit fa0ea58

Please sign in to comment.