1
+ from strategy .dependecies import *
2
+
3
+ # class MarketOnClosePortfolio(Portfolio):
4
+ class MarketOnClosePortfolio ():
5
+ """
6
+ Here we define our portfolio to keep track of our indicators & strategies
7
+
8
+ Return:
9
+ Pandas DataFrame for a set strategy analysis.
10
+ """
11
+
12
+ def __init__ (self , initial_capital , cap_on_order , inital_num_shares , comission_per_trade , symbol , data , signal_hist ):
13
+ # Class Constructor
14
+ # super().__init__()
15
+ self .initial_capital = initial_capital # Intial Amount of Cash that can be lost (Euros)
16
+ self .inital_num_shares = inital_num_shares # Initial Amount of Shares (before the start of trading)
17
+ self .cap_on_order = cap_on_order # Fixed Amount of Cash set to be placed on each trade order
18
+ self .num_share_to_buy = 0.068 # Fixed Amount of Shares set to be placed on each trade order
19
+ self .comission_per_trade = comission_per_trade # Comission (%) per trade
20
+ self .symbol = symbol # Market Ticker Symbol
21
+ self .signal_hist = signal_hist # Signal History DataFrame
22
+ self .df = data # Target Timeframe based DataFrame for a Trading Period of `X`
23
+ self .positions = self .generate_positions () # Postiton Signal History DataFrame
24
+
25
+ def generate_positions (self ):
26
+ # Generate a pandas DataFrame to store quantity held at any “bar” timeframe
27
+ positions = pd .DataFrame (index = self .signal_hist .index ).fillna (0.0 )
28
+ positions [self .symbol ] = 0.068 * self .signal_hist ['trade_signal' ] # Transact 100 shares on a signal
29
+
30
+ # DEV
31
+ # print (positions)
32
+
33
+ return positions
34
+
35
+ def backtest_portfolio (self ):
36
+ # Create a new DataFrame ‘portfolio’ to store the market value of an open position
37
+ portfolio = self .positions .multiply (self .df ['p_open' ], axis = 0 )
38
+ pos_diff = self .positions .diff ()
39
+
40
+ portfolio .dropna (inplace = True )
41
+
42
+ print (portfolio )
43
+ print (pos_diff )
44
+
45
+ portfolio ['comission ($)' ] = (self .df ['p_open' ] * (self .comission_per_trade / 100 )) * self .num_share_to_buy
46
+
47
+ # Create a ‘holdings’ Series that totals all open position market values
48
+ # and a ‘cash’ column that stores remaining cash in account
49
+ portfolio ['holdings' ] = self .positions .multiply (self .df ['p_open' ], axis = 0 ).sum (axis = 1 )
50
+ portfolio ['cash' ] = self .initial_capital - (pos_diff .multiply (self .df ['p_open' ], axis = 0 )).sum (axis = 1 ).cumsum ()
51
+
52
+ # Sum up the cash and holdings to create full account ‘equity’, then create the percentage returns
53
+ portfolio ['total' ] = portfolio ['cash' ] + portfolio ['holdings' ] - portfolio ['comission ($)' ]
54
+ portfolio ['returns' ] = portfolio ['total' ].pct_change ()
55
+ portfolio [portfolio .eq (0.00000 )] = np .nan
56
+
57
+ col_round = ['holdings' , 'cash' , 'total' , 'comission ($)' ]
58
+ portfolio [col_round ] = portfolio [col_round ].round (2 )
59
+ portfolio = portfolio .round ({'returns' : 5 })
60
+ print (portfolio .tail (50 ))
61
+
62
+ # DEV
63
+ print (portfolio )
64
+
65
+ return portfolio
66
+
67
+ def backtest_portfolio_v2 (self ):
68
+ # (Filter) Remove 0.0 'position' values, as they are unecessary for the end analysis result
69
+ self .signal_hist = self .signal_hist [self .signal_hist ['position' ] != 0.0 ]
70
+ # Create a new DataFrame ‘portfolio’ to store the market value of an open position
71
+ portfolio = pd .DataFrame (index = self .signal_hist .index ).fillna (0.0 )
72
+
73
+ # Buy Order DataFrame
74
+ buy = self .signal_hist [self .signal_hist .position == 1.0 ]
75
+ buy ['Buy Order Time' ] = self .df .open_time
76
+ buy ['Buy Price' ] = buy ['SMA20' ]
77
+ buy .reset_index (inplace = True )
78
+
79
+ # Sell Order DataFrame
80
+ sell = self .signal_hist [self .signal_hist .position == - 1.0 ]
81
+ sell ['Sell Order Time' ] = self .df .open_time
82
+ sell ['Sell Price' ] = sell ['SMA20' ]
83
+ sell .reset_index (inplace = True )
84
+
85
+ # Using 'concat' because it works with NaN Column Values
86
+ portfolio = pd .concat ([buy ['Buy Order Time' ],
87
+ buy ['Buy Price' ],
88
+ sell ['Sell Order Time' ],
89
+ sell ['Sell Price' ]], axis = 1 )
90
+
91
+ # Place Buy Order at SMA20 Price (Leading Indicator)
92
+ # portfolio['BTC'] = cap_on_order / self.signal_hist.loc[(self.signal_hist['position'] == 1.0)]['SMA20']
93
+
94
+ # (Optional) Add `comission` to the portfolio ie: comission per trade in ($) dollars
95
+ portfolio ['Comission ($)' ] = self .cap_on_order * (2 * self .comission_per_trade / 100 )
96
+ # Add `Share Quantity` to Portfolio to View how many Shares I own at the time of the trade
97
+ portfolio ['Share Quantity' ] = self .cap_on_order / buy ['Buy Price' ]
98
+ # Add `Net Trade` to Portfolio to view the total Net profit/loss
99
+ portfolio ['total_net' ] = self .cap_on_order / (sell ['Sell Price' ] - buy ['Buy Price' ] - portfolio ['Comission ($)' ])
100
+ # Add `Percentage Change`
101
+ portfolio ['(%) Change' ] = portfolio ['total_net' ] / buy ['Buy Price' ] * 100
102
+ # Add `Portfolio Total` to Portfolio
103
+ portfolio ['Portfolio Total' ] = (self .initial_capital - portfolio ['Comission ($)' ])
104
+ # Add `Available Cash` to Portfolio
105
+ portfolio ['Available Cash ($)' ] = self .initial_capital - (portfolio ['Share Quantity' ] * portfolio ['Sell Price' ])
106
+
107
+ # Dataframe Options and Filters
108
+ portfolio .reset_index (inplace = True )
109
+ portfolio .fillna (method = 'ffill' , inplace = True )
110
+
111
+ # DEV
112
+ # print(portfolio)
113
+
114
+ return portfolio
115
+
116
+ def backtest_portfolio_v3 (self ):
117
+
118
+ # FIXME: Fic the "Available Cash ($)" column data values and conditions
119
+
120
+ # (Filter) Remove 0.0 'position' values, as they are unecessary for the end analysis result
121
+ self .signal_hist = self .signal_hist [self .signal_hist ['position' ] != 0.0 ]
122
+ # Create a new DataFrame ‘portfolio’ to store the market value of an open position
123
+ portfolio = pd .DataFrame (index = self .signal_hist .index ).fillna (0.0 )
124
+ share_diff = self .positions .diff ()
125
+
126
+ # print(share_diff)
127
+
128
+ # Buy Order DataFrame
129
+ buy = self .signal_hist [self .signal_hist .position == 1.0 ]
130
+ buy ['Buy Order Time' ] = self .df .open_time
131
+ buy ['Buy Price' ] = buy ['SMA20' ]
132
+ buy ['Available Cash ($)' ] = self .initial_capital - (share_diff .multiply (buy ['Buy Price' ], axis = 0 )).sum (axis = 1 )
133
+ buy ['Holdings ($)' ] = (self .positions .multiply (buy ['Buy Price' ], axis = 0 )).sum (axis = 1 )
134
+ buy ['Share Quantity' ] = self .positions
135
+ buy .reset_index (inplace = True )
136
+
137
+ # Sell Order DataFrame
138
+ sell = self .signal_hist [self .signal_hist .position == - 1.0 ]
139
+ sell ['Sell Order Time' ] = self .df .open_time
140
+ sell ['Sell Price' ] = sell ['SMA20' ]
141
+ sell ['Return Cash ($)' ] = - (share_diff .multiply (sell ['Sell Price' ], axis = 0 )).sum (axis = 1 )
142
+ sell .reset_index (inplace = True )
143
+
144
+ # Using 'concat' because it works with NaN Column Values
145
+ portfolio = pd .concat ([buy ['Available Cash ($)' ],
146
+ buy ['Buy Order Time' ],
147
+ buy ['Buy Price' ],
148
+ buy ['Holdings ($)' ],
149
+ buy ['Share Quantity' ],
150
+ sell ['Sell Order Time' ],
151
+ sell ['Sell Price' ],
152
+ sell ['Return Cash ($)' ]], axis = 1 )
153
+
154
+ # Add `Percentage Change`
155
+ portfolio ['(%) Change' ] = (portfolio ['Return Cash ($)' ] - portfolio ['Holdings ($)' ]) / portfolio ['Holdings ($)' ] * 100
156
+ # portfolio['Running Total'] = (portfolio['Available Cash ($)'] + (portfolio['Available Cash ($)'] * (portfolio['(%) Change'] * 100)))
157
+
158
+ # (Optional) Add `comission` to the portfolio ie: comission per trade in ($) dollars
159
+ portfolio ['Comission ($)' ] = (portfolio ['Holdings ($)' ] + portfolio ['Return Cash ($)' ]) * (self .comission_per_trade / 100 )
160
+
161
+ # Dataframe Options and Filters
162
+ portfolio .fillna (method = 'ffill' , inplace = True )
163
+
164
+ # DEV
165
+ # print(portfolio)
166
+
167
+ return portfolio
0 commit comments