tradingsystemsdata 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ __version__ = "1.0.0"
2
+
3
+
4
+
5
+
@@ -0,0 +1,348 @@
1
+ """
2
+ Exit signals based on a dollar value.
3
+
4
+ """
5
+ import pandas as pd
6
+ import numpy as np
7
+
8
+ class DollarExit():
9
+ """
10
+ Calculate dollar value based exit signals.
11
+
12
+ """
13
+
14
+ @classmethod
15
+ def exit_dollar(
16
+ cls,
17
+ prices: pd.DataFrame,
18
+ trigger_value: pd.Series,
19
+ exit_level: str) -> tuple[pd.DataFrame, np.ndarray]:
20
+ """
21
+ Calculate exit based on a dollar amount.
22
+
23
+ Parameters
24
+ ----------
25
+ prices : DataFrame
26
+ The OHLC data.
27
+ trigger_value : Series
28
+ The series to trigger exit.
29
+ trade_number : Series
30
+ Array of trade numbers.
31
+ end_of_day_position : Series
32
+ The close of day, long/short/flat position.
33
+ trade_high_price : Series
34
+ The high price of the trade.
35
+ trade_low_price : Series
36
+ The low price of the trade.
37
+ exit_level : Str
38
+ The type of exit strategy.
39
+
40
+ Returns
41
+ -------
42
+ prices : DataFrame
43
+ The OHLC data
44
+ exit : Series
45
+ Exit signals.
46
+
47
+ """
48
+
49
+ # Calculate exit signal based on a profit target
50
+ if exit_level == 'profit_target':
51
+ prices, exit_ = cls._exit_profit_target(
52
+ prices=prices,
53
+ trigger_value=trigger_value)
54
+
55
+ # Calculate exit signal based on a loss from entry price
56
+ elif exit_level == 'initial':
57
+ prices, exit_ = cls._exit_initial_dollar_loss(
58
+ prices=prices,
59
+ trigger_value=trigger_value)
60
+
61
+ # Calculate exit signal based on a breakeven level
62
+ elif exit_level == 'breakeven':
63
+ prices, exit_ = cls._exit_breakeven(
64
+ prices=prices,
65
+ trigger_value=trigger_value)
66
+
67
+ # Calculate exit signal based on a trailing stop referencing the close
68
+ elif exit_level == 'trail_close':
69
+ prices, exit_ = cls._exit_trailing(
70
+ prices=prices,
71
+ trigger_value=trigger_value)
72
+
73
+ # Calculate exit signal based on a trailing stop referencing the
74
+ # high/low
75
+ elif exit_level == 'trail_high_low':
76
+ prices, exit_ = cls._exit_trailing(
77
+ prices=prices,
78
+ trigger_value=trigger_value)
79
+
80
+ return prices, exit_
81
+
82
+
83
+ @staticmethod
84
+ def _exit_profit_target(
85
+ prices: pd.DataFrame,
86
+ trigger_value: pd.Series) -> tuple[pd.DataFrame, np.ndarray]:
87
+ """
88
+ Calculate exit based on a profit target.
89
+
90
+ Parameters
91
+ ----------
92
+ prices : DataFrame
93
+ The OHLC data.
94
+ trigger_value : Series
95
+ The series to trigger exit.
96
+ trade_number : Series
97
+ Array of trade numbers.
98
+ end_of_day_position : Series
99
+ The close of day, long/short/flat position.
100
+
101
+ Returns
102
+ -------
103
+ prices : DataFrame
104
+ The OHLC data
105
+ profit_target_exit : Series
106
+ The exit signals.
107
+
108
+ """
109
+ trade_number = prices['raw_trade_number']
110
+ end_of_day_position = prices['raw_end_of_day_position']
111
+
112
+ # Create an empty array to store the signals
113
+ profit_target_exit = np.array([0]*len(prices))
114
+
115
+ # For each row in the data
116
+ for row in range(1, len(prices)):
117
+
118
+ # If there is a trade on
119
+ if trade_number.iloc[row] != 0:
120
+
121
+ # If there is a long position
122
+ if end_of_day_position.iloc[row] > 0:
123
+
124
+ # If the close is greater than the trigger value
125
+ if prices['Close'].iloc[row] > trigger_value.iloc[row]:
126
+
127
+ # Set the exit signal to -1
128
+ profit_target_exit[row] = -1
129
+
130
+ # If there is a short position
131
+ elif end_of_day_position.iloc[row] < 0:
132
+
133
+ # If the close is less than the trigger value
134
+ if prices['Close'].iloc[row] < trigger_value.iloc[row]:
135
+
136
+ # Set the exit signal to 1
137
+ profit_target_exit[row] = 1
138
+
139
+ else:
140
+ # Set the exit signal to 0
141
+ profit_target_exit[row] = 0
142
+
143
+ return prices, profit_target_exit
144
+
145
+
146
+ @staticmethod
147
+ def _exit_initial_dollar_loss(
148
+ prices: pd.DataFrame,
149
+ trigger_value: pd.Series) -> tuple[pd.DataFrame, np.ndarray]:
150
+ """
151
+ Calculate exit based on a given loss from the entry point.
152
+
153
+ Parameters
154
+ ----------
155
+ prices : DataFrame
156
+ The OHLC data.
157
+ trigger_value : Series
158
+ The series to trigger exit.
159
+ trade_number : Series
160
+ Array of trade numbers.
161
+ end_of_day_position : Series
162
+ The close of day, long/short/flat position.
163
+
164
+ Returns
165
+ -------
166
+ prices : DataFrame
167
+ The OHLC data.
168
+ initial_dollar_loss_exit : Series
169
+ The exit signals.
170
+
171
+ """
172
+
173
+ trade_number = prices['raw_trade_number']
174
+ end_of_day_position = prices['raw_end_of_day_position']
175
+
176
+ # Create an empty array to store the signals
177
+ initial_dollar_loss_exit = np.array([0]*len(prices))
178
+
179
+ # For each row in the data
180
+ for row in range(1, len(prices)):
181
+
182
+ # If there is a trade on
183
+ if trade_number.iloc[row] != 0:
184
+
185
+ # If there is a long position
186
+ if end_of_day_position.iloc[row] > 0:
187
+
188
+ # If the close is less than the trigger value
189
+ if prices['Close'].iloc[row] < trigger_value.iloc[row]:
190
+
191
+ # Set the exit signal to -1
192
+ initial_dollar_loss_exit[row] = -1
193
+
194
+ # If there is a short position
195
+ elif end_of_day_position.iloc[row] < 0:
196
+
197
+ # If the close is greater than the trigger value
198
+ if prices['Close'].iloc[row] > trigger_value.iloc[row]:
199
+
200
+ # Set the exit signal to 1
201
+ initial_dollar_loss_exit[row] = 1
202
+
203
+ else:
204
+ # Set the exit signal to 0
205
+ initial_dollar_loss_exit[row] = 0
206
+
207
+ return prices, initial_dollar_loss_exit
208
+
209
+
210
+ @staticmethod
211
+ def _exit_breakeven(
212
+ prices: pd.DataFrame,
213
+ trigger_value: pd.Series) -> tuple[pd.DataFrame, np.ndarray]:
214
+ """
215
+ Calculate exit based on passing a breakeven threshold.
216
+
217
+ Parameters
218
+ ----------
219
+ prices : DataFrame
220
+ The OHLC data.
221
+ trigger_value : Series
222
+ The series to trigger exit.
223
+ trade_number : Series
224
+ Array of trade numbers.
225
+ end_of_day_position : Series
226
+ The close of day, long/short/flat position.
227
+ trade_high_price : Series
228
+ The high price of the trade.
229
+ trade_low_price : Series
230
+ The low price of the trade.
231
+
232
+ Returns
233
+ -------
234
+ prices : DataFrame
235
+ The OHLC data.
236
+ breakeven_exit : Series
237
+ The exit signals.
238
+
239
+ """
240
+
241
+ trade_number = prices['raw_trade_number']
242
+ end_of_day_position = prices['raw_end_of_day_position']
243
+ trade_high_price = prices['raw_trade_high_price']
244
+ trade_low_price = prices['raw_trade_low_price']
245
+
246
+ # Create an empty array to store the signals
247
+ breakeven_exit = np.array([0.0]*len(prices))
248
+
249
+ # For each row in the data
250
+ for row in range(1, len(prices)):
251
+
252
+ # If there is a trade on
253
+ if trade_number.iloc[row] != 0:
254
+
255
+ # If there is a long position
256
+ if end_of_day_position.iloc[row] > 0:
257
+
258
+ # If the high price of the trade is greater than the
259
+ # trigger value
260
+ if trade_high_price.iloc[row] > trigger_value.iloc[row]:
261
+
262
+ # If the close is less than the trigger value
263
+ if prices['Close'].iloc[row] < trigger_value.iloc[row]:
264
+
265
+ # Set the exit signal to -1
266
+ breakeven_exit[row] = -1
267
+
268
+ # If there is a short position
269
+ elif end_of_day_position.iloc[row] < 0:
270
+
271
+ # If the low price of the trade is less than the
272
+ # trigger value
273
+ if trade_low_price.iloc[row] < trigger_value.iloc[row]:
274
+
275
+ # If the close is greater than the trigger value
276
+ if prices['Close'].iloc[row] > trigger_value.iloc[row]:
277
+
278
+ # Set the exit signal to 1
279
+ breakeven_exit[row] = 1
280
+ else:
281
+ # Set the exit signal to 0
282
+ breakeven_exit[row] = 0
283
+
284
+ return prices, breakeven_exit
285
+
286
+
287
+ @staticmethod
288
+ def _exit_trailing(
289
+ prices: pd.DataFrame,
290
+ trigger_value: pd.Series) -> tuple[pd.DataFrame, np.ndarray]:
291
+ """
292
+ Calculate exit based on a trailing stop.
293
+
294
+ Parameters
295
+ ----------
296
+ prices : DataFrame
297
+ The OHLC data.
298
+ trigger_value : Series
299
+ The series to trigger exit.
300
+ trade_number : Series
301
+ Array of trade numbers.
302
+ end_of_day_position : Series
303
+ The close of day, long/short/flat position.
304
+
305
+ Returns
306
+ -------
307
+ prices : DataFrame
308
+ The OHLC data.
309
+ trailing_exit : Series
310
+ The exit signals.
311
+
312
+ """
313
+
314
+ trade_number = prices['raw_trade_number']
315
+ end_of_day_position = prices['raw_end_of_day_position']
316
+
317
+ # Create an empty array to store the signals
318
+ trailing_exit = np.array([0.0]*len(prices))
319
+
320
+ # For each row in the data
321
+ for row in range(1, len(prices)):
322
+
323
+ # If there is a trade on
324
+ if trade_number.iloc[row] != 0:
325
+
326
+ # If there is a long position
327
+ if end_of_day_position.iloc[row] > 0:
328
+
329
+ # If the close is less than the trigger value
330
+ if prices['Close'].iloc[row] < trigger_value.iloc[row]:
331
+
332
+ # Set the exit signal to -1
333
+ trailing_exit[row] = -1
334
+
335
+ # If there is a short position
336
+ elif end_of_day_position.iloc[row] < 0:
337
+
338
+ # If the close is greater than the trigger value
339
+ if prices['Close'].iloc[row] > trigger_value.iloc[row]:
340
+
341
+ # Set the exit signal to 1
342
+ trailing_exit[row] = 1
343
+
344
+ else:
345
+ # Set the exit signal to 0
346
+ trailing_exit[row] = 0
347
+
348
+ return prices, trailing_exit
@@ -0,0 +1,279 @@
1
+ """
2
+ Graph the performance of the trading strategy
3
+
4
+ """
5
+ import numpy as np
6
+ import pandas as pd
7
+ # pylint: disable=unbalanced-tuple-unpacking
8
+ # pylint: disable=no-else-return
9
+
10
+ class GraphData():
11
+ """
12
+ Class of functions used to return data to graph trading system performance
13
+
14
+ """
15
+
16
+ @staticmethod
17
+ def graph_variables(
18
+ prices: pd.DataFrame,
19
+ entry_type: str,
20
+ entry_signal_indicators: dict | None = None) -> dict:
21
+ """
22
+ Create graph initialization variables
23
+
24
+ Returns
25
+ -------
26
+ dates : Pandas Series
27
+ The dates to plot on the x-axis.
28
+ price : Pandas Series
29
+ Closing Prices.
30
+ equity : Pandas Series
31
+ Daily Mark to Market Equity level.
32
+ cumsig : Pandas Series
33
+ The cumulative buy / sell signal.
34
+ lower_bound : Pandas Series
35
+ Lower point to set where trading signals are plotted from.
36
+ upper_bound : Pandas Series
37
+ Upper point to set where trading signals are plotted from.
38
+
39
+ """
40
+
41
+ # Dictionary to store default params
42
+ graph_params = {}
43
+
44
+ # Set the dates to the index of the main DataFrame
45
+ graph_params['dates'] = prices.index
46
+
47
+ # Closing Prices
48
+ graph_params['price'] = prices['Close']
49
+
50
+ # MTM Equity
51
+ graph_params['equity'] = prices['mtm_equity']
52
+
53
+ # Cumulative sum of the combined entry, exit and stop signal
54
+ graph_params['cumsig'] = prices['combined_signal'].cumsum()
55
+
56
+ # The lower and upper bounds are used in setting where the trade
57
+ # signals are plotted on the price chart
58
+ # If the entry is a channel breakout
59
+ if entry_type == 'channel_breakout':
60
+
61
+ # Set the lower bound as rolling low close prices
62
+ graph_params['lower_bound'] = prices[
63
+ entry_signal_indicators[entry_type][0]]
64
+
65
+ # Set the upper bound as rolling high close prices
66
+ graph_params['upper_bound'] = prices[
67
+ entry_signal_indicators[entry_type][1]]
68
+
69
+ elif entry_type in ['2ma', '3ma', '4ma']:
70
+
71
+ # Set the lower bound as the lowest of the moving average values
72
+ # and the price
73
+ graph_params['lower_bound'] = prices['min_ma']
74
+
75
+ # Set the upper bound as the highest of the moving average values
76
+ # and the price
77
+ graph_params['upper_bound'] = prices['max_ma']
78
+
79
+ # Otherwise
80
+ else:
81
+ # Set the upper and lower bounds to the closing price
82
+ graph_params['lower_bound'] = graph_params['price']
83
+ graph_params['upper_bound'] = graph_params['price']
84
+
85
+ return graph_params
86
+
87
+
88
+ @staticmethod
89
+ def _bar_color(
90
+ price_data: pd.Series,
91
+ color1: str,
92
+ color2: str) -> np.ndarray:
93
+ """
94
+ Set barchart color to green if positive and red if negative.
95
+
96
+ Parameters
97
+ ----------
98
+ price_data : Series
99
+ Price data.
100
+ color1 : Str
101
+ Color for positive data.
102
+ color2 : Str
103
+ Color for negative data.
104
+
105
+ Returns
106
+ -------
107
+ Series
108
+ Series of colors for each data point.
109
+
110
+ """
111
+ return np.where(price_data.values > 0, color1, color2).T
112
+
113
+
114
+ @classmethod
115
+ def create_signals(
116
+ cls,
117
+ prices: pd.DataFrame,
118
+ graph_params: dict) -> dict:
119
+ """
120
+ Create trade signals to be plotted on main price chart
121
+
122
+ Parameters
123
+ ----------
124
+ cumsig : Pandas Series
125
+ The cumulative buy / sell signal.
126
+ lower_bound : Pandas Series
127
+ Lower point to set where trading signals are plotted from.
128
+ upper_bound : Pandas Series
129
+ Upper point to set where trading signals are plotted from.
130
+
131
+ Returns
132
+ -------
133
+ signal_dict : Dict
134
+ Dictionary containing the trade signal details.
135
+
136
+ """
137
+ # Create empty dictionary
138
+ signal_dict = {}
139
+ signal_dict['data_markers'] = {}
140
+
141
+ upper, lower = cls._set_upper_lower(graph_params=graph_params)
142
+
143
+ buy_sell_distance = 0.10 * (upper - lower) # 0.07
144
+ flat_distance = 0.15 * (upper - lower) # 0.1
145
+
146
+ # Buy signal to go long is where the current cumulative signal is to be
147
+ # long when yesterday it was flat
148
+ signal_dict['buy_long_signals'] = (
149
+ (graph_params['cumsig'] == 1)
150
+ & (graph_params['cumsig'].shift() != 1))
151
+
152
+ # Place the marker at the specified distance below the lower bound
153
+ signal_dict['buy_long_marker'] = (
154
+ graph_params['lower_bound']
155
+ * signal_dict['buy_long_signals']
156
+ - buy_sell_distance)
157
+ #- graph_params['lower_bound'].max()*buy_sell_distance)
158
+
159
+ signal_dict['buy_long_marker'] = signal_dict[
160
+ 'buy_long_marker'][signal_dict['buy_long_signals']]
161
+
162
+ # Add raw signal position for use in api
163
+ signal_dict['data_markers']['openLong'] = (
164
+ graph_params['lower_bound']
165
+ * signal_dict['buy_long_signals']
166
+ )
167
+
168
+ signal_dict['data_markers']['openLong'] = signal_dict['data_markers'][
169
+ 'openLong'][signal_dict['buy_long_signals']]
170
+
171
+ # Set the dates for the buy long signals
172
+ signal_dict['buy_long_dates'] = prices.index[
173
+ signal_dict['buy_long_signals']]
174
+
175
+ signal_dict['data_markers']['openLongDates'] = signal_dict['buy_long_dates']
176
+
177
+ # Buy signal to go flat is where the current cumulative signal is to be
178
+ # flat when yesterday it was short
179
+ signal_dict['buy_flat_signals'] = (
180
+ (graph_params['cumsig'] == 0)
181
+ & (graph_params['cumsig'].shift() == -1))
182
+
183
+ # Place the marker at the specified distance below the lower bound
184
+ signal_dict['buy_flat_marker'] = (
185
+ graph_params['lower_bound']
186
+ * signal_dict['buy_flat_signals']
187
+ - flat_distance)
188
+ #- graph_params['lower_bound'].max()*flat_distance)
189
+
190
+ signal_dict['buy_flat_marker'] = signal_dict[
191
+ 'buy_flat_marker'][signal_dict['buy_flat_signals']]
192
+
193
+ # Add raw signal position for use in api
194
+ signal_dict['data_markers']['closeShort'] = (
195
+ graph_params['lower_bound']
196
+ * signal_dict['buy_flat_signals']
197
+ )
198
+ signal_dict['data_markers']['closeShort'] = signal_dict['data_markers']['closeShort'][signal_dict['buy_flat_signals']]
199
+
200
+ # Set the dates for the buy flat signals
201
+ signal_dict['buy_flat_dates'] = prices.index[
202
+ signal_dict['buy_flat_signals']]
203
+
204
+ signal_dict['data_markers']['closeShortDates'] = signal_dict['buy_flat_dates']
205
+
206
+ # Sell signal to go flat is where the current cumulative signal is to
207
+ # be flat when yesterday it was long
208
+ signal_dict['sell_flat_signals'] = (
209
+ (graph_params['cumsig'] == 0)
210
+ & (graph_params['cumsig'].shift() == 1))
211
+
212
+ # Place the marker at the specified distance above the upper bound
213
+ signal_dict['sell_flat_marker'] = (
214
+ graph_params['upper_bound']
215
+ * signal_dict['sell_flat_signals']
216
+ + flat_distance)
217
+ #+ graph_params['upper_bound'].max()*flat_distance)
218
+
219
+ signal_dict['sell_flat_marker'] = signal_dict[
220
+ 'sell_flat_marker'][signal_dict['sell_flat_signals']]
221
+
222
+ # Add raw signal position for use in api
223
+ signal_dict['data_markers']['closeLong'] = (
224
+ graph_params['upper_bound']
225
+ * signal_dict['sell_flat_signals']
226
+ )
227
+
228
+ signal_dict['data_markers']['closeLong'] = signal_dict['data_markers']['closeLong'][signal_dict['sell_flat_signals']]
229
+
230
+ # Set the dates for the sell flat signals
231
+ signal_dict['sell_flat_dates'] = prices.index[
232
+ signal_dict['sell_flat_signals']]
233
+
234
+ signal_dict['data_markers']['closeLongDates'] = signal_dict['sell_flat_dates']
235
+
236
+ # Set the dates for the sell short signals
237
+ signal_dict['sell_short_signals'] = (
238
+ (graph_params['cumsig'] == -1)
239
+ & (graph_params['cumsig'].shift() != -1))
240
+
241
+ # Place the marker at the specified distance above the upper bound
242
+ signal_dict['sell_short_marker'] = (
243
+ graph_params['upper_bound']
244
+ * signal_dict['sell_short_signals']
245
+ + buy_sell_distance)
246
+ #+ graph_params['upper_bound'].max()*buy_sell_distance)
247
+
248
+ signal_dict['sell_short_marker'] = signal_dict[
249
+ 'sell_short_marker'][signal_dict['sell_short_signals']]
250
+
251
+ # Add raw signal position for use in api
252
+ signal_dict['data_markers']['openShort'] = (
253
+ graph_params['upper_bound']
254
+ * signal_dict['sell_short_signals']
255
+ )
256
+
257
+ signal_dict['data_markers']['openShort'] = signal_dict['data_markers']['openShort'][signal_dict['sell_short_signals']]
258
+
259
+ # Set the dates for the sell short signals
260
+ signal_dict['sell_short_dates'] = prices.index[
261
+ signal_dict['sell_short_signals']]
262
+
263
+ signal_dict['data_markers']['openShortDates'] = signal_dict['sell_short_dates']
264
+
265
+ return signal_dict
266
+
267
+
268
+ @staticmethod
269
+ def _set_upper_lower(
270
+ graph_params: dict) -> tuple[float, float]:
271
+ # Set upper to the max of the upper bound and lower to the lowest
272
+ # non-zero value of the lower bound, stripping zeros and nan values
273
+
274
+ upper = graph_params['upper_bound'][
275
+ graph_params['upper_bound'] != 0].dropna().max()
276
+ lower = graph_params['lower_bound'][
277
+ graph_params['lower_bound'] != 0].dropna().min()
278
+
279
+ return upper, lower