investfly-sdk 1.1__tar.gz → 1.3__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.
Files changed (48) hide show
  1. {investfly_sdk-1.1/investfly_sdk.egg-info → investfly_sdk-1.3}/PKG-INFO +32 -13
  2. {investfly_sdk-1.1 → investfly_sdk-1.3}/README.md +31 -12
  3. investfly_sdk-1.3/investfly/__init__.py +5 -0
  4. investfly_sdk-1.3/investfly/api/IndicatorApiClient.py +24 -0
  5. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/InvestflyApiClient.py +5 -1
  6. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/MarketDataApiClient.py +4 -0
  7. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/PortfolioApiClient.py +4 -0
  8. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/RestApiClient.py +5 -0
  9. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/StrategyApiClient.py +23 -5
  10. investfly_sdk-1.3/investfly/api/__init__.py +17 -0
  11. investfly_sdk-1.3/investfly/cli/InvestflyCli.py +218 -0
  12. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/CommonModels.py +31 -0
  13. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/Indicator.py +89 -14
  14. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/MarketData.py +13 -2
  15. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/MarketDataIds.py +2 -1
  16. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/PortfolioModels.py +11 -0
  17. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/SecurityUniverseSelector.py +9 -1
  18. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/StrategyModels.py +41 -2
  19. investfly_sdk-1.3/investfly/models/TradingStrategy.py +110 -0
  20. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/__init__.py +9 -1
  21. {investfly_sdk-1.1 → investfly_sdk-1.3/investfly_sdk.egg-info}/PKG-INFO +32 -13
  22. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/SOURCES.txt +1 -0
  23. {investfly_sdk-1.1 → investfly_sdk-1.3}/pyproject.toml +1 -1
  24. investfly_sdk-1.1/investfly/__init__.py +0 -3
  25. investfly_sdk-1.1/investfly/api/__init__.py +0 -3
  26. investfly_sdk-1.1/investfly/cli/InvestflyCli.py +0 -194
  27. investfly_sdk-1.1/investfly/models/TradingStrategy.py +0 -59
  28. {investfly_sdk-1.1 → investfly_sdk-1.3}/LICENSE.txt +0 -0
  29. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/cli/__init__.py +0 -0
  30. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/ModelUtils.py +0 -0
  31. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/SecurityFilterModels.py +0 -0
  32. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/__init__.py +0 -0
  33. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/IndicatorTemplate.py +0 -0
  34. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/NewsSentiment.py +0 -0
  35. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/RsiOfSma.py +0 -0
  36. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/SmaEmaAverage.py +0 -0
  37. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/__init__.py +0 -0
  38. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/strategies/SmaCrossOverStrategy.py +0 -0
  39. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/strategies/SmaCrossOverTemplate.py +0 -0
  40. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/strategies/__init__.py +0 -0
  41. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/utils/CommonUtils.py +0 -0
  42. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/utils/PercentBasedPortfolioAllocator.py +0 -0
  43. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/utils/__init__.py +0 -0
  44. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/dependency_links.txt +0 -0
  45. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/entry_points.txt +0 -0
  46. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/requires.txt +0 -0
  47. {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/top_level.txt +0 -0
  48. {investfly_sdk-1.1 → investfly_sdk-1.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: investfly-sdk
3
- Version: 1.1
3
+ Version: 1.3
4
4
  Summary: Investfly SDK
5
5
  Author-email: "Investfly.com" <admin@investfly.com>
6
6
  License: The MIT License (MIT)
@@ -83,25 +83,40 @@ pip install investfly-sdk
83
83
 
84
84
  **Launch investfly-cli**
85
85
 
86
- Investfly SDK comes with a command line tool (CLI) called investfly-cli.
86
+ Install investfly-sdk also adds investfly-cli in your path inside the virtual environment. It can be launched simply by using `investfly-cli` command in the virtual env.
87
+
87
88
  ```commandline
88
- investfly-cli
89
+ (venv) user@host$ investfly-cli
89
90
 
90
91
  investfly-cli$ -h
91
- usage: investfly-cli [-h] {login,logout,listStrategies,copySamples,exit} ...
92
+ usage: investfly-cli [-h]
93
+ {login,logout,strategy.list,strategy.copysamples,strategy.create,strategy.download,strategy.update,strategy.backtest.start,strategy.backtest.stop,strategy.backtest.result,exit}
94
+ ...
92
95
 
93
96
  positional arguments:
94
- {login,logout,listStrategies,copySamples,exit}
97
+ {login,logout,strategy.list,strategy.copysamples,strategy.create,strategy.download,strategy.update,strategy.backtest.start,strategy.backtest.stop,strategy.backtest.result,exit}
95
98
  Available Commands
96
99
  login Login to Investfly
97
100
  logout Logout from Investfly
98
- listStrategies List Python Strategies
99
- copySamples Copy Samples from SDK
100
- ....
101
- ....
101
+ strategy.list List Python Strategies
102
+ strategy.copysamples
103
+ Copy Samples from SDK
104
+ strategy.create Create a new trading strategy
105
+ strategy.download Download one of your strategy and save it to a file
106
+ strategy.update Update strategy Python Code
107
+ strategy.backtest.start
108
+ Start backtest for strategy
109
+ strategy.backtest.stop
110
+ Stop backtest for strategy
111
+ strategy.backtest.result
112
+ Get backtest result, waiting if backtest is still
113
+ running
102
114
  exit Stop and Exit CLI
103
115
 
116
+ options:
117
+ -h, --help show this help message and exit
104
118
 
119
+ investfly-cli$
105
120
  ```
106
121
 
107
122
  **Test Installation**
@@ -123,7 +138,7 @@ Investfly-SDK comes with a starter strategy template and many sample strategies
123
138
  **Copy Samples**
124
139
 
125
140
  ```commandline
126
- investfly-cli$ copySamples
141
+ investfly-cli$ copysamples
127
142
  Samples copied to ./samples directory
128
143
  ```
129
144
 
@@ -135,10 +150,10 @@ But for now, we'll use the unmodified sample
135
150
  investfly-cli$ login -u <YOUR_USERNAME> -p <YOUR_PASSWORD>
136
151
  Session(username='xxxxx', clientId='xxxxxx-krfs61aa', clientToken='766fad47-3e1e-4f43-a77a-72a95a395fec')
137
152
 
138
- investfly-cli$ createStrategy -n MySmaCrossOverStrategy -f ./samples/strategies/SmaCrossOverStrategy.py
153
+ investfly-cli$ strategy.create -n MySmaCrossOverStrategy -f ./samples/strategies/SmaCrossOverStrategy.py
139
154
  Created strategy 83
140
155
 
141
- investfly-cli$ listStrategies
156
+ investfly-cli$ strategy.list
142
157
  {'strategyId': 83, 'strategyName': 'MySmaCrossOverStrategy'}
143
158
 
144
159
  ```
@@ -148,7 +163,7 @@ investfly-cli$ listStrategies
148
163
  Edit and update ./samples/strategies/SmaCrossOverStrategy.py as you like. For testing, change the `StandardSymbolsList.SP_100` to `StandardSymbolsList.SP_500` inside `getSecurityUniverseSelector` function.
149
164
 
150
165
  ```commandline
151
- investfly-cli$ updateStrategyCode --id 83 -f ./samples/strategies/SmaCrossOverStrategy.py
166
+ investfly-cli$ strategy.update --id 83 -f ./samples/strategies/SmaCrossOverStrategy.py
152
167
  Code Updated
153
168
  ```
154
169
 
@@ -168,6 +183,10 @@ When using the IDE, open `investfly` directory created above as a project with y
168
183
  Make sure that Python Interpreter is configured to the virtual environment `investfly/venv/bin/python` created above.
169
184
 
170
185
 
186
+ # API Docs
187
+
188
+ API Docs are published at https://www.investfly.com/guides/docs/index.html
189
+
171
190
  # Getting Help
172
191
  Please email [admin@investfly.com](admin@investfly.com) for any support or bug report
173
192
 
@@ -34,25 +34,40 @@ pip install investfly-sdk
34
34
 
35
35
  **Launch investfly-cli**
36
36
 
37
- Investfly SDK comes with a command line tool (CLI) called investfly-cli.
37
+ Install investfly-sdk also adds investfly-cli in your path inside the virtual environment. It can be launched simply by using `investfly-cli` command in the virtual env.
38
+
38
39
  ```commandline
39
- investfly-cli
40
+ (venv) user@host$ investfly-cli
40
41
 
41
42
  investfly-cli$ -h
42
- usage: investfly-cli [-h] {login,logout,listStrategies,copySamples,exit} ...
43
+ usage: investfly-cli [-h]
44
+ {login,logout,strategy.list,strategy.copysamples,strategy.create,strategy.download,strategy.update,strategy.backtest.start,strategy.backtest.stop,strategy.backtest.result,exit}
45
+ ...
43
46
 
44
47
  positional arguments:
45
- {login,logout,listStrategies,copySamples,exit}
48
+ {login,logout,strategy.list,strategy.copysamples,strategy.create,strategy.download,strategy.update,strategy.backtest.start,strategy.backtest.stop,strategy.backtest.result,exit}
46
49
  Available Commands
47
50
  login Login to Investfly
48
51
  logout Logout from Investfly
49
- listStrategies List Python Strategies
50
- copySamples Copy Samples from SDK
51
- ....
52
- ....
52
+ strategy.list List Python Strategies
53
+ strategy.copysamples
54
+ Copy Samples from SDK
55
+ strategy.create Create a new trading strategy
56
+ strategy.download Download one of your strategy and save it to a file
57
+ strategy.update Update strategy Python Code
58
+ strategy.backtest.start
59
+ Start backtest for strategy
60
+ strategy.backtest.stop
61
+ Stop backtest for strategy
62
+ strategy.backtest.result
63
+ Get backtest result, waiting if backtest is still
64
+ running
53
65
  exit Stop and Exit CLI
54
66
 
67
+ options:
68
+ -h, --help show this help message and exit
55
69
 
70
+ investfly-cli$
56
71
  ```
57
72
 
58
73
  **Test Installation**
@@ -74,7 +89,7 @@ Investfly-SDK comes with a starter strategy template and many sample strategies
74
89
  **Copy Samples**
75
90
 
76
91
  ```commandline
77
- investfly-cli$ copySamples
92
+ investfly-cli$ copysamples
78
93
  Samples copied to ./samples directory
79
94
  ```
80
95
 
@@ -86,10 +101,10 @@ But for now, we'll use the unmodified sample
86
101
  investfly-cli$ login -u <YOUR_USERNAME> -p <YOUR_PASSWORD>
87
102
  Session(username='xxxxx', clientId='xxxxxx-krfs61aa', clientToken='766fad47-3e1e-4f43-a77a-72a95a395fec')
88
103
 
89
- investfly-cli$ createStrategy -n MySmaCrossOverStrategy -f ./samples/strategies/SmaCrossOverStrategy.py
104
+ investfly-cli$ strategy.create -n MySmaCrossOverStrategy -f ./samples/strategies/SmaCrossOverStrategy.py
90
105
  Created strategy 83
91
106
 
92
- investfly-cli$ listStrategies
107
+ investfly-cli$ strategy.list
93
108
  {'strategyId': 83, 'strategyName': 'MySmaCrossOverStrategy'}
94
109
 
95
110
  ```
@@ -99,7 +114,7 @@ investfly-cli$ listStrategies
99
114
  Edit and update ./samples/strategies/SmaCrossOverStrategy.py as you like. For testing, change the `StandardSymbolsList.SP_100` to `StandardSymbolsList.SP_500` inside `getSecurityUniverseSelector` function.
100
115
 
101
116
  ```commandline
102
- investfly-cli$ updateStrategyCode --id 83 -f ./samples/strategies/SmaCrossOverStrategy.py
117
+ investfly-cli$ strategy.update --id 83 -f ./samples/strategies/SmaCrossOverStrategy.py
103
118
  Code Updated
104
119
  ```
105
120
 
@@ -119,6 +134,10 @@ When using the IDE, open `investfly` directory created above as a project with y
119
134
  Make sure that Python Interpreter is configured to the virtual environment `investfly/venv/bin/python` created above.
120
135
 
121
136
 
137
+ # API Docs
138
+
139
+ API Docs are published at https://www.investfly.com/guides/docs/index.html
140
+
122
141
  # Getting Help
123
142
  Please email [admin@investfly.com](admin@investfly.com) for any support or bug report
124
143
 
@@ -0,0 +1,5 @@
1
+ """
2
+ .. include:: ../README.md
3
+ """
4
+
5
+ __all__ = ['api', 'models', 'utils']
@@ -0,0 +1,24 @@
1
+ from typing import List, Dict, Any
2
+
3
+ from investfly.api.RestApiClient import RestApiClient
4
+ from investfly.models import IndicatorSpec
5
+
6
+
7
+ class IndicatorApiClient:
8
+
9
+ def __init__(self, restApiClient: RestApiClient) -> None:
10
+ self.restApiClient = restApiClient
11
+
12
+ def listIndicators(self) -> List[IndicatorSpec]:
13
+ indicatorsListDict = self.restApiClient.doGet('/indicator/list/custom')
14
+ result: List[IndicatorSpec] = []
15
+ for indicatorDict in indicatorsListDict:
16
+ result.append(IndicatorSpec.fromDict(indicatorDict))
17
+ return result
18
+
19
+ def getIndicatorCode(self, indicatorId: str) -> str:
20
+ return self.restApiClient.doGet(f'/indicator/custom/{indicatorId}/code')
21
+
22
+ def createUpdateIndicator(self, code: str) -> IndicatorSpec:
23
+ specDict: Dict[str, Any] = self.restApiClient.doPostCode('/indicator/custom/update', code)
24
+ return IndicatorSpec.fromDict(specDict)
@@ -1,5 +1,6 @@
1
1
  import datetime
2
2
 
3
+ from investfly.api.IndicatorApiClient import IndicatorApiClient
3
4
  from investfly.api.MarketDataApiClient import MarketDataApiClient
4
5
  from investfly.api.PortfolioApiClient import PortfolioApiClient
5
6
  from investfly.api.RestApiClient import RestApiClient
@@ -16,9 +17,12 @@ class InvestflyApiClient:
16
17
  def __init__(self, baseUrl: str = "https://api.investfly.com"):
17
18
  self.restApiClient = RestApiClient(baseUrl)
18
19
  self.marketApi = MarketDataApiClient(self.restApiClient)
19
- """Class used to make calls to /market endpoint """
20
+ """Class used to make calls to /marketdata and /symbol endpoint to get market and symbol data"""
20
21
  self.portfolioApi = PortfolioApiClient(self.restApiClient)
22
+ """Class used to make calls to /portfolio endpoint to get portfolio and brokerage account data"""
21
23
  self.strategyApi = StrategyApiClient(self.restApiClient)
24
+ """Class used to make calls to /strategy endpoint to operate on trading strategies"""
25
+ self.indicatorApi = IndicatorApiClient(self.restApiClient)
22
26
 
23
27
  def login(self, username, password) -> Session:
24
28
  """
@@ -4,6 +4,10 @@ from investfly.api.RestApiClient import RestApiClient
4
4
 
5
5
 
6
6
  class MarketDataApiClient:
7
+ """
8
+ MarketDataApiClient is intended to make calls to /marketdata and /symbol endpoint to get market and symbol data
9
+ from Investfly
10
+ """
7
11
 
8
12
  def __init__(self, restApiClient: RestApiClient) -> None:
9
13
  self.restApiClient = restApiClient
@@ -6,6 +6,10 @@ from investfly.models.PortfolioModels import Broker, Portfolio, Balances, OpenPo
6
6
 
7
7
 
8
8
  class PortfolioApiClient:
9
+ """
10
+ This class is intended to make REST API calls to /portfolio endpoint to get information on virtual portfolio
11
+ and connected brokerage account
12
+ """
9
13
 
10
14
  def __init__(self, restApiClient: RestApiClient) -> None:
11
15
  self.restApiClient = restApiClient
@@ -12,6 +12,11 @@ warnings.simplefilter("ignore")
12
12
 
13
13
  class RestApiClient:
14
14
 
15
+ """
16
+ Internal class to make REST API requests. Users of the SDK do not use this class directly.
17
+ Please use investfly.api.InvestflyApiClient` instead
18
+ """
19
+
15
20
  def __init__(self, baseUrl: str) -> None:
16
21
  self.headers: Dict[str, str] = {}
17
22
  self.baseUrl = baseUrl
@@ -1,16 +1,21 @@
1
1
  from typing import List, Any, Dict
2
2
  from investfly.api.RestApiClient import RestApiClient
3
- from investfly.models.StrategyModels import TradingStrategyModel
3
+ from investfly.models.CommonModels import Message
4
+ from investfly.models.StrategyModels import TradingStrategyModel, BacktestResult
4
5
 
5
6
 
6
7
  class StrategyApiClient:
7
8
 
9
+ """
10
+ Class used to make calls to /strategy endpoint to operate on trading strategies.
11
+ """
12
+
8
13
  def __init__(self, restApiClient: RestApiClient) -> None:
9
14
  self.restApiClient = restApiClient
10
15
 
11
- def getStrategies(self) -> List[TradingStrategyModel]:
16
+ def listStrategies(self) -> List[TradingStrategyModel]:
12
17
  strategiesList: List[Dict[str, Any]] = self.restApiClient.doGet('/strategy/list')
13
- strategiesList = list(filter(lambda jsonDict: jsonDict['type'] == 'CUSTOM', strategiesList))
18
+ strategiesList = list(filter(lambda jsonDict: jsonDict['type'] == 'SCRIPT', strategiesList))
14
19
  return list(map(lambda jsonDict: TradingStrategyModel.fromDict(jsonDict), strategiesList))
15
20
 
16
21
  def getStrategy(self, strategyId: int) -> TradingStrategyModel:
@@ -19,9 +24,22 @@ class StrategyApiClient:
19
24
 
20
25
  def createStrategy(self, strategyModel: TradingStrategyModel) -> TradingStrategyModel:
21
26
  strategyDict = strategyModel.toDict()
22
- strategyDict['type'] = 'CUSTOM'
27
+ strategyDict['type'] = 'SCRIPT'
23
28
  strategyDict = self.restApiClient.doPost('/strategy/create', strategyDict)
24
29
  return TradingStrategyModel.fromDict(strategyDict)
25
30
 
26
31
  def updateStrategyCode(self, strategyId: int, code: str) -> str:
27
- return self.restApiClient.doPostCode('/strategy/' + str(strategyId) + '/update/code', code)
32
+ return self.restApiClient.doPostCode('/strategy/' + str(strategyId) + '/update/code', code)
33
+
34
+ def startBacktest(self, strategyId: int) -> Message:
35
+ message = self.restApiClient.doPost(f'/backtest/{strategyId}/start', {})
36
+ return Message.fromDict(message)
37
+
38
+ def stopBacktest(self, strategyId: int) -> Message:
39
+ message = self.restApiClient.doPost(f'/backtest/{strategyId}/stop', {})
40
+ return Message.fromDict(message)
41
+
42
+ def getBacktestResult(self, strategyId: int) -> BacktestResult:
43
+ resultDict: Dict[str, Any] = self.restApiClient.doGet(f'/backtest/{strategyId}/result')
44
+ return BacktestResult.fromDict(resultDict)
45
+
@@ -0,0 +1,17 @@
1
+ """ This package contains REST API Client classes to make REST API calls to Investfly server.
2
+ The entry point is `investfly.api.InvestflyApiClient`
3
+
4
+ InvestflyApiClient has members to access specific endpoints such as strategyApi, portfolioApi etc
5
+
6
+ For now, only those API methods that are commonly used during in strategy development are added in the clients.
7
+
8
+ ```
9
+ from investfly.api.InvestflyApiClient import InvestflyApiClient
10
+ api = InvestflyApiClient()
11
+ api.login("<YOUR USERNAME>", "<YOUR PASSWORD>")
12
+ pythonStrategies = api.strategyApi.getStrategies()
13
+ print(pythonStrategies)
14
+ api.logout()
15
+ ```
16
+
17
+ """
@@ -0,0 +1,218 @@
1
+ import argparse
2
+ import pickle
3
+ import os.path
4
+ import time
5
+ from typing import List, cast
6
+
7
+ from investfly.api.InvestflyApiClient import InvestflyApiClient
8
+ from investfly.models import Session, IndicatorSpec
9
+ from investfly.models.StrategyModels import TradingStrategyModel, BacktestResult, BacktestStatus, BacktestResultStatus
10
+
11
+ from investfly import samples
12
+ import inspect
13
+ from pathlib import Path
14
+ import shutil
15
+
16
+
17
+ class InvestflyCli:
18
+
19
+ def __init__(self):
20
+ self.running: bool = True
21
+ self.investflyApi = InvestflyApiClient()
22
+
23
+
24
+ def __loginAction(self, args: argparse.Namespace) -> Session:
25
+ username = args.username
26
+ password = args.password
27
+ session = self.investflyApi.login(username, password)
28
+ return session
29
+
30
+ def __logoutAction(self, args: argparse.Namespace) -> None:
31
+ self.investflyApi.logout()
32
+
33
+ def __exitAction(self, args: argparse.Namespace|None) -> None:
34
+ if self.investflyApi.isLoggedIn():
35
+ self.investflyApi.logout()
36
+ self.running = False
37
+
38
+ def __copySamples(self, args: argparse.Namespace) -> str:
39
+ samplesPath = inspect.getfile(samples)
40
+ path = Path(samplesPath)
41
+ parentPath = path.parent
42
+ shutil.copytree(parentPath, './samples', dirs_exist_ok=True)
43
+ return "Samples copied to ./samples directory"
44
+
45
+
46
+ # ==== Strategy Command Handlers
47
+
48
+ def __listStrategies(self, args: argparse.Namespace) -> str:
49
+ strategies: List[TradingStrategyModel] = self.investflyApi.strategyApi.listStrategies()
50
+ strategiesDictList = list(map(lambda model: str({'strategyId': model.strategyId, 'strategyName': model.strategyName}), strategies))
51
+ return "\n".join(strategiesDictList)
52
+
53
+ def __createStrategy(self, args: argparse.Namespace) -> str:
54
+ name = args.name
55
+ path = args.file
56
+ with open(path, 'r') as source_file:
57
+ code = source_file.read()
58
+ tradingStrategyModel = TradingStrategyModel(strategyName=name, strategyDesc=name, pythonCode=code)
59
+ tradingStrategyModel = self.investflyApi.strategyApi.createStrategy(tradingStrategyModel)
60
+ return f'Created strategy {tradingStrategyModel.strategyId}'
61
+
62
+ def __downloadCode(self, args: argparse.Namespace) -> str:
63
+ strategyId = int(args.id)
64
+ path = args.file
65
+ strategyModel: TradingStrategyModel = self.investflyApi.strategyApi.getStrategy(strategyId)
66
+ code: str = cast(str, strategyModel.pythonCode)
67
+ with open(path, 'w') as out_file:
68
+ out_file.write(code)
69
+ return f"Strategy saved to {path}"
70
+
71
+ def __updateCode(self, args: argparse.Namespace) -> str:
72
+ strategyId = int(args.id)
73
+ path = args.file
74
+ with open(path, 'r') as source_file:
75
+ code = source_file.read()
76
+ self.investflyApi.strategyApi.updateStrategyCode(strategyId, code)
77
+ return 'Code Updated'
78
+
79
+ def __startBacktest(self, args: argparse.Namespace) -> str:
80
+ strategyId = int(args.id)
81
+ message = self.investflyApi.strategyApi.startBacktest(strategyId)
82
+ return str(message.toDict())
83
+
84
+ def __stopBacktest(self, args: argparse.Namespace) -> str:
85
+ strategyId = int(args.id)
86
+ message = self.investflyApi.strategyApi.stopBacktest(strategyId)
87
+ return str(message.toDict())
88
+
89
+ def __pollResults(self, args: argparse.Namespace) -> str:
90
+ strategyId = int(args.id)
91
+ backtestResult: BacktestResult = self.investflyApi.strategyApi.getBacktestResult(strategyId)
92
+ backtestStatus: BacktestResultStatus = backtestResult.status
93
+ print(str(backtestStatus.toDict()))
94
+ notFinished = backtestStatus.jobStatus == BacktestStatus.QUEUED or backtestStatus.jobStatus == BacktestStatus.INITIALIZING or backtestStatus.jobStatus == BacktestStatus.RUNNING
95
+ try:
96
+ while notFinished:
97
+ time.sleep(3)
98
+ backtestResult = self.investflyApi.strategyApi.getBacktestResult(strategyId)
99
+ backtestStatus = backtestResult.status
100
+ print(str(backtestStatus.toDict()))
101
+ notFinished = backtestStatus.jobStatus == BacktestStatus.QUEUED or backtestStatus.jobStatus == BacktestStatus.INITIALIZING or backtestStatus.jobStatus == BacktestStatus.RUNNING
102
+ except KeyboardInterrupt as e:
103
+ print("Interrupted")
104
+ pass
105
+ return str(backtestResult.performance)
106
+
107
+ # ==== INDICATOR COMMAND HANDLERS
108
+
109
+ def __listIndicators(self, args: argparse.Namespace) -> str:
110
+ indicators: List[IndicatorSpec] = self.investflyApi.indicatorApi.listIndicators()
111
+ idList = list(map(lambda spec: spec.indicatorId, indicators))
112
+ return str(idList)
113
+
114
+ def __createUpdateIndicator(self, args: argparse.Namespace):
115
+ path = args.file
116
+ with open(path, 'r') as source_file:
117
+ code = source_file.read()
118
+ self.investflyApi.indicatorApi.createUpdateIndicator(code)
119
+
120
+ def __downloadIndicatorCode(self, args: argparse.Namespace):
121
+ indicatorId = args.id
122
+ path = args.file
123
+ code = self.investflyApi.indicatorApi.getIndicatorCode(indicatorId)
124
+ with open(path, 'w') as out_file:
125
+ out_file.write(code)
126
+ return f"Indicator saved to {path}"
127
+
128
+ def runCli(self) -> None:
129
+ parser = argparse.ArgumentParser(prog="investfly-cli")
130
+ subparser = parser.add_subparsers(help='Available Commands', dest="command")
131
+
132
+ parser_login = subparser.add_parser('login', help='Login to Investfly')
133
+ parser_login.add_argument('-u', '--username', required=True, help='Input username')
134
+ parser_login.add_argument('-p', '--password', required=True, help='Input user password')
135
+ parser_login.set_defaults(func=self.__loginAction)
136
+
137
+ parser_logout = subparser.add_parser('logout', help="Logout from Investfly")
138
+ parser_logout.set_defaults(func=self.__logoutAction)
139
+
140
+ parser_copySamples = subparser.add_parser('copysamples', help='Copy Strategy and Indicator Samples from SDK')
141
+ parser_copySamples.set_defaults(func=self.__copySamples)
142
+
143
+ parser_exit = subparser.add_parser('exit', help='Stop and Exit CLI')
144
+ parser_exit.set_defaults(func = self.__exitAction)
145
+
146
+ # ======= STRATEGY COMMANDS ==========
147
+
148
+ parser_listStrategies = subparser.add_parser('strategy.list', help='List Python Strategies')
149
+ parser_listStrategies.set_defaults(func=self.__listStrategies)
150
+
151
+ parser_createStrategy = subparser.add_parser('strategy.create', help='Create a new trading strategy')
152
+ parser_createStrategy.add_argument('-n', '--name', required=True, help='Strategy Name')
153
+ parser_createStrategy.add_argument('-f', '--file', required=True, help='Python File Path relative to the project root that contains strategy code')
154
+ parser_createStrategy.set_defaults(func=self.__createStrategy)
155
+
156
+ parser_downloadStrategy = subparser.add_parser('strategy.download', help='Download one of your strategy python code and save it to a file')
157
+ parser_downloadStrategy.add_argument('-i', '--id', required=True, help='Strategy ID')
158
+ parser_downloadStrategy.add_argument('-f', '--file', required=True, help='File path (with file name) to save strategy python code')
159
+ parser_downloadStrategy.set_defaults(func=self.__downloadCode)
160
+
161
+ parser_updateCode = subparser.add_parser('strategy.update', help='Update strategy Python Code')
162
+ parser_updateCode.add_argument('-i', '--id', required=True, help='Strategy ID')
163
+ parser_updateCode.add_argument('-f', '--file', required=True, help='File path (with file name) that contains strategy code')
164
+ parser_updateCode.set_defaults(func=self.__updateCode)
165
+
166
+ parser_startBacktest = subparser.add_parser('strategy.backtest.start', help='Start backtest for strategy')
167
+ parser_startBacktest.add_argument('-i', '--id', required=True, help='Strategy ID')
168
+ parser_startBacktest.set_defaults(func=self.__startBacktest)
169
+
170
+ parser_stopBacktest = subparser.add_parser('strategy.backtest.stop', help='Stop backtest for strategy')
171
+ parser_stopBacktest.add_argument('-i', '--id', required=True, help='Strategy ID')
172
+ parser_stopBacktest.set_defaults(func=self.__stopBacktest)
173
+
174
+ parser_resultBacktest = subparser.add_parser('strategy.backtest.result', help='Get backtest result, waiting if backtest is still running')
175
+ parser_resultBacktest.add_argument('-i', '--id', required=True, help='Strategy ID')
176
+ parser_resultBacktest.set_defaults(func=self.__pollResults)
177
+
178
+ # ====== INDICATOR COMMANDS ====
179
+
180
+ parser_listIndicators = subparser.add_parser('indicator.list', help='List Custom Indicators')
181
+ parser_listIndicators.set_defaults(func=self.__listIndicators)
182
+
183
+ parser_downloadIndicator = subparser.add_parser('indicator.download', help='Download indicator python code and save it to a file')
184
+ parser_downloadIndicator.add_argument('-i', '--id', required=True, help='IndicatorId ID')
185
+ parser_downloadIndicator.add_argument('-f', '--file', required=True, help='File path (with file name) to save indicator python code')
186
+ parser_downloadIndicator.set_defaults(func=self.__downloadIndicatorCode)
187
+
188
+ parser_createUpdateIndicator = subparser.add_parser('indicator.update', help='Create or update indicator. Indicator ID is retried from ClassName')
189
+ parser_createUpdateIndicator.add_argument('-f', '--file', required=True, help='File path (with file name) that contains indicator code')
190
+ parser_createUpdateIndicator.set_defaults(func=self.__createUpdateIndicator)
191
+
192
+ while self.running:
193
+ try:
194
+ data = input("\ninvestfly-cli$ ")
195
+ args = parser.parse_args(data.split())
196
+ if args.command is None:
197
+ # When user hits Enter without any command
198
+ parser.print_help()
199
+ else:
200
+ result = args.func(args)
201
+ if result is not None:
202
+ print(result)
203
+ except SystemExit:
204
+ # System exit is caught because when -h is used, argparser displays help and exists the apputils with SystemExit
205
+ pass
206
+ except KeyboardInterrupt:
207
+ self.__exitAction(None)
208
+ except Exception as e:
209
+ print("Received exception " + str(e))
210
+
211
+
212
+
213
+ def main():
214
+ investflyCli = InvestflyCli()
215
+ investflyCli.runCli()
216
+
217
+ if __name__ == '__main__':
218
+ main()
@@ -10,6 +10,9 @@ from investfly.models.ModelUtils import ModelUtils
10
10
 
11
11
  @dataclass
12
12
  class DatedValue:
13
+ """
14
+ Data Container class to hold Time,Value for timed numeric values
15
+ """
13
16
  date: datetime
14
17
  value: float | int
15
18
 
@@ -25,6 +28,11 @@ class DatedValue:
25
28
 
26
29
 
27
30
  class TimeUnit(str, Enum):
31
+
32
+ """
33
+ TimeUnit Enum (MINUTES, HOURS, DAYS)
34
+ """
35
+
28
36
  MINUTES = "MINUTES"
29
37
  HOURS = "HOURS"
30
38
  DAYS = "DAYS"
@@ -38,6 +46,7 @@ class TimeUnit(str, Enum):
38
46
 
39
47
  @dataclass
40
48
  class TimeDelta:
49
+ """ TimeDelta container class similar to python timedelta """
41
50
  value: int
42
51
  unit: TimeUnit
43
52
 
@@ -58,9 +67,31 @@ class TimeDelta:
58
67
  return TimeDelta(json_dict['value'], TimeUnit[json_dict['unit']])
59
68
 
60
69
 
70
+ class MessageType(str, Enum):
71
+ ERROR = "ERROR"
72
+ SUCCESS = "SUCCESS"
73
+ WARN = "WARN"
74
+
75
+ @dataclass
76
+ class Message:
77
+ type: MessageType
78
+ message: str
79
+
80
+ def toDict(self) -> Dict[str, Any]:
81
+ return self.__dict__.copy()
82
+
83
+ @staticmethod
84
+ def fromDict(json_dict: Dict[str, Any]) -> Message:
85
+ return Message(MessageType[json_dict['type']], json_dict['message'])
86
+
87
+
88
+
61
89
 
62
90
  @dataclass
63
91
  class Session:
92
+
93
+ """ Class that represents logged in user session with the Investfly server """
94
+
64
95
  username: str
65
96
  clientId: str
66
97
  clientToken: str