kwess 0.0.7__py3-none-any.whl → 0.1.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.
kwess/__init__.py CHANGED
@@ -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
@@ -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
 
@@ -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
 
@@ -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,7 +449,7 @@ 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
 
@@ -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,7 +583,7 @@ 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
 
@@ -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
 
kwess/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+
2
+ from kwess.kwess_acb import main
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
7
+
kwess/kwess_acb.py ADDED
@@ -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,32 +1,34 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: kwess
3
- Version: 0.0.7
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
18
15
  Requires-Python: >=3.6
19
16
  Description-Content-Type: text/markdown
20
17
  License-File: LICENSE.txt
21
- Requires-Dist: requests >=2.28.1
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
 
@@ -99,7 +101,7 @@ print(accs)
99
101
  sim = qs.search_symbols("vfv", verbose="88")
100
102
  print(sim)
101
103
 
102
- 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="**")
103
105
  print(sim)
104
106
 
105
107
  sim = qs.get_symbols_by_names("hom.un.to")
@@ -128,6 +130,50 @@ pprint(ops)
128
130
 
129
131
  ```
130
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
+ ```
131
177
 
132
178
  # API Class And Methods
133
179
 
@@ -501,5 +547,32 @@ Returns:
501
547
  If gmt is True, the returned time will be considered as gmt time.
502
548
  ```
503
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
+
504
577
 
505
578
  Let me know if you have any questions: <kaiyoux@gmail.com>
@@ -0,0 +1,10 @@
1
+ kwess/__init__.py,sha256=ma5qr504ZX-5aB3pSJz6OIXncDpdv-j0L7GyeDAOLe8,43905
2
+ kwess/__main__.py,sha256=Y3IEU-vW_vbl_cpabJfofkyJPvjrZM-AnEKWitFUdeQ,86
3
+ kwess/kwess_acb.py,sha256=fIEwBgygiYbSdmvwcq-utDIS4wJa7iJv9FcJT72PZJI,7239
4
+ kwess-0.1.0.dist-info/licenses/LICENSE.txt,sha256=gcuuhKKc5-dwvyvHsXjlC9oM6N5gZ6umYbC8ewW1Yvg,35821
5
+ kwess-0.1.0.dist-info/METADATA,sha256=y7LMOdHx1DcSIhalWYfANCuDL73bOLbRCnsiZFF7z24,22714
6
+ kwess-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
7
+ kwess-0.1.0.dist-info/entry_points.txt,sha256=jzERIdOqYeuefJsph3YQRGdUZYVdN1uo4SAQq17SNMI,51
8
+ kwess-0.1.0.dist-info/top_level.txt,sha256=jdYzcYQHwnZajr9CyHMIRBi6MrEGYTJ-uaAT3M50kP0,6
9
+ kwess-0.1.0.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
10
+ kwess-0.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kwess_acb = kwess.kwess_acb:main
@@ -1,7 +0,0 @@
1
- kwess/__init__.py,sha256=hG0J_zyNPRCVd-_VEbg9a26HvtfcbdnkUHpRJ0PhyGI,43967
2
- kwess-0.0.7.dist-info/LICENSE.txt,sha256=gcuuhKKc5-dwvyvHsXjlC9oM6N5gZ6umYbC8ewW1Yvg,35821
3
- kwess-0.0.7.dist-info/METADATA,sha256=MbNWz6np_171fBoycthEz60AQ7kqj3APbDS7sykUpH0,19940
4
- kwess-0.0.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
5
- kwess-0.0.7.dist-info/top_level.txt,sha256=jdYzcYQHwnZajr9CyHMIRBi6MrEGYTJ-uaAT3M50kP0,6
6
- kwess-0.0.7.dist-info/zip-safe,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
7
- kwess-0.0.7.dist-info/RECORD,,