kwess 0.0.6__tar.gz → 0.1.0__tar.gz

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.
@@ -1,17 +1,14 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: kwess
3
- Version: 0.0.6
3
+ Version: 0.1.0
4
4
  Summary: Questrade API wrapper.
5
- Home-page: https://github.com/kaiyoux/kwess
6
- Author: Issa Lompo
7
- Author-email: kaiyoux@gmail.com
8
- Maintainer: Issa Lompo
9
- Maintainer-email: kaiyoux@gmail.com
10
- License: GNU General Public License v3 (GPLv3)
11
- Project-URL: Bug Tracker, https://github.com/kaiyoux/kwess/issues
12
- Keywords: Questrade,api,REST,wrapper
5
+ Author-email: Issa Lompo <kaiyoux@gmail.com>
6
+ Maintainer-email: Issa Lompo <kaiyoux@gmail.com>
7
+ License-Expression: GPL-3.0-or-later
8
+ Project-URL: Homepage, https://github.com/pypa/kwess
9
+ Project-URL: Bug Tracker, https://github.com
10
+ Keywords: questrade,api,REST,wrapper,ACB,profit,loss,average cost base,adjusted cost base,logs,kwess
13
11
  Classifier: Programming Language :: Python :: 3
14
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
15
12
  Classifier: Operating System :: OS Independent
16
13
  Classifier: Development Status :: 5 - Production/Stable
17
14
  Classifier: Intended Audience :: Developers
@@ -19,14 +16,19 @@ Requires-Python: >=3.6
19
16
  Description-Content-Type: text/markdown
20
17
  License-File: LICENSE.txt
21
18
  Requires-Dist: requests>=2.28.1
19
+ Dynamic: license-file
22
20
 
23
21
  # Introduction
24
22
 
25
23
  Yet another Questrade API wrapper.
26
-
27
24
  For details about the input parameters and output results, please visit
28
25
  the [Questrade API documentation](https://www.questrade.com/api/home).
29
26
 
27
+ Also includes an executable module that determines the ACB
28
+ (Average/Adjusted Cost Base) of buy and sell activities from
29
+ saved logs, and prints the profit/loss amounts of sell transactions
30
+ up till, but not including, a given cut-off year.
31
+
30
32
  ### To install:
31
33
  **python -m pip install kwess**
32
34
 
@@ -56,6 +58,7 @@ for acc in accs:
56
58
  print(acc, "\n")
57
59
 
58
60
  # Get (all types of) TFSA account orders from 17/8/2022 to now
61
+ # Questrade does not seem to keep old account orders - only the most recent
59
62
  accs = qs.get_account_orders(startdatetime=dt(year=2022, month=8, day=17), \
60
63
  verbose="vv")
61
64
  for acc in accs:
@@ -98,7 +101,7 @@ print(accs)
98
101
  sim = qs.search_symbols("vfv", verbose="88")
99
102
  print(sim)
100
103
 
101
- sim = qs.get_symbols_by_names("xdiv.to,xuu.to,cve.to", verbose="**")
104
+ sim = qs.get_symbols_by_names("xdiv.to,xuu.to,qqc.to", verbose="**")
102
105
  print(sim)
103
106
 
104
107
  sim = qs.get_symbols_by_names("hom.un.to")
@@ -127,6 +130,50 @@ pprint(ops)
127
130
 
128
131
  ```
129
132
 
133
+ # Sample Script To Pull Your Questrade Logs
134
+
135
+ ```
136
+ from kwess import Trader
137
+ from datetime import datetime as dt
138
+ import json
139
+
140
+
141
+ def get_logs(qt, startdate, enddate=None, accounttype="TFSA"):
142
+ """
143
+ Parameters:
144
+ - qt Trader object.
145
+ - startdate datetime object for beginning period.
146
+ - enddate datetime object for end period. Defaults to None, which will be treated as now.
147
+ - accounttype type of account for which activities will be obtained. Defaults to TFSA.
148
+ """
149
+ if enddate == None:
150
+ enddate = dt.now()
151
+ for filename in ["activities", "orders", "executions"]:
152
+ filename = accounttype + "_account_" + filename + "_from_" + str(startdate.date()) + "_to_" + str(enddate.date()) + ".json"
153
+ with open(filename, mode="at", encoding="utf-8") as jfp:
154
+ if "activities" in filename:
155
+ accs = qt.get_account_activities(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose="nnn")
156
+ elif "orders" in filename:
157
+ accs = qt.get_account_orders(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose="nnn")
158
+ else:
159
+ accs = qt.get_account_executions(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose='nnn')
160
+
161
+ for acc in accs:
162
+ json.dump(acc, jfp, ensure_ascii=False, indent=2)
163
+
164
+
165
+
166
+
167
+ if __name__ == "__main__":
168
+ #account_types = ["TFSA", "Cash", "Margin"]
169
+ #account_types = ["Cash"]
170
+ account_types = ["Margin"]
171
+ qt = Trader(verbose='n')
172
+
173
+ for acc_type in account_types:
174
+ get_logs(qt=qt, accounttype=acc_type, startdate=dt(year=2025, month=9, day=17))
175
+
176
+ ```
130
177
 
131
178
  # API Class And Methods
132
179
 
@@ -159,7 +206,7 @@ Parameters:
159
206
  - server_type could be 2 possible values: "live" or "test".
160
207
  "live" will allow you to interact with your real Questrade account.
161
208
  "test" is for interacting with your test account.
162
- - timeout number of seconds to wait for the API server to respond before
209
+ - timeout number of seconds to wait for the server to respond before
163
210
  giving up.
164
211
  Defaults to 15 seconds. Set timeout to None if you wish to wait forever
165
212
  for a response.
@@ -173,7 +220,7 @@ build_datetime_string(self, adatetime=None, gmt=False)
173
220
  Description:
174
221
  Higher level helper method used to build a Questrade datetime string.
175
222
  Parameters:
176
- - adatetime a datetime object.
223
+ - adatetime a datetime object. Defaults to now.
177
224
  - gmt optional boolean indicating if datetime is Greenwich Mean Time.
178
225
  Default value is False.
179
226
  Returns:
@@ -222,8 +269,8 @@ Returns:
222
269
  Account balances as a Python object representation of the returned json.
223
270
 
224
271
 
225
- get_account_executions(self, accounttype='TFSA', startdatetime=None,
226
- enddatetime=None, verbose='')
272
+ get_account_executions(self, startdatetime, enddatetime=None,
273
+ accounttype='TFSA', verbose='')
227
274
  Description:
228
275
  Generator that provides account executions from the account related to
229
276
  account type accounttype, between the range specified by startdatetime and
@@ -304,7 +351,7 @@ Yields:
304
351
  json.
305
352
 
306
353
 
307
- get_market_candles(self, sid, interval, startdatetime=None, enddatetime=None,
354
+ get_market_candles(self, sid, interval, startdatetime, enddatetime=None,
308
355
  verbose='')
309
356
  Description:
310
357
  Provides a list of json formatted market candles.
@@ -380,7 +427,7 @@ Returns:
380
427
 
381
428
  get_new_refresh_token(self, token, server_type, verbose='')
382
429
  Description:
383
- Obtains a new refresh token (and new access token) from the API server.
430
+ Obtains a new refresh token (and new access token) from the Authorization server.
384
431
  You generally would not need to call this method, as it is potentially called by
385
432
  Trader during initialization.
386
433
  Trader will only call this method if the access token has expired.
@@ -500,5 +547,32 @@ Returns:
500
547
  If gmt is True, the returned time will be considered as gmt time.
501
548
  ```
502
549
 
550
+ # Example of Executable Module
551
+
552
+ Open a command line in the directory containing your logs, and run the module:
553
+ ```
554
+ kwess_acb
555
+ (or)
556
+ python -m kwess.kwess_acb
557
+
558
+ ```
559
+
560
+ ### To run the scrpit:
561
+ **kwess_acb [-h] [-s SYMBOL [SYMBOL ...]] [-y CUT_OFF_YEAR] [-p PATH]**
562
+ ```
563
+ Computes the ACB (Average/Adjusted Cost Base) from kwess activity logs, and
564
+ prints profit/loss of Sell transactions.
565
+
566
+ options:
567
+ -h, --help show this help message and exit.
568
+ -s SYMBOL [SYMBOL ...], --symbol SYMBOL [SYMBOL ...]
569
+ ticker symbol(s).
570
+ -y CUT_OFF_YEAR, --cut_off_year CUT_OFF_YEAR
571
+ cut off year. defaults to the current year.
572
+ -p PATH, --path PATH path of the directory containing the logs.
573
+ defaults to the current directory.
574
+
575
+ ```
576
+
503
577
 
504
578
  Let me know if you have any questions: <kaiyoux@gmail.com>
@@ -1,32 +1,14 @@
1
- Metadata-Version: 2.1
2
- Name: kwess
3
- Version: 0.0.6
4
- Summary: Questrade API wrapper.
5
- Home-page: https://github.com/kaiyoux/kwess
6
- Author: Issa Lompo
7
- Author-email: kaiyoux@gmail.com
8
- Maintainer: Issa Lompo
9
- Maintainer-email: kaiyoux@gmail.com
10
- License: GNU General Public License v3 (GPLv3)
11
- Project-URL: Bug Tracker, https://github.com/kaiyoux/kwess/issues
12
- Keywords: Questrade,api,REST,wrapper
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
15
- Classifier: Operating System :: OS Independent
16
- Classifier: Development Status :: 5 - Production/Stable
17
- Classifier: Intended Audience :: Developers
18
- Requires-Python: >=3.6
19
- Description-Content-Type: text/markdown
20
- License-File: LICENSE.txt
21
- Requires-Dist: requests>=2.28.1
22
-
23
1
  # Introduction
24
2
 
25
3
  Yet another Questrade API wrapper.
26
-
27
4
  For details about the input parameters and output results, please visit
28
5
  the [Questrade API documentation](https://www.questrade.com/api/home).
29
6
 
7
+ Also includes an executable module that determines the ACB
8
+ (Average/Adjusted Cost Base) of buy and sell activities from
9
+ saved logs, and prints the profit/loss amounts of sell transactions
10
+ up till, but not including, a given cut-off year.
11
+
30
12
  ### To install:
31
13
  **python -m pip install kwess**
32
14
 
@@ -56,6 +38,7 @@ for acc in accs:
56
38
  print(acc, "\n")
57
39
 
58
40
  # Get (all types of) TFSA account orders from 17/8/2022 to now
41
+ # Questrade does not seem to keep old account orders - only the most recent
59
42
  accs = qs.get_account_orders(startdatetime=dt(year=2022, month=8, day=17), \
60
43
  verbose="vv")
61
44
  for acc in accs:
@@ -98,7 +81,7 @@ print(accs)
98
81
  sim = qs.search_symbols("vfv", verbose="88")
99
82
  print(sim)
100
83
 
101
- sim = qs.get_symbols_by_names("xdiv.to,xuu.to,cve.to", verbose="**")
84
+ sim = qs.get_symbols_by_names("xdiv.to,xuu.to,qqc.to", verbose="**")
102
85
  print(sim)
103
86
 
104
87
  sim = qs.get_symbols_by_names("hom.un.to")
@@ -127,6 +110,50 @@ pprint(ops)
127
110
 
128
111
  ```
129
112
 
113
+ # Sample Script To Pull Your Questrade Logs
114
+
115
+ ```
116
+ from kwess import Trader
117
+ from datetime import datetime as dt
118
+ import json
119
+
120
+
121
+ def get_logs(qt, startdate, enddate=None, accounttype="TFSA"):
122
+ """
123
+ Parameters:
124
+ - qt Trader object.
125
+ - startdate datetime object for beginning period.
126
+ - enddate datetime object for end period. Defaults to None, which will be treated as now.
127
+ - accounttype type of account for which activities will be obtained. Defaults to TFSA.
128
+ """
129
+ if enddate == None:
130
+ enddate = dt.now()
131
+ for filename in ["activities", "orders", "executions"]:
132
+ filename = accounttype + "_account_" + filename + "_from_" + str(startdate.date()) + "_to_" + str(enddate.date()) + ".json"
133
+ with open(filename, mode="at", encoding="utf-8") as jfp:
134
+ if "activities" in filename:
135
+ accs = qt.get_account_activities(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose="nnn")
136
+ elif "orders" in filename:
137
+ accs = qt.get_account_orders(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose="nnn")
138
+ else:
139
+ accs = qt.get_account_executions(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose='nnn')
140
+
141
+ for acc in accs:
142
+ json.dump(acc, jfp, ensure_ascii=False, indent=2)
143
+
144
+
145
+
146
+
147
+ if __name__ == "__main__":
148
+ #account_types = ["TFSA", "Cash", "Margin"]
149
+ #account_types = ["Cash"]
150
+ account_types = ["Margin"]
151
+ qt = Trader(verbose='n')
152
+
153
+ for acc_type in account_types:
154
+ get_logs(qt=qt, accounttype=acc_type, startdate=dt(year=2025, month=9, day=17))
155
+
156
+ ```
130
157
 
131
158
  # API Class And Methods
132
159
 
@@ -159,7 +186,7 @@ Parameters:
159
186
  - server_type could be 2 possible values: "live" or "test".
160
187
  "live" will allow you to interact with your real Questrade account.
161
188
  "test" is for interacting with your test account.
162
- - timeout number of seconds to wait for the API server to respond before
189
+ - timeout number of seconds to wait for the server to respond before
163
190
  giving up.
164
191
  Defaults to 15 seconds. Set timeout to None if you wish to wait forever
165
192
  for a response.
@@ -173,7 +200,7 @@ build_datetime_string(self, adatetime=None, gmt=False)
173
200
  Description:
174
201
  Higher level helper method used to build a Questrade datetime string.
175
202
  Parameters:
176
- - adatetime a datetime object.
203
+ - adatetime a datetime object. Defaults to now.
177
204
  - gmt optional boolean indicating if datetime is Greenwich Mean Time.
178
205
  Default value is False.
179
206
  Returns:
@@ -222,8 +249,8 @@ Returns:
222
249
  Account balances as a Python object representation of the returned json.
223
250
 
224
251
 
225
- get_account_executions(self, accounttype='TFSA', startdatetime=None,
226
- enddatetime=None, verbose='')
252
+ get_account_executions(self, startdatetime, enddatetime=None,
253
+ accounttype='TFSA', verbose='')
227
254
  Description:
228
255
  Generator that provides account executions from the account related to
229
256
  account type accounttype, between the range specified by startdatetime and
@@ -304,7 +331,7 @@ Yields:
304
331
  json.
305
332
 
306
333
 
307
- get_market_candles(self, sid, interval, startdatetime=None, enddatetime=None,
334
+ get_market_candles(self, sid, interval, startdatetime, enddatetime=None,
308
335
  verbose='')
309
336
  Description:
310
337
  Provides a list of json formatted market candles.
@@ -380,7 +407,7 @@ Returns:
380
407
 
381
408
  get_new_refresh_token(self, token, server_type, verbose='')
382
409
  Description:
383
- Obtains a new refresh token (and new access token) from the API server.
410
+ Obtains a new refresh token (and new access token) from the Authorization server.
384
411
  You generally would not need to call this method, as it is potentially called by
385
412
  Trader during initialization.
386
413
  Trader will only call this method if the access token has expired.
@@ -500,5 +527,32 @@ Returns:
500
527
  If gmt is True, the returned time will be considered as gmt time.
501
528
  ```
502
529
 
530
+ # Example of Executable Module
531
+
532
+ Open a command line in the directory containing your logs, and run the module:
533
+ ```
534
+ kwess_acb
535
+ (or)
536
+ python -m kwess.kwess_acb
537
+
538
+ ```
539
+
540
+ ### To run the scrpit:
541
+ **kwess_acb [-h] [-s SYMBOL [SYMBOL ...]] [-y CUT_OFF_YEAR] [-p PATH]**
542
+ ```
543
+ Computes the ACB (Average/Adjusted Cost Base) from kwess activity logs, and
544
+ prints profit/loss of Sell transactions.
545
+
546
+ options:
547
+ -h, --help show this help message and exit.
548
+ -s SYMBOL [SYMBOL ...], --symbol SYMBOL [SYMBOL ...]
549
+ ticker symbol(s).
550
+ -y CUT_OFF_YEAR, --cut_off_year CUT_OFF_YEAR
551
+ cut off year. defaults to the current year.
552
+ -p PATH, --path PATH path of the directory containing the logs.
553
+ defaults to the current directory.
554
+
555
+ ```
556
+
503
557
 
504
558
  Let me know if you have any questions: <kaiyoux@gmail.com>
@@ -1,7 +1,7 @@
1
1
 
2
2
  import requests
3
3
  import sys
4
- from pprint import pprint
4
+ from pprint import pprint as pp
5
5
  from datetime import datetime as dt, timezone as tz, timedelta as td
6
6
  import time
7
7
  import json
@@ -35,7 +35,7 @@ class Trader:
35
35
  - server_type could be 2 possible values: "live" or "test". "live" will allow you to
36
36
  interact with your real Questrade account. "test" is for interacting with your test
37
37
  account.
38
- - timeout number of seconds to wait for the API server to respond before giving up.
38
+ - timeout number of seconds to wait for the server to respond before giving up.
39
39
  Defaults to 15 seconds. Set timeout to None if you wish to wait forever for a response.
40
40
  - verbose level of verbosity represented by the number of characters in a string.
41
41
  Defaults to empty string. Maximum verbosity is 1 or "v".
@@ -88,7 +88,7 @@ class Trader:
88
88
  def get_new_refresh_token(self, token, verbose=''):
89
89
  """
90
90
  Description:
91
- Obtains a new refresh token (and new access token) from the API server.
91
+ Obtains a new refresh token (and new access token) from the Authorization server.
92
92
  You generally would not need to call this method, as it is potentially called by Trader
93
93
  during initialization.
94
94
  Trader will only call this method if the access token has expired.
@@ -179,7 +179,7 @@ class Trader:
179
179
  print(f"Accounts for user id {self.userid}:")
180
180
  for account in self.accounts:
181
181
  if verbose:
182
- pprint(account)
182
+ pp(account)
183
183
  yield account
184
184
 
185
185
 
@@ -231,8 +231,8 @@ class Trader:
231
231
  datetime objects.
232
232
  Parameters:
233
233
  - startdatetime datetime object specifying the start of a range.
234
- - enddatetime optional datetime object specifying the end of a range. Defaults to
235
- now (datetime.datetime.now()) if not specified.
234
+ - enddatetime optional datetime object specifying the end of a range.
235
+ Defaults to now (datetime.datetime.now()) if not specified.
236
236
  - accounttype type of Questrade account. Defaults to "tfsa".
237
237
  - verbose level of verbosity represented by the number of characters in a string.
238
238
  Defaults to empty string. Maximum verbosity is 3 or "vvv".
@@ -262,7 +262,7 @@ class Trader:
262
262
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for account activities.", f"{self.server_type} server returned {resp.status_code} on get_account_activities().", ex)
263
263
 
264
264
  if verbosity > 1:
265
- pprint(resp.json())
265
+ pp(resp.json())
266
266
  return resp.json()
267
267
 
268
268
 
@@ -334,7 +334,7 @@ class Trader:
334
334
  Description:
335
335
  Higher level helper method used to build a Questrade datetime string.
336
336
  Parameters:
337
- - adatetime a datetime object.
337
+ - adatetime a datetime object. Defaults to now.
338
338
  - gmt optional boolean indicating if datetime is Greenwich Mean Time.
339
339
  Default value is False.
340
340
  Returns:
@@ -413,7 +413,7 @@ class Trader:
413
413
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for account orders.", f"{self.server_type} server returned {resp.status_code} on get_account_orders().", ex)
414
414
 
415
415
  if verbosity > 1:
416
- pprint(resp.json())
416
+ pp(resp.json())
417
417
  return resp.json()
418
418
 
419
419
 
@@ -449,12 +449,12 @@ class Trader:
449
449
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for account orders by ids.", f"{self.server_type} server returned {resp.status_code} on get_account_orders_by_ids().", ex)
450
450
 
451
451
  if verbosity > 0:
452
- pprint(resp.json())
452
+ pp(resp.json())
453
453
  return resp.json()
454
454
 
455
455
 
456
456
  @get_all
457
- def get_account_executions(self, accounttype="TFSA", startdatetime=None, enddatetime=None, verbose=''):
457
+ def get_account_executions(self, startdatetime, enddatetime=None, accounttype="TFSA", verbose=''):
458
458
  """
459
459
  Description:
460
460
  Generator that provides account executions from the account related to account type
@@ -494,7 +494,7 @@ class Trader:
494
494
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for account executions.", f"{self.server_type} server returned {resp.status_code} on get_account_executions().", ex)
495
495
 
496
496
  if verbosity > 1:
497
- pprint(resp.json())
497
+ pp(resp.json())
498
498
  return resp.json()
499
499
 
500
500
 
@@ -525,7 +525,7 @@ class Trader:
525
525
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for account balances.", f"{self.server_type} server returned {resp.status_code} on get_account_balances().", ex)
526
526
 
527
527
  if verbosity > 0:
528
- pprint(resp.json())
528
+ pp(resp.json())
529
529
  return resp.json()
530
530
 
531
531
 
@@ -556,7 +556,7 @@ class Trader:
556
556
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for account positions.", f"{self.server_type} server returned {resp.status_code} on get_account_positions().", ex)
557
557
 
558
558
  if verbosity > 0:
559
- pprint(resp.json())
559
+ pp(resp.json())
560
560
  return resp.json()
561
561
 
562
562
 
@@ -583,12 +583,12 @@ class Trader:
583
583
 
584
584
  rd = resp.json()
585
585
  if verbosity > 0:
586
- pprint(dt.strptime(rd["time"][:-13], '%Y-%m-%dT%X'))
586
+ pp(dt.strptime(rd["time"][:-13], '%Y-%m-%dT%X'))
587
587
  return dt.strptime(rd["time"][:-13], '%Y-%m-%dT%X'), rd
588
588
 
589
589
 
590
590
  @get_all
591
- def get_market_candles(self, sid, interval, startdatetime=None, enddatetime=None, verbose=''):
591
+ def get_market_candles(self, sid, interval, startdatetime, enddatetime=None, verbose=''):
592
592
  """
593
593
  Description:
594
594
  Provides a list of json formatted market candles.
@@ -623,7 +623,7 @@ class Trader:
623
623
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for market candles.", f"{self.server_type} server returned {resp.status_code} on get_market_candles().", ex)
624
624
 
625
625
  if verbosity > 0:
626
- pprint(resp.json())
626
+ pp(resp.json())
627
627
  return resp.json()
628
628
 
629
629
 
@@ -652,7 +652,7 @@ class Trader:
652
652
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for market quote strategies.", f"{self.server_type} server returned {resp.status_code} on get_market_quotes_strategies().", ex)
653
653
 
654
654
  if verbosity > 0:
655
- pprint(resp.json())
655
+ pp(resp.json())
656
656
  return resp.json()
657
657
 
658
658
 
@@ -684,7 +684,7 @@ class Trader:
684
684
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for market quote options.", f"{self.server_type} server returned {resp.status_code} on get_market_quotes_options().", ex)
685
685
 
686
686
  if verbosity > 0:
687
- pprint(resp.json())
687
+ pp(resp.json())
688
688
  return resp.json()
689
689
 
690
690
 
@@ -729,7 +729,7 @@ class Trader:
729
729
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for market quotes.", f"{self.server_type} server returned {resp.status_code} on get_market_quotes().", ex)
730
730
 
731
731
  if verbosity > 0:
732
- pprint(resp.json())
732
+ pp(resp.json())
733
733
  return resp.json()
734
734
 
735
735
 
@@ -756,7 +756,7 @@ class Trader:
756
756
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for markets.", f"{self.server_type} server returned {resp.status_code} on get_markets().", ex)
757
757
 
758
758
  if verbosity > 0:
759
- pprint(resp.json())
759
+ pp(resp.json())
760
760
  return resp.json()
761
761
 
762
762
 
@@ -784,7 +784,7 @@ class Trader:
784
784
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for symbol options.", f"{self.server_type} server returned {resp.status_code} on get_symbol_options().", ex)
785
785
 
786
786
  if verbosity > 0:
787
- pprint(resp.json())
787
+ pp(resp.json())
788
788
  return resp.json()
789
789
 
790
790
 
@@ -814,7 +814,7 @@ class Trader:
814
814
  self._report_and_exit(resp.request.url, resp.text, "Failed to search server for symbols.", f"{self.server_type} server returned {resp.status_code} on search_symbols().", ex)
815
815
 
816
816
  if verbosity > 0:
817
- pprint(resp.json())
817
+ pp(resp.json())
818
818
  return resp.json()
819
819
 
820
820
 
@@ -855,7 +855,7 @@ class Trader:
855
855
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for symbols by ids.", f"{self.server_type} server returned {resp.status_code} on get_symbols_by_ids().", ex)
856
856
 
857
857
  if verbosity > 0:
858
- pprint(resp.json())
858
+ pp(resp.json())
859
859
  return resp.json()
860
860
 
861
861
 
@@ -889,6 +889,6 @@ class Trader:
889
889
  self._report_and_exit(resp.request.url, resp.text, "Failed to query server for symbols by names.", f"{self.server_type} server returned {resp.status_code} on get_symbols_by_names().", ex)
890
890
 
891
891
  if verbosity > 0:
892
- pprint(resp.json())
892
+ pp(resp.json())
893
893
  return resp.json()
894
894
 
@@ -0,0 +1,7 @@
1
+
2
+ from kwess.kwess_acb import main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
7
+
@@ -0,0 +1,176 @@
1
+ # kwess_acb.py
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from pprint import pprint as pp
6
+ from datetime import datetime as dt
7
+ import argparse
8
+ import math
9
+ import sys
10
+
11
+
12
+ class Activities:
13
+ def __init__(self, cut_off_year=0, path="."):
14
+ """
15
+ Description:
16
+ Initializer of an Activities object.
17
+ Parameters:
18
+ - cut_off_year the ACB will be calculated up until (but not including) the cut_off_year.
19
+ - path directory path in which your Questrade investment activities logs are located.
20
+ Returns:
21
+ Activities object.
22
+ """
23
+ self.data = []
24
+ self.all_symbols = set()
25
+ self.path = path
26
+ self.symbol = ""
27
+ if cut_off_year == 0:
28
+ self.cut_off_year = dt.today().year
29
+ else:
30
+ self.cut_off_year = cut_off_year
31
+ self.load_from_json_files()
32
+
33
+
34
+ def load_from_json_files(self):
35
+ """
36
+ Description:
37
+ Loads activities logs. It is called during Activities object initialization.
38
+ """
39
+ p = Path(self.path)
40
+ if not p.exists():
41
+ print(f"Invalid path: {self.path}")
42
+ sys.exit(1)
43
+ data = []
44
+ for e in p.glob("*activities*.json"):
45
+ with e.open(mode="rt") as f:
46
+ t = f.read()
47
+ t = t.replace("}{", "},{")
48
+ s = "[" + t + "]"
49
+ l = json.loads(s)
50
+ for d in l:
51
+ for v in d.values():
52
+ for a in v:
53
+ #pp(a)
54
+ if (dt.fromisoformat(a["settlementDate"]).year < self.cut_off_year) and (a["action"] in ("Buy", "Sell")):
55
+ data.append(a)
56
+ self.all_symbols.add(a["symbol"])
57
+ self.data = sorted(data, key=lambda x: dt.fromisoformat(x["settlementDate"]))
58
+
59
+
60
+ def generate_adjusted_cost_base(self, symbol, include_fees=True):
61
+ """
62
+ Description:
63
+ Calculates the ACB and prints profit/loss of Sell transactions for the given symbol.
64
+ Parameters:
65
+ - symbol the ticker symbol.
66
+ - include_fees whether or not to include the commission fees in the calculation. Defaults to True.
67
+ """
68
+ prev_shares = 0
69
+ prev_cost_base = 0
70
+ prev_cost_base_per_share = 1
71
+ add_newline = False
72
+ for e in self.data:
73
+ if e["symbol"] == symbol.upper():
74
+ if e["action"] == "Buy":
75
+ shares = e["quantity"] + prev_shares
76
+ if include_fees:
77
+ cost_base = math.fabs(e["netAmount"]) + prev_cost_base # includes commission and fees
78
+ else:
79
+ cost_base = math.fabs(e["grossAmount"]) + prev_cost_base # does not include commission and fees
80
+ e["total_shares"] = shares
81
+ e["total_cost_base"] = cost_base
82
+ e["cost_base_per_share"] = cost_base / shares
83
+ prev_shares = shares
84
+ prev_cost_base = cost_base
85
+ prev_cost_base_per_share = e["cost_base_per_share"]
86
+ elif e["action"] == "Sell":
87
+ cost_base_per_share = prev_cost_base_per_share
88
+ if include_fees:
89
+ #print(f"e[grossAmount] {e["grossAmount"]}\ne[commission] {e["commission"]}\ne[quantity] {e["quantity"]}\nprev_cost_base {prev_cost_base}\n prev_shares {prev_shares}")
90
+ capital_gain_loss = e["grossAmount"] + e["commission"] + e["quantity"] * cost_base_per_share
91
+ else:
92
+ capital_gain_loss = e["grossAmount"] + e["quantity"] * cost_base_per_share
93
+ prev_cost_base += e["quantity"] * cost_base_per_share
94
+ prev_shares += e["quantity"]
95
+ e["capital_gain_loss"] = capital_gain_loss
96
+ e["total_shares"] = prev_shares
97
+ e["total_cost_base"] = prev_cost_base # for debugging
98
+ e["cost_base_per_share"] = cost_base_per_share # for debugging
99
+ if capital_gain_loss > 0:
100
+ print(f'{symbol} capital gain of {capital_gain_loss} on {e["settlementDate"]}')
101
+ add_newline = True
102
+ elif capital_gain_loss < 0:
103
+ print(f'{symbol} capital loss of {capital_gain_loss} on {e["settlementDate"]}')
104
+ add_newline = True
105
+ if add_newline:
106
+ print()
107
+
108
+
109
+ def generate_all_ACBs(self, include_fees=True):
110
+ """
111
+ Description:
112
+ Calculates the ACB and prints profit/loss of Sell transactions for all the symbols present in the activities logs.
113
+ Parameters:
114
+ - include_fees whether or not to include the commission fees in the calculation. Defaults to True.
115
+ """
116
+ for symbol in self.all_symbols:
117
+ self.generate_adjusted_cost_base(symbol, include_fees)
118
+
119
+
120
+ def print_by_symbol(self, symbol):
121
+ """
122
+ Description:
123
+ Prints the activities logs relating to symbol.
124
+ Parameters:
125
+ - symbol the ticker symbol.
126
+ """
127
+ for e in self.data:
128
+ if e["symbol"] == symbol.upper():
129
+ pp(e)
130
+
131
+
132
+ def __str__(self):
133
+ """
134
+ Description:
135
+ Used for printing the activities logs.
136
+ Returns:
137
+ String representation of the activities logs.
138
+ """
139
+ s = ""
140
+ for e in self.data:
141
+ s = "\n".join([s, str(e)])
142
+ return s
143
+
144
+
145
+ def __repr__(self):
146
+ """
147
+ Description:
148
+ Used for printing the activities object.
149
+ Returns:
150
+ String representation of the Activities object.
151
+ """
152
+ return f"Activities(data: {self.data}\nsymbol: {self.symbol}\nall_symbols: {self.all_symbols}\npath: {self.path}\ncut_off_year: {self.cut_off_year})"
153
+
154
+
155
+ def main():
156
+ """
157
+ Description:
158
+ Calculates the ACB and prints the profit/loss of Sell transactions for the symbol(s) present in the activities logs.
159
+ """
160
+ parser = argparse.ArgumentParser(prog="kwess_acb.py", description="Computes the ACB (Average/Adjusted Cost Base) from kwess activity logs, and prints profit/loss of Sell transactions.")
161
+ parser.add_argument("-s", "--symbol", nargs="+", help="ticker symbol(s).")
162
+ parser.add_argument("-y", "--cut_off_year", default=0, help="cut off year. defaults to the current year.")
163
+ parser.add_argument("-p", "--path", default=".", help="path of the directory containing the logs. defaults to the current directory.")
164
+ args = parser.parse_args()
165
+
166
+ acts = Activities(cut_off_year=int(args.cut_off_year), path=args.path)
167
+ if args.symbol:
168
+ for symbol in args.symbol:
169
+ acts.generate_adjusted_cost_base(symbol)
170
+ else:
171
+ acts.generate_all_ACBs()
172
+
173
+
174
+
175
+ if __name__ == "__main__":
176
+ main()
@@ -1,10 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: kwess
3
+ Version: 0.1.0
4
+ Summary: Questrade API wrapper.
5
+ Author-email: Issa Lompo <kaiyoux@gmail.com>
6
+ Maintainer-email: Issa Lompo <kaiyoux@gmail.com>
7
+ License-Expression: GPL-3.0-or-later
8
+ Project-URL: Homepage, https://github.com/pypa/kwess
9
+ Project-URL: Bug Tracker, https://github.com
10
+ Keywords: questrade,api,REST,wrapper,ACB,profit,loss,average cost base,adjusted cost base,logs,kwess
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Requires-Python: >=3.6
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE.txt
18
+ Requires-Dist: requests>=2.28.1
19
+ Dynamic: license-file
20
+
1
21
  # Introduction
2
22
 
3
23
  Yet another Questrade API wrapper.
4
-
5
24
  For details about the input parameters and output results, please visit
6
25
  the [Questrade API documentation](https://www.questrade.com/api/home).
7
26
 
27
+ Also includes an executable module that determines the ACB
28
+ (Average/Adjusted Cost Base) of buy and sell activities from
29
+ saved logs, and prints the profit/loss amounts of sell transactions
30
+ up till, but not including, a given cut-off year.
31
+
8
32
  ### To install:
9
33
  **python -m pip install kwess**
10
34
 
@@ -34,6 +58,7 @@ for acc in accs:
34
58
  print(acc, "\n")
35
59
 
36
60
  # Get (all types of) TFSA account orders from 17/8/2022 to now
61
+ # Questrade does not seem to keep old account orders - only the most recent
37
62
  accs = qs.get_account_orders(startdatetime=dt(year=2022, month=8, day=17), \
38
63
  verbose="vv")
39
64
  for acc in accs:
@@ -76,7 +101,7 @@ print(accs)
76
101
  sim = qs.search_symbols("vfv", verbose="88")
77
102
  print(sim)
78
103
 
79
- sim = qs.get_symbols_by_names("xdiv.to,xuu.to,cve.to", verbose="**")
104
+ sim = qs.get_symbols_by_names("xdiv.to,xuu.to,qqc.to", verbose="**")
80
105
  print(sim)
81
106
 
82
107
  sim = qs.get_symbols_by_names("hom.un.to")
@@ -105,6 +130,50 @@ pprint(ops)
105
130
 
106
131
  ```
107
132
 
133
+ # Sample Script To Pull Your Questrade Logs
134
+
135
+ ```
136
+ from kwess import Trader
137
+ from datetime import datetime as dt
138
+ import json
139
+
140
+
141
+ def get_logs(qt, startdate, enddate=None, accounttype="TFSA"):
142
+ """
143
+ Parameters:
144
+ - qt Trader object.
145
+ - startdate datetime object for beginning period.
146
+ - enddate datetime object for end period. Defaults to None, which will be treated as now.
147
+ - accounttype type of account for which activities will be obtained. Defaults to TFSA.
148
+ """
149
+ if enddate == None:
150
+ enddate = dt.now()
151
+ for filename in ["activities", "orders", "executions"]:
152
+ filename = accounttype + "_account_" + filename + "_from_" + str(startdate.date()) + "_to_" + str(enddate.date()) + ".json"
153
+ with open(filename, mode="at", encoding="utf-8") as jfp:
154
+ if "activities" in filename:
155
+ accs = qt.get_account_activities(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose="nnn")
156
+ elif "orders" in filename:
157
+ accs = qt.get_account_orders(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose="nnn")
158
+ else:
159
+ accs = qt.get_account_executions(accounttype=accounttype, startdatetime=startdate, enddatetime=enddate, verbose='nnn')
160
+
161
+ for acc in accs:
162
+ json.dump(acc, jfp, ensure_ascii=False, indent=2)
163
+
164
+
165
+
166
+
167
+ if __name__ == "__main__":
168
+ #account_types = ["TFSA", "Cash", "Margin"]
169
+ #account_types = ["Cash"]
170
+ account_types = ["Margin"]
171
+ qt = Trader(verbose='n')
172
+
173
+ for acc_type in account_types:
174
+ get_logs(qt=qt, accounttype=acc_type, startdate=dt(year=2025, month=9, day=17))
175
+
176
+ ```
108
177
 
109
178
  # API Class And Methods
110
179
 
@@ -137,7 +206,7 @@ Parameters:
137
206
  - server_type could be 2 possible values: "live" or "test".
138
207
  "live" will allow you to interact with your real Questrade account.
139
208
  "test" is for interacting with your test account.
140
- - timeout number of seconds to wait for the API server to respond before
209
+ - timeout number of seconds to wait for the server to respond before
141
210
  giving up.
142
211
  Defaults to 15 seconds. Set timeout to None if you wish to wait forever
143
212
  for a response.
@@ -151,7 +220,7 @@ build_datetime_string(self, adatetime=None, gmt=False)
151
220
  Description:
152
221
  Higher level helper method used to build a Questrade datetime string.
153
222
  Parameters:
154
- - adatetime a datetime object.
223
+ - adatetime a datetime object. Defaults to now.
155
224
  - gmt optional boolean indicating if datetime is Greenwich Mean Time.
156
225
  Default value is False.
157
226
  Returns:
@@ -200,8 +269,8 @@ Returns:
200
269
  Account balances as a Python object representation of the returned json.
201
270
 
202
271
 
203
- get_account_executions(self, accounttype='TFSA', startdatetime=None,
204
- enddatetime=None, verbose='')
272
+ get_account_executions(self, startdatetime, enddatetime=None,
273
+ accounttype='TFSA', verbose='')
205
274
  Description:
206
275
  Generator that provides account executions from the account related to
207
276
  account type accounttype, between the range specified by startdatetime and
@@ -282,7 +351,7 @@ Yields:
282
351
  json.
283
352
 
284
353
 
285
- get_market_candles(self, sid, interval, startdatetime=None, enddatetime=None,
354
+ get_market_candles(self, sid, interval, startdatetime, enddatetime=None,
286
355
  verbose='')
287
356
  Description:
288
357
  Provides a list of json formatted market candles.
@@ -358,7 +427,7 @@ Returns:
358
427
 
359
428
  get_new_refresh_token(self, token, server_type, verbose='')
360
429
  Description:
361
- Obtains a new refresh token (and new access token) from the API server.
430
+ Obtains a new refresh token (and new access token) from the Authorization server.
362
431
  You generally would not need to call this method, as it is potentially called by
363
432
  Trader during initialization.
364
433
  Trader will only call this method if the access token has expired.
@@ -478,5 +547,32 @@ Returns:
478
547
  If gmt is True, the returned time will be considered as gmt time.
479
548
  ```
480
549
 
550
+ # Example of Executable Module
551
+
552
+ Open a command line in the directory containing your logs, and run the module:
553
+ ```
554
+ kwess_acb
555
+ (or)
556
+ python -m kwess.kwess_acb
557
+
558
+ ```
559
+
560
+ ### To run the scrpit:
561
+ **kwess_acb [-h] [-s SYMBOL [SYMBOL ...]] [-y CUT_OFF_YEAR] [-p PATH]**
562
+ ```
563
+ Computes the ACB (Average/Adjusted Cost Base) from kwess activity logs, and
564
+ prints profit/loss of Sell transactions.
565
+
566
+ options:
567
+ -h, --help show this help message and exit.
568
+ -s SYMBOL [SYMBOL ...], --symbol SYMBOL [SYMBOL ...]
569
+ ticker symbol(s).
570
+ -y CUT_OFF_YEAR, --cut_off_year CUT_OFF_YEAR
571
+ cut off year. defaults to the current year.
572
+ -p PATH, --path PATH path of the directory containing the logs.
573
+ defaults to the current directory.
574
+
575
+ ```
576
+
481
577
 
482
578
  Let me know if you have any questions: <kaiyoux@gmail.com>
@@ -1,11 +1,13 @@
1
1
  LICENSE.txt
2
2
  README.md
3
3
  pyproject.toml
4
- setup.cfg
5
4
  kwess/__init__.py
5
+ kwess/__main__.py
6
+ kwess/kwess_acb.py
6
7
  kwess.egg-info/PKG-INFO
7
8
  kwess.egg-info/SOURCES.txt
8
9
  kwess.egg-info/dependency_links.txt
10
+ kwess.egg-info/entry_points.txt
9
11
  kwess.egg-info/requires.txt
10
12
  kwess.egg-info/top_level.txt
11
13
  kwess.egg-info/zip-safe
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kwess_acb = kwess.kwess_acb:main
@@ -1 +1,2 @@
1
+ dist
1
2
  kwess
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kwess"
7
+ version = "0.1.0"
8
+ description = "Questrade API wrapper."
9
+ readme = "README.md"
10
+ requires-python = ">=3.6"
11
+ keywords = ["questrade", "api", "REST", "wrapper", "ACB", "profit", "loss", "average cost base", "adjusted cost base", "logs", "kwess"]
12
+ license = "GPL-3.0-or-later"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Intended Audience :: Developers",
18
+ ]
19
+
20
+ # Authors and Maintainers are now arrays of tables
21
+ authors = [
22
+ {name = "Issa Lompo", email = "kaiyoux@gmail.com"}
23
+ ]
24
+ maintainers = [
25
+ {name = "Issa Lompo", email = "kaiyoux@gmail.com"}
26
+ ]
27
+
28
+ dependencies = ["requests>=2.28.1"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/pypa/kwess"
32
+ "Bug Tracker" = "https://github.com"
33
+
34
+ [project.scripts]
35
+ kwess_acb = "kwess.kwess_acb:main"
36
+
37
+ [tool.setuptools]
38
+ include-package-data = true
39
+ zip-safe = true
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["."] # Standard if your code is in the root; use ["src"] if using src layout
43
+
44
+ [tool.setuptools.package-data]
45
+ sample_configs = ["*.txt"]
46
+ "*" = ["*.txt", "*.md"]
kwess-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -1,3 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=42"]
3
- build-backend = "setuptools.build_meta"
kwess-0.0.6/setup.cfg DELETED
@@ -1,39 +0,0 @@
1
- [metadata]
2
- name = kwess
3
- version = 0.0.6
4
- author = Issa Lompo
5
- author_email = kaiyoux@gmail.com
6
- maintainer = Issa Lompo
7
- maintainer_email = kaiyoux@gmail.com
8
- description = Questrade API wrapper.
9
- long_description = file: README.md
10
- long_description_content_type = text/markdown
11
- url = https://github.com/kaiyoux/kwess
12
- keywords = Questrade, api, REST, wrapper
13
- license = GNU General Public License v3 (GPLv3)
14
- license_files = LICENSE.txt
15
- project_urls =
16
- Bug Tracker = https://github.com/kaiyoux/kwess/issues
17
- classifiers =
18
- Programming Language :: Python :: 3
19
- License :: OSI Approved :: GNU General Public License v3 (GPLv3)
20
- Operating System :: OS Independent
21
- Development Status :: 5 - Production/Stable
22
- Intended Audience :: Developers
23
-
24
- [options]
25
- packages = find:
26
- python_requires = >=3.6
27
- zip_safe = True
28
- include_package_data = True
29
- install_requires =
30
- requests>=2.28.1
31
-
32
- [options.package_data]
33
- sample_configs = *.txt
34
- * = *.txt, *.md
35
-
36
- [egg_info]
37
- tag_build =
38
- tag_date = 0
39
-
File without changes
File without changes
File without changes