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.
- {investfly_sdk-1.1/investfly_sdk.egg-info → investfly_sdk-1.3}/PKG-INFO +32 -13
- {investfly_sdk-1.1 → investfly_sdk-1.3}/README.md +31 -12
- investfly_sdk-1.3/investfly/__init__.py +5 -0
- investfly_sdk-1.3/investfly/api/IndicatorApiClient.py +24 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/InvestflyApiClient.py +5 -1
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/MarketDataApiClient.py +4 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/PortfolioApiClient.py +4 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/RestApiClient.py +5 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/api/StrategyApiClient.py +23 -5
- investfly_sdk-1.3/investfly/api/__init__.py +17 -0
- investfly_sdk-1.3/investfly/cli/InvestflyCli.py +218 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/CommonModels.py +31 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/Indicator.py +89 -14
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/MarketData.py +13 -2
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/MarketDataIds.py +2 -1
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/PortfolioModels.py +11 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/SecurityUniverseSelector.py +9 -1
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/StrategyModels.py +41 -2
- investfly_sdk-1.3/investfly/models/TradingStrategy.py +110 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/__init__.py +9 -1
- {investfly_sdk-1.1 → investfly_sdk-1.3/investfly_sdk.egg-info}/PKG-INFO +32 -13
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/SOURCES.txt +1 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/pyproject.toml +1 -1
- investfly_sdk-1.1/investfly/__init__.py +0 -3
- investfly_sdk-1.1/investfly/api/__init__.py +0 -3
- investfly_sdk-1.1/investfly/cli/InvestflyCli.py +0 -194
- investfly_sdk-1.1/investfly/models/TradingStrategy.py +0 -59
- {investfly_sdk-1.1 → investfly_sdk-1.3}/LICENSE.txt +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/cli/__init__.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/ModelUtils.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/models/SecurityFilterModels.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/__init__.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/IndicatorTemplate.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/NewsSentiment.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/RsiOfSma.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/SmaEmaAverage.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/indicators/__init__.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/strategies/SmaCrossOverStrategy.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/strategies/SmaCrossOverTemplate.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/samples/strategies/__init__.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/utils/CommonUtils.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/utils/PercentBasedPortfolioAllocator.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly/utils/__init__.py +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/dependency_links.txt +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/entry_points.txt +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/requires.txt +0 -0
- {investfly_sdk-1.1 → investfly_sdk-1.3}/investfly_sdk.egg-info/top_level.txt +0 -0
- {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.
|
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
|
-
|
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]
|
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,
|
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
|
-
|
99
|
-
|
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$
|
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$
|
153
|
+
investfly-cli$ strategy.create -n MySmaCrossOverStrategy -f ./samples/strategies/SmaCrossOverStrategy.py
|
139
154
|
Created strategy 83
|
140
155
|
|
141
|
-
investfly-cli$
|
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$
|
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
|
-
|
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]
|
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,
|
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
|
-
|
50
|
-
|
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$
|
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$
|
104
|
+
investfly-cli$ strategy.create -n MySmaCrossOverStrategy -f ./samples/strategies/SmaCrossOverStrategy.py
|
90
105
|
Created strategy 83
|
91
106
|
|
92
|
-
investfly-cli$
|
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$
|
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,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 /
|
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.
|
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
|
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'] == '
|
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'] = '
|
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
|