investfly-sdk 1.1__py3-none-any.whl → 1.3__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.
- investfly/__init__.py +3 -1
- investfly/api/IndicatorApiClient.py +24 -0
- investfly/api/InvestflyApiClient.py +5 -1
- investfly/api/MarketDataApiClient.py +4 -0
- investfly/api/PortfolioApiClient.py +4 -0
- investfly/api/RestApiClient.py +5 -0
- investfly/api/StrategyApiClient.py +23 -5
- investfly/api/__init__.py +15 -1
- investfly/cli/InvestflyCli.py +115 -91
- investfly/models/CommonModels.py +31 -0
- investfly/models/Indicator.py +89 -14
- investfly/models/MarketData.py +13 -2
- investfly/models/MarketDataIds.py +2 -1
- investfly/models/PortfolioModels.py +11 -0
- investfly/models/SecurityUniverseSelector.py +9 -1
- investfly/models/StrategyModels.py +41 -2
- investfly/models/TradingStrategy.py +67 -16
- investfly/models/__init__.py +9 -1
- {investfly_sdk-1.1.dist-info → investfly_sdk-1.3.dist-info}/METADATA +32 -13
- investfly_sdk-1.3.dist-info/RECORD +39 -0
- {investfly_sdk-1.1.dist-info → investfly_sdk-1.3.dist-info}/WHEEL +1 -1
- investfly_sdk-1.1.dist-info/RECORD +0 -38
- {investfly_sdk-1.1.dist-info → investfly_sdk-1.3.dist-info}/LICENSE.txt +0 -0
- {investfly_sdk-1.1.dist-info → investfly_sdk-1.3.dist-info}/entry_points.txt +0 -0
- {investfly_sdk-1.1.dist-info → investfly_sdk-1.3.dist-info}/top_level.txt +0 -0
investfly/__init__.py
CHANGED
@@ -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
|
investfly/api/RestApiClient.py
CHANGED
@@ -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
|
+
|
investfly/api/__init__.py
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
-
""" This package contains REST API Client classes to make REST API calls to Investfly
|
1
|
+
""" This package contains REST API Client classes to make REST API calls to Investfly server.
|
2
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
|
+
|
3
17
|
"""
|
investfly/cli/InvestflyCli.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
import argparse
|
2
2
|
import pickle
|
3
3
|
import os.path
|
4
|
-
|
4
|
+
import time
|
5
|
+
from typing import List, cast
|
5
6
|
|
6
7
|
from investfly.api.InvestflyApiClient import InvestflyApiClient
|
7
|
-
from investfly.models import Session
|
8
|
-
from investfly.models.StrategyModels import TradingStrategyModel
|
8
|
+
from investfly.models import Session, IndicatorSpec
|
9
|
+
from investfly.models.StrategyModels import TradingStrategyModel, BacktestResult, BacktestStatus, BacktestResultStatus
|
9
10
|
|
10
11
|
from investfly import samples
|
11
12
|
import inspect
|
@@ -29,10 +30,10 @@ class InvestflyCli:
|
|
29
30
|
def __logoutAction(self, args: argparse.Namespace) -> None:
|
30
31
|
self.investflyApi.logout()
|
31
32
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def __exitAction(self, args: argparse.Namespace|None) -> None:
|
34
|
+
if self.investflyApi.isLoggedIn():
|
35
|
+
self.investflyApi.logout()
|
36
|
+
self.running = False
|
36
37
|
|
37
38
|
def __copySamples(self, args: argparse.Namespace) -> str:
|
38
39
|
samplesPath = inspect.getfile(samples)
|
@@ -41,6 +42,14 @@ class InvestflyCli:
|
|
41
42
|
shutil.copytree(parentPath, './samples', dirs_exist_ok=True)
|
42
43
|
return "Samples copied to ./samples directory"
|
43
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
|
+
|
44
53
|
def __createStrategy(self, args: argparse.Namespace) -> str:
|
45
54
|
name = args.name
|
46
55
|
path = args.file
|
@@ -50,6 +59,15 @@ class InvestflyCli:
|
|
50
59
|
tradingStrategyModel = self.investflyApi.strategyApi.createStrategy(tradingStrategyModel)
|
51
60
|
return f'Created strategy {tradingStrategyModel.strategyId}'
|
52
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
|
+
|
53
71
|
def __updateCode(self, args: argparse.Namespace) -> str:
|
54
72
|
strategyId = int(args.id)
|
55
73
|
path = args.file
|
@@ -58,10 +76,54 @@ class InvestflyCli:
|
|
58
76
|
self.investflyApi.strategyApi.updateStrategyCode(strategyId, code)
|
59
77
|
return 'Code Updated'
|
60
78
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
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}"
|
65
127
|
|
66
128
|
def runCli(self) -> None:
|
67
129
|
parser = argparse.ArgumentParser(prog="investfly-cli")
|
@@ -75,25 +137,57 @@ class InvestflyCli:
|
|
75
137
|
parser_logout = subparser.add_parser('logout', help="Logout from Investfly")
|
76
138
|
parser_logout.set_defaults(func=self.__logoutAction)
|
77
139
|
|
78
|
-
|
79
|
-
parser_listStrategies.set_defaults(func=self.__listStrategies)
|
80
|
-
|
81
|
-
parser_copySamples = subparser.add_parser('copySamples', help='Copy Samples from SDK')
|
140
|
+
parser_copySamples = subparser.add_parser('copysamples', help='Copy Strategy and Indicator Samples from SDK')
|
82
141
|
parser_copySamples.set_defaults(func=self.__copySamples)
|
83
142
|
|
84
|
-
|
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')
|
85
152
|
parser_createStrategy.add_argument('-n', '--name', required=True, help='Strategy Name')
|
86
153
|
parser_createStrategy.add_argument('-f', '--file', required=True, help='Python File Path relative to the project root that contains strategy code')
|
87
154
|
parser_createStrategy.set_defaults(func=self.__createStrategy)
|
88
155
|
|
89
|
-
|
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')
|
90
162
|
parser_updateCode.add_argument('-i', '--id', required=True, help='Strategy ID')
|
91
|
-
parser_updateCode.add_argument('-f', '--file', required=True, help='
|
163
|
+
parser_updateCode.add_argument('-f', '--file', required=True, help='File path (with file name) that contains strategy code')
|
92
164
|
parser_updateCode.set_defaults(func=self.__updateCode)
|
93
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)
|
94
169
|
|
95
|
-
|
96
|
-
|
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)
|
97
191
|
|
98
192
|
while self.running:
|
99
193
|
try:
|
@@ -120,75 +214,5 @@ def main():
|
|
120
214
|
investflyCli = InvestflyCli()
|
121
215
|
investflyCli.runCli()
|
122
216
|
|
123
|
-
# # Check to see if user already has a session active
|
124
|
-
# if os.path.exists('/tmp/loginSession'):
|
125
|
-
# tmpfile = open('/tmp/loginSession', 'rb')
|
126
|
-
# restApi = pickle.load(tmpfile)
|
127
|
-
# tmpfile.close()
|
128
|
-
# else:
|
129
|
-
# restApi = StrategyApiClient("https://api.investfly.com")
|
130
|
-
#
|
131
|
-
# # CLI Commands
|
132
|
-
# parser = argparse.ArgumentParser()
|
133
|
-
# subparser = parser.add_subparsers(dest='command')
|
134
|
-
#
|
135
|
-
# parser_login = subparser.add_parser('login', help='Login to Investfly')
|
136
|
-
# parser_login.add_argument('-u', '--username', required='true', help='Input username')
|
137
|
-
# parser_login.add_argument('-p', '--password', required='true', help='Input user password')
|
138
|
-
#
|
139
|
-
# subparser.add_parser('whoami', help='View your user information')
|
140
|
-
#
|
141
|
-
# subparser.add_parser('logout', help='Logout')
|
142
|
-
#
|
143
|
-
# parser_strategy = subparser.add_parser('strategy', help='View all your current strategies')
|
144
|
-
# parser_strategy.add_argument('-id', help='Provide the Strategy ID')
|
145
|
-
# parser_strategy.add_argument('-o', '--output-file', help='Provide a location to save the output file of a custom strategy (Requires Strategy ID)')
|
146
|
-
# parser_strategy.add_argument('-u', '--update-file', help='Provide the file location to update the script of a custom strategy (Requires Strategy ID)')
|
147
|
-
#
|
148
|
-
# args = parser.parse_args()
|
149
|
-
#
|
150
|
-
# # If user is logging in, create a new login session and save it locally
|
151
|
-
# if args.command == 'login':
|
152
|
-
# restApi.login(args.username, args.password)
|
153
|
-
# tmpFile = open('/tmp/loginSession', 'ab')
|
154
|
-
# pickle.dump(restApi, tmpFile)
|
155
|
-
# tmpFile.close()
|
156
|
-
#
|
157
|
-
# elif args.command == 'logout':
|
158
|
-
# restApi.logout()
|
159
|
-
# os.remove('/tmp/loginSession')
|
160
|
-
#
|
161
|
-
# elif args.command == 'whoami':
|
162
|
-
# restApi.getStatus()
|
163
|
-
#
|
164
|
-
# elif args.command == 'strategy':
|
165
|
-
# if all(e is None for e in [args.id, args.output_file, args.update_file]):
|
166
|
-
# restApi.getStrategies()
|
167
|
-
# elif (args.output_file is not None) and (args.id is not None):
|
168
|
-
# try:
|
169
|
-
# code = restApi.saveStrategy(args.id)
|
170
|
-
# file = open(args.output_file, "w")
|
171
|
-
# file.write(code)
|
172
|
-
# file.close()
|
173
|
-
# print('File successfully saved to '+args.output_file)
|
174
|
-
# except Exception as e:
|
175
|
-
# print(e)
|
176
|
-
# elif (args.update_file is not None) and (args.id is not None):
|
177
|
-
# try:
|
178
|
-
# file = open(args.update_file, "r")
|
179
|
-
# code = file.read()
|
180
|
-
# restApi.updateStrategy(args.id, code)
|
181
|
-
# file.close()
|
182
|
-
# except Exception as e:
|
183
|
-
# print(e)
|
184
|
-
# else:
|
185
|
-
# parser_strategy.print_help()
|
186
|
-
#
|
187
|
-
#
|
188
|
-
# else:
|
189
|
-
# parser.print_help()
|
190
|
-
|
191
|
-
|
192
217
|
if __name__ == '__main__':
|
193
|
-
|
194
|
-
print(inspect.getfile(samples))
|
218
|
+
main()
|
investfly/models/CommonModels.py
CHANGED
@@ -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
|
investfly/models/Indicator.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from abc import ABC, abstractmethod
|
2
4
|
from dataclasses import dataclass
|
3
5
|
from enum import Enum
|
@@ -9,6 +11,9 @@ from investfly.models.SecurityFilterModels import DataSource, DataParam
|
|
9
11
|
|
10
12
|
|
11
13
|
class ParamType(str, Enum):
|
14
|
+
|
15
|
+
""" Indicator Param Type """
|
16
|
+
|
12
17
|
INTEGER = 'INTEGER'
|
13
18
|
FLOAT = 'FLOAT'
|
14
19
|
BOOLEAN = 'BOOLEAN'
|
@@ -24,12 +29,22 @@ class ParamType(str, Enum):
|
|
24
29
|
|
25
30
|
@dataclass
|
26
31
|
class IndicatorParamSpec:
|
32
|
+
|
33
|
+
""" Class that represents Indicator Parameter Specification """
|
34
|
+
|
27
35
|
paramType: ParamType
|
36
|
+
""" Parameter Type (INTEGER, FLOAT, BOOLEAN, STRING, BARINTERVAL) """
|
28
37
|
|
29
38
|
required: bool = True
|
39
|
+
""" Whether this parameter is required or optional"""
|
40
|
+
|
30
41
|
# The default value here is just a "hint" to the UI to auto-fill indicator value with reasonable default
|
31
42
|
defaultValue: Any | None = None
|
43
|
+
""" The default value for the parameter to auto-populate mainly in UI """
|
44
|
+
|
32
45
|
options: List[Any] | None = None
|
46
|
+
""" Valid value options (if any). If specified, then in the UI, this parameter renders as a dropdown select list.
|
47
|
+
If left as None, parameter renders and freeform input text field. """
|
33
48
|
|
34
49
|
PERIOD_VALUES: ClassVar[List[int]] = [2, 3, 4, 5, 8, 9, 10, 12, 14, 15, 20, 26, 30, 40, 50, 60, 70, 80, 90, 100, 120, 130, 140, 150, 180, 200, 250, 300]
|
35
50
|
|
@@ -37,10 +52,20 @@ class IndicatorParamSpec:
|
|
37
52
|
d = self.__dict__.copy()
|
38
53
|
return d
|
39
54
|
|
55
|
+
@staticmethod
|
56
|
+
def fromDict(json_dict: Dict[str, Any]) -> IndicatorParamSpec:
|
57
|
+
paramType = ParamType[json_dict['paramType']]
|
58
|
+
required = json_dict['required']
|
59
|
+
defaultValue = json_dict.get('defaultValue')
|
60
|
+
options = json_dict.get('options')
|
61
|
+
return IndicatorParamSpec(paramType, required, defaultValue, options)
|
62
|
+
|
40
63
|
|
41
64
|
class IndicatorValueType(str, Enum):
|
42
|
-
|
43
|
-
|
65
|
+
"""
|
66
|
+
Indicator ValueType can possibly used by Investfly to validate expression and optimize experience for users
|
67
|
+
For e.g, all Indicators of same valueType can be plotted in the same y-axis
|
68
|
+
"""
|
44
69
|
|
45
70
|
PRICE = "PRICE"
|
46
71
|
|
@@ -92,28 +117,85 @@ class IndicatorSpec:
|
|
92
117
|
jsonDict['params'] = paramsDict
|
93
118
|
return jsonDict
|
94
119
|
|
120
|
+
@staticmethod
|
121
|
+
def fromDict(json_dict: Dict[str, Any]) -> IndicatorSpec:
|
122
|
+
name = json_dict['name']
|
123
|
+
indicatorSpec: IndicatorSpec = IndicatorSpec(name)
|
124
|
+
indicatorSpec.indicatorId = json_dict['indicatorId']
|
125
|
+
indicatorSpec.description = json_dict['description']
|
126
|
+
indicatorSpec.valueType = IndicatorValueType[json_dict['valueType']]
|
127
|
+
for key, value in json_dict['params'].items():
|
128
|
+
indicatorSpec.params[key] = IndicatorParamSpec.fromDict(value)
|
129
|
+
return indicatorSpec
|
130
|
+
|
95
131
|
def __str__(self):
|
96
132
|
return str(self.__dict__)
|
97
133
|
|
98
134
|
|
99
135
|
class Indicator(ABC):
|
136
|
+
"""
|
137
|
+
The primary class to implement a custom Indicator. A Custom Indicator is like standard indicator (e.g SMA, RSI)
|
138
|
+
and can be used in any place that standard indicator can be used (e.g screener, charts, strategy etc)
|
139
|
+
Investfly comes with a set of standard indicators. If you find that the indicator you want is not supported
|
140
|
+
or you can a small variation (e.g SMA but with using Heikin Ashi Candles), then you can use this function
|
141
|
+
"""
|
100
142
|
|
101
143
|
@abstractmethod
|
102
144
|
def getIndicatorSpec(self) -> IndicatorSpec:
|
103
|
-
|
104
|
-
|
145
|
+
"""
|
146
|
+
Return IndicatorSpec with name, description, required params, and valuetype
|
147
|
+
See IndicatorSpec abstract class for more details
|
148
|
+
:return: `IndicatorSpec`
|
149
|
+
"""
|
150
|
+
|
105
151
|
pass
|
106
152
|
|
107
153
|
def getDataSourceType(self) -> DataSource:
|
108
|
-
|
109
|
-
|
154
|
+
"""
|
155
|
+
Return the DataSource that this indicator is based on. Possible values are:
|
156
|
+
DataSource.BARS, DataSource.QUOTE, DataSource.NEWS, DataSource.FINANCIAL
|
157
|
+
:return: `investfly.models.SecurityFilterModels.DataSource`
|
158
|
+
"""
|
159
|
+
|
110
160
|
return DataSource.BARS
|
111
161
|
|
112
162
|
@abstractmethod
|
113
163
|
def computeSeries(self, params: Dict[str, Any], data: List[Any]) -> List[DatedValue]:
|
114
|
-
|
164
|
+
"""
|
165
|
+
Compute indicator series based on provided input timed data series and parameter values.
|
166
|
+
This function must return List of indicator values instead of only the most recent single value because indicator
|
167
|
+
series is required to plot in the price chart and also to use in backtest
|
168
|
+
The timestamps in the `investfly.models.CommonModels.DatedValue` must correspond to timestamps in input data
|
169
|
+
The length of input data depends on context (e.g is this indicator being evaluated for backtest or screener?)
|
170
|
+
and `dataCountToComputeCurrentValue` function below
|
171
|
+
|
172
|
+
:param params: User supplied indicator parameter values. The keys match the keys from `IndicatorSpec.params`
|
173
|
+
:param data: List of timed data values as specified in `Indicator.getDataSourceType`.
|
174
|
+
:return: List of `investfly.models.CommonModels.DatedValue` representing indicator values for each timeunit
|
175
|
+
"""
|
176
|
+
|
115
177
|
pass
|
116
178
|
|
179
|
+
def dataCountToComputeCurrentValue(self, params: Dict[str, Any]) -> int | None:
|
180
|
+
"""
|
181
|
+
When this indicator is used in screener and trading strategy when is evaluated in real-time, only
|
182
|
+
the "current" value of the indicator is required. The historical values are NOT required. Therefore,
|
183
|
+
when the system calls `computeSeries` above with all available data (e.g 10 years of historical bars),
|
184
|
+
then it is un-necessarily slow and wasteful. This function is used to control the size of input data
|
185
|
+
that will be passed to `computeSeries` method above.
|
186
|
+
|
187
|
+
The default implementation tries to make the best guess, but override as needed
|
188
|
+
|
189
|
+
:param params: User supplied input parameter values
|
190
|
+
:return: integer representing how many input data points are required to compute the 'current' indicator value.
|
191
|
+
For e.g, if this was SMA indicator with period=5, then you should return 5
|
192
|
+
"""
|
193
|
+
total = 0
|
194
|
+
for key, value in params.items():
|
195
|
+
if isinstance(value, int) and key != DataParam.COUNT:
|
196
|
+
total += value
|
197
|
+
return max(total, 1)
|
198
|
+
|
117
199
|
def validateParams(self, paramVals: Dict[str, Any]):
|
118
200
|
spec: IndicatorSpec = self.getIndicatorSpec()
|
119
201
|
for paramName, paramSpec in spec.params.items():
|
@@ -139,13 +221,6 @@ class Indicator(ABC):
|
|
139
221
|
raise Exception(f"Param {paramName} provided value {paramVal} is not one of the allowed value")
|
140
222
|
|
141
223
|
|
142
|
-
def dataCountToComputeCurrentValue(self, params: Dict[str, Any]) -> int | None:
|
143
|
-
total = 0
|
144
|
-
for key, value in params.items():
|
145
|
-
if isinstance(value, int) and key != DataParam.COUNT:
|
146
|
-
total += value
|
147
|
-
return max(total, 1)
|
148
|
-
|
149
224
|
def addStandardParamsToDef(self, indicatorDef: IndicatorSpec):
|
150
225
|
# Note that setting default values for optional params impact alias/key generation for indicator instances (e.g SMA_5_1MIN_1)
|
151
226
|
# Hence, its better to leave them as None
|
investfly/models/MarketData.py
CHANGED
@@ -17,6 +17,10 @@ def formatDatetime(dt: datetime) -> str:
|
|
17
17
|
|
18
18
|
|
19
19
|
class SecurityType(str, Enum):
|
20
|
+
"""
|
21
|
+
Enum representing Security Type (STOCK, ETF)
|
22
|
+
"""
|
23
|
+
|
20
24
|
STOCK = "STOCK"
|
21
25
|
ETF = "ETF"
|
22
26
|
|
@@ -31,8 +35,15 @@ class SecurityType(str, Enum):
|
|
31
35
|
|
32
36
|
@dataclass(frozen=True)
|
33
37
|
class Security:
|
38
|
+
"""
|
39
|
+
Class representing a security instrument that is traded in the market
|
40
|
+
"""
|
41
|
+
|
34
42
|
symbol: str
|
43
|
+
""" Security Symbol """
|
44
|
+
|
35
45
|
securityType: SecurityType
|
46
|
+
""" Security Type """
|
36
47
|
|
37
48
|
@staticmethod
|
38
49
|
def createStock(symbol: str):
|
@@ -48,6 +59,7 @@ class Security:
|
|
48
59
|
|
49
60
|
@dataclass
|
50
61
|
class Quote:
|
62
|
+
""" Class representing Price Quote """
|
51
63
|
symbol: str
|
52
64
|
date: datetime
|
53
65
|
lastPrice: float
|
@@ -122,9 +134,8 @@ class Quote:
|
|
122
134
|
return bar
|
123
135
|
|
124
136
|
|
125
|
-
|
126
|
-
|
127
137
|
class BarInterval(str, Enum):
|
138
|
+
""" Enum to represent BarInterval """
|
128
139
|
ONE_MINUTE = "ONE_MINUTE"
|
129
140
|
FIVE_MINUTE = "FIVE_MINUTE"
|
130
141
|
FIFTEEN_MINUTE = "FIFTEEN_MINUTE"
|
@@ -15,7 +15,7 @@ class QuoteField(str, Enum):
|
|
15
15
|
DAY_CHANGE_OPEN = "DAY_CHANGE_OPEN"
|
16
16
|
|
17
17
|
class FinancialField(str, Enum):
|
18
|
-
|
18
|
+
"""Financial Fields supported by Invesfly"""
|
19
19
|
|
20
20
|
# SHARE
|
21
21
|
OUTSTANDING_SHARES = "OUTSTANDING_SHARES"
|
@@ -63,6 +63,7 @@ class FinancialField(str, Enum):
|
|
63
63
|
|
64
64
|
|
65
65
|
class StandardIndicatorId(str, Enum):
|
66
|
+
""" Technical Indicator supported by Investfly"""
|
66
67
|
|
67
68
|
# Works by default on DAILY bars
|
68
69
|
AVGVOL = "AVGVOLUME"
|
@@ -10,6 +10,8 @@ from investfly.models.MarketData import Security
|
|
10
10
|
from investfly.models.ModelUtils import ModelUtils
|
11
11
|
|
12
12
|
class PositionType(str, Enum):
|
13
|
+
""" PositionType Enum """
|
14
|
+
|
13
15
|
LONG = "LONG",
|
14
16
|
SHORT = "SHORT"
|
15
17
|
CLOSE = "CLOSE"
|
@@ -22,6 +24,8 @@ class PositionType(str, Enum):
|
|
22
24
|
|
23
25
|
|
24
26
|
class TradeType(str, Enum):
|
27
|
+
"""Trade Type Enum """
|
28
|
+
|
25
29
|
BUY = "BUY"
|
26
30
|
SELL = "SELL"
|
27
31
|
SHORT = "SHORT"
|
@@ -35,6 +39,8 @@ class TradeType(str, Enum):
|
|
35
39
|
|
36
40
|
|
37
41
|
class OrderType(str, Enum):
|
42
|
+
"""Order Type Enum """
|
43
|
+
|
38
44
|
MARKET_ORDER = "MARKET_ORDER"
|
39
45
|
LIMIT_ORDER = "LIMIT_ORDER"
|
40
46
|
STOP_ORDER = "STOP_ORDER"
|
@@ -43,6 +49,9 @@ class OrderType(str, Enum):
|
|
43
49
|
|
44
50
|
|
45
51
|
class Broker(str, Enum):
|
52
|
+
|
53
|
+
"""Broker Type Enum"""
|
54
|
+
|
46
55
|
INVESTFLY = "INVESTFLY"
|
47
56
|
TRADIER = "TRADIER"
|
48
57
|
TDAMERITRADE = "TDAMERITRADE"
|
@@ -57,6 +66,7 @@ class Broker(str, Enum):
|
|
57
66
|
|
58
67
|
@dataclass
|
59
68
|
class TradeOrder:
|
69
|
+
""" A class that represents a Trade Order """
|
60
70
|
security: Security
|
61
71
|
tradeType: TradeType
|
62
72
|
orderType: OrderType = OrderType.MARKET_ORDER
|
@@ -72,6 +82,7 @@ class TradeOrder:
|
|
72
82
|
|
73
83
|
@dataclass
|
74
84
|
class OrderStatus:
|
85
|
+
""" Trade Order Status"""
|
75
86
|
orderId: int
|
76
87
|
status: str
|
77
88
|
message: str | None = None
|
@@ -128,10 +128,18 @@ class FinancialQuery:
|
|
128
128
|
|
129
129
|
@dataclass
|
130
130
|
class SecurityUniverseSelector:
|
131
|
+
"""
|
132
|
+
This class is used to specify the set of stocks to use in trading strategy.
|
133
|
+
You can pick one of the standard list (e.g SP100) that we provide, provide your own list with comma separated symbols list,
|
134
|
+
or provide a query based on fundamental metrics like MarketCap, PE Ratio etc.
|
135
|
+
"""
|
131
136
|
universeType: SecurityUniverseType
|
137
|
+
"""The approach used to specify the stocks. Depending on the universeType, one of the attribute below must be specified"""
|
138
|
+
|
132
139
|
standardList: StandardSymbolsList | None = None
|
133
|
-
|
140
|
+
"Standard Symbol List (i.e SP500, SP100). Required if `universeType` is set to `STANDARD_LIST`"
|
134
141
|
|
142
|
+
customList: CustomSecurityList | None = None
|
135
143
|
financialQuery: FinancialQuery | None = None
|
136
144
|
|
137
145
|
@staticmethod
|
@@ -10,7 +10,7 @@ from typing import Dict, Any, List
|
|
10
10
|
from investfly.models.CommonModels import TimeDelta
|
11
11
|
from investfly.models.MarketData import Security
|
12
12
|
from investfly.models.ModelUtils import ModelUtils
|
13
|
-
from investfly.models.PortfolioModels import PositionType, Portfolio, TradeOrder
|
13
|
+
from investfly.models.PortfolioModels import PositionType, Portfolio, TradeOrder, PortfolioPerformance
|
14
14
|
from investfly.models.SecurityFilterModels import DataParam
|
15
15
|
|
16
16
|
|
@@ -143,4 +143,43 @@ class TradingStrategyModel:
|
|
143
143
|
|
144
144
|
def toDict(self) -> Dict[str, Any]:
|
145
145
|
dict = self.__dict__.copy()
|
146
|
-
return dict
|
146
|
+
return dict
|
147
|
+
|
148
|
+
|
149
|
+
class BacktestStatus(str, Enum):
|
150
|
+
NOT_STARTED = "NOT_STARTED",
|
151
|
+
QUEUED = "QUEUED",
|
152
|
+
INITIALIZING = "INITIALIZING",
|
153
|
+
RUNNING = "RUNNING"
|
154
|
+
COMPLETE = "COMPLETE"
|
155
|
+
ERROR = "ERROR"
|
156
|
+
|
157
|
+
@dataclass
|
158
|
+
class BacktestResultStatus:
|
159
|
+
jobStatus: BacktestStatus
|
160
|
+
percentComplete: int
|
161
|
+
|
162
|
+
def toDict(self) -> Dict[str, Any]:
|
163
|
+
dict = self.__dict__.copy()
|
164
|
+
return dict
|
165
|
+
|
166
|
+
@staticmethod
|
167
|
+
def fromDict(json_dict: Dict[str, Any]) -> BacktestResultStatus:
|
168
|
+
status = BacktestStatus[json_dict['jobStatus']]
|
169
|
+
percentComplete = json_dict['percentComplete']
|
170
|
+
return BacktestResultStatus(status, percentComplete)
|
171
|
+
|
172
|
+
@dataclass
|
173
|
+
class BacktestResult:
|
174
|
+
status: BacktestResultStatus
|
175
|
+
performance: PortfolioPerformance
|
176
|
+
def toDict(self) -> Dict[str, Any]:
|
177
|
+
dict = self.__dict__.copy()
|
178
|
+
dict['performance'] = self.performance.toDict()
|
179
|
+
return dict
|
180
|
+
|
181
|
+
@staticmethod
|
182
|
+
def fromDict(json_dict: Dict[str, Any]) -> BacktestResult:
|
183
|
+
status = BacktestResultStatus.fromDict(json_dict['status'])
|
184
|
+
performance = PortfolioPerformance.fromDict(json_dict['performance'])
|
185
|
+
return BacktestResult(status, performance)
|
@@ -9,43 +9,94 @@ from investfly.utils.PercentBasedPortfolioAllocator import PercentBasedPortfolio
|
|
9
9
|
|
10
10
|
|
11
11
|
class TradingStrategy(ABC):
|
12
|
+
"""
|
13
|
+
This is the main interface (abstract class) used to implement a trading strategy.
|
14
|
+
"""
|
12
15
|
|
13
16
|
def __init__(self) -> None:
|
14
17
|
self.state: Dict[str, int | float | bool] = {}
|
18
|
+
"""The persisted state of the strategy. """
|
15
19
|
|
16
20
|
@abstractmethod
|
17
21
|
def getSecurityUniverseSelector(self) -> SecurityUniverseSelector:
|
22
|
+
"""
|
23
|
+
This function is used to narrow down the set of securities (i.e stocks) against which to run your logic.
|
24
|
+
You can pick one of the standard list (e.g SP100) that we provide, provide your own list with comma separated symbols list,
|
25
|
+
or provide a query based on fundamental metrics like MarketCap, PE Ratio etc.
|
26
|
+
See docs on
|
27
|
+
`investfly.models.SecurityUniverseSelector.SecurityUniverseSelector` for more details.
|
28
|
+
Returns
|
29
|
+
:return: `investfly.models.SecurityUniverseSelector.SecurityUniverseSelector`
|
30
|
+
"""
|
18
31
|
pass
|
19
32
|
|
20
|
-
|
21
|
-
This function must be annotated with OnData to indicate when should this function be called.
|
22
|
-
The function is called whenever a new data is available based on the subscribed data
|
23
|
-
This function is called separately for each security
|
24
|
-
@DataParams({
|
25
|
-
"sma2": {"datatype": DataType.INDICATOR, "indicator": "SMA", "barinterval": BarInterval.ONE_MINUTE, "period": 2, "count": 2},
|
26
|
-
"sma3": {"datatype": DataType.INDICATOR, "indicator": "SMA", "barinterval": BarInterval.ONE_MINUTE, "period": 3, "count": 2},
|
27
|
-
"allOneMinBars": {"datatype": DataType.BARS, "barinterval": BarInterval.ONE_MINUTE},
|
28
|
-
"latestDailyBar": {"datatype": DataType.BARS, "barinterval": BarInterval.ONE_DAY, "count":1},
|
29
|
-
"quote": {"datatype": DataType.QUOTE},
|
30
|
-
"lastprice": {"datatype": DataType.QUOTE, "field": QuoteField.LASTPRICE},
|
31
|
-
"allFinancials": {"datatype": DataType.FINANCIAL},
|
32
|
-
"revenue": {"datatype": DataType.FINANCIAL, "field": FinancialField.REVENUE}
|
33
|
-
|
34
|
-
})
|
35
|
-
"""
|
33
|
+
|
36
34
|
@abstractmethod
|
37
35
|
def evaluateOpenTradeCondition(self, security: Security, data: Dict[str, Any]) -> TradeSignal | None:
|
36
|
+
"""
|
37
|
+
This function must be annotated with @DataParams to indicate what data (i.e indicator values) are needed as shown below.
|
38
|
+
The function is called whenever a new data is available based on the subscribed data.
|
39
|
+
This function is called separately for each security.
|
40
|
+
```
|
41
|
+
@DataParams({
|
42
|
+
"sma2": {"datatype": DataType.INDICATOR, "indicator": "SMA", "barinterval": BarInterval.ONE_MINUTE, "period": 2, "count": 2},
|
43
|
+
"sma3": {"datatype": DataType.INDICATOR, "indicator": "SMA", "barinterval": BarInterval.ONE_MINUTE, "period": 3, "count": 2},
|
44
|
+
"allOneMinBars": {"datatype": DataType.BARS, "barinterval": BarInterval.ONE_MINUTE},
|
45
|
+
"latestDailyBar": {"datatype": DataType.BARS, "barinterval": BarInterval.ONE_DAY, "count":1},
|
46
|
+
"quote": {"datatype": DataType.QUOTE},
|
47
|
+
"lastprice": {"datatype": DataType.QUOTE, "field": QuoteField.LASTPRICE},
|
48
|
+
"allFinancials": {"datatype": DataType.FINANCIAL},
|
49
|
+
"revenue": {"datatype": DataType.FINANCIAL, "field": FinancialField.REVENUE}
|
50
|
+
|
51
|
+
})
|
52
|
+
```
|
53
|
+
:param security: The stock security against which this is evaluated. You use it to construct TradeSignal
|
54
|
+
:param data: Dictionary with the requested data based on @DataParams annotation.
|
55
|
+
|
56
|
+
The keys of the data param dictionary match the keys specified in @DataParams annotation. The value depends on the datatype parameter.
|
57
|
+
datatype=INDICATOR, value type = `investfly.models.CommonModels.DatedValue`
|
58
|
+
datatype=QUOTE, field is specified, value type = `investfly.models.CommonModels.DatedValue`
|
59
|
+
datatype=QUOTE, field is not specified, value type is `investfly.models.MarketData.Quote` object (has dayOpen, dayHigh, dayLow, prevOpen etc)
|
60
|
+
datatype=BARS, value type is `investfly.models.MarketData.Bar`
|
61
|
+
|
62
|
+
Further, if the count is specified and greater than 1, value is returned as a List
|
63
|
+
:return: `investfly.model.StrategyModels.TradeSignal` if open condition matches and to signal open trade, None if open trade condition does not match
|
64
|
+
"""
|
38
65
|
pass
|
39
66
|
|
40
67
|
def processOpenTradeSignals(self, portfolio: Portfolio, tradeSignals: List[TradeSignal]) -> List[TradeOrder]:
|
68
|
+
"""
|
69
|
+
This method is used to convert TradeSignals into TradeOrders. You must do this for couple reasons:
|
70
|
+
1. Assume 1000 stocks match the open trade condition and so you have 1000 TradeSignals, but that does not
|
71
|
+
mean that you want to open position for 1000 stocks in your portfolio. You may want to order those trade signals
|
72
|
+
by strength and limit to top 10 trade signals
|
73
|
+
2. Your portfolio may already have open position for a stock corresponding to particular trade signal. In that case,
|
74
|
+
you may wan to skip that trade signal, and prioritize opening new position for other stocks
|
75
|
+
3. Here, you also set TradeOrder speficiations such as order type, quantity etc
|
76
|
+
4. You may want to fully rebalance portfolio baseed on these new trade signals
|
77
|
+
:param portfolio: Current portfolio state
|
78
|
+
:param tradeSignals: Trade Signals correspoding to stocks matching open trade condition
|
79
|
+
:return: List of TradeOrders to execute
|
80
|
+
"""
|
41
81
|
portfolioAllocator = PercentBasedPortfolioAllocator(10, PositionType.LONG)
|
42
82
|
return portfolioAllocator.allocatePortfolio(portfolio, tradeSignals)
|
43
83
|
|
44
84
|
def getStandardCloseCondition(self) -> StandardCloseCriteria | None:
|
85
|
+
"""
|
86
|
+
TargetProfit, StopLoss, TrailingStop and Timeout are considered as standard close criteria. This function
|
87
|
+
is created so that you can specify those standard close/exit conditions easily
|
88
|
+
:return: `investfly.models.StrategyModels.StandardCloseCriteria`
|
89
|
+
"""
|
45
90
|
# Note that these are always executed as MARKET_ORDER
|
46
91
|
return None
|
47
92
|
|
48
93
|
def evaluateCloseTradeCondition(self, openPos: OpenPosition, data) -> TradeOrder | None:
|
94
|
+
"""
|
95
|
+
Implementing this method is optional. But when implemented, it should be implemented similar to evaluateOpenTradeCondition
|
96
|
+
:param openPos: The open position
|
97
|
+
:param data: Requested data that corresponds to the open position's security symbol
|
98
|
+
:return: TradeOrder if the position is supposed to be closed, None otherwise
|
99
|
+
"""
|
49
100
|
return None
|
50
101
|
|
51
102
|
def runAtInterval(self, portfolio: Portfolio) -> List[TradeOrder]:
|
investfly/models/__init__.py
CHANGED
@@ -1,10 +1,18 @@
|
|
1
|
+
""""
|
2
|
+
This package contains all data model classes used in Strategy and Indicator definition.
|
3
|
+
|
4
|
+
The main class for defining trading strategy is `investfly.models.TradingStrategy.TradingStrategy`
|
5
|
+
|
6
|
+
The main class for defining custom technical indicator is `investfly.models.Indicator.Indicator`
|
7
|
+
"""
|
8
|
+
|
1
9
|
from investfly.models.CommonModels import DatedValue, TimeUnit, TimeDelta, Session
|
2
10
|
from investfly.models.Indicator import ParamType, IndicatorParamSpec, IndicatorValueType, IndicatorSpec, Indicator
|
3
11
|
from investfly.models.MarketData import SecurityType, Security, Quote, BarInterval, Bar
|
4
12
|
from investfly.models.MarketDataIds import QuoteField, FinancialField, StandardIndicatorId
|
5
13
|
from investfly.models.PortfolioModels import PositionType, TradeType, Broker, TradeOrder, OrderStatus, PendingOrder, Balances, CompletedTrade, OpenPosition, ClosedPosition, Portfolio, PortfolioPerformance
|
6
14
|
from investfly.models.SecurityUniverseSelector import StandardSymbolsList, CustomSecurityList, SecurityUniverseType, SecurityUniverseSelector, FinancialQuery, FinancialCondition, ComparisonOperator
|
7
|
-
from investfly.models.StrategyModels import DataParams, ScheduleInterval, Schedule, TradeSignal, StandardCloseCriteria, PortfolioSecurityAllocator
|
15
|
+
from investfly.models.StrategyModels import DataParams, ScheduleInterval, Schedule, TradeSignal, StandardCloseCriteria, PortfolioSecurityAllocator, BacktestStatus
|
8
16
|
from investfly.models.TradingStrategy import TradingStrategy
|
9
17
|
from investfly.models.SecurityFilterModels import DataType, DataParam, DataSource
|
10
18
|
|
@@ -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
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
investfly/__init__.py,sha256=Ku0IZu4VYvNGui70Xu-bG1fB8XtVNQxBey6f5nLIKVU,71
|
2
|
+
investfly/api/IndicatorApiClient.py,sha256=G2bEWaCvcmotp4jx-IoiqHblZZWAdwaUjDNP5ZoaagM,931
|
3
|
+
investfly/api/InvestflyApiClient.py,sha256=I_hi1Uw8EGa3jr06cNkrGbhjIT90BPr_YN5EFilykwQ,2111
|
4
|
+
investfly/api/MarketDataApiClient.py,sha256=bOlMzzZzN5A35oo0Iml2xCekV0jOig8Q_L66xi6n0n0,611
|
5
|
+
investfly/api/PortfolioApiClient.py,sha256=llNISIHWSx3wvf83usGhwPhVr-3RYGAndBNpZV9w3W0,1858
|
6
|
+
investfly/api/RestApiClient.py,sha256=XjvJCqAqUMPa7tXkhxGwaOj1xgHkfLcA8Q0027Z6xm8,3236
|
7
|
+
investfly/api/StrategyApiClient.py,sha256=w5_hQrxtQ7-1C1uu0UKL37ZbcjXmtdBAYXPtEiM-MC4,2087
|
8
|
+
investfly/api/__init__.py,sha256=JeeOmrsPbVk4B26DT3hbvBoxAvkMEhJ-PeKeloNGF08,600
|
9
|
+
investfly/cli/InvestflyCli.py,sha256=SCRqC4GpaVPB7RDzMAgr7_GpEl0ai4GF1qlBKXsjUws,10565
|
10
|
+
investfly/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
investfly/models/CommonModels.py,sha256=kbyWsez1voii2K2wyflYCW7i9lYQh49nbrts4bTvvG4,2348
|
12
|
+
investfly/models/Indicator.py,sha256=9v46Ozs2O8UPUjDzVC1MpoGx2iwnA__ZyrySVXxX8Qo,10660
|
13
|
+
investfly/models/MarketData.py,sha256=NbyOh472uIXyHaTW9eYCBbiTu2szSq47iCz8i3qI1v0,6081
|
14
|
+
investfly/models/MarketDataIds.py,sha256=CTNCb7G8NgLb84LYCqqxwlhO8e6RV-AUGwJH9GDv53A,3248
|
15
|
+
investfly/models/ModelUtils.py,sha256=pnrVIDM26LqK0Wuw7gTs0c97lCLIV_fm0EUtlEfT7j4,964
|
16
|
+
investfly/models/PortfolioModels.py,sha256=wEHzaxEMEmliNR1OXS0WNzzao7ZA5qhKIPzAjWQuUOM,8658
|
17
|
+
investfly/models/SecurityFilterModels.py,sha256=4baTBBI-XOKh8uTpvqVvk3unD-xHoyooO3dd5lKWbXA,5806
|
18
|
+
investfly/models/SecurityUniverseSelector.py,sha256=N2cYhgRz3PTh6T98liiiTbJNg27SBpaUaIQGgDHFbF4,8645
|
19
|
+
investfly/models/StrategyModels.py,sha256=n9MVOJFPtc_Wkiq5TyhdQnaiTUeXGMYqLmBE9IEiW10,5553
|
20
|
+
investfly/models/TradingStrategy.py,sha256=uUgQpZRgioVADIKerdC5VD0LEy9LvDqEEGvtRTgWASM,6381
|
21
|
+
investfly/models/__init__.py,sha256=6uMfJwcYaH1r-T-bbh6gMud0VpnoSQTkPNDVMDE3JXo,1383
|
22
|
+
investfly/samples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
+
investfly/samples/indicators/IndicatorTemplate.py,sha256=X0AlStLnL1SBSnTwrtW_sthm00tmuN5V7N-GrtiarmM,4454
|
24
|
+
investfly/samples/indicators/NewsSentiment.py,sha256=fcpAqOcNWmqYsP-xwJquCX_6G7Ntr3A1-m31eJHAUOE,65095
|
25
|
+
investfly/samples/indicators/RsiOfSma.py,sha256=kiLvMhrsbc_lV0EEpERGW2im19u5XmyJk88aTDGSBis,1719
|
26
|
+
investfly/samples/indicators/SmaEmaAverage.py,sha256=9pp3TtEKJADD_bfufwrWlwMswlTLoN7Nj6BC_jJ1sRs,1749
|
27
|
+
investfly/samples/indicators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
28
|
+
investfly/samples/strategies/SmaCrossOverStrategy.py,sha256=szjmnqQN8O7CRIS1vc-fRONKg4Dy1d59dZhiSEpEJAI,1699
|
29
|
+
investfly/samples/strategies/SmaCrossOverTemplate.py,sha256=HSyVh89_d-k1aAzqs_WFt8EDxs809OjWq6V3VJVUNQw,8200
|
30
|
+
investfly/samples/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
+
investfly/utils/CommonUtils.py,sha256=lCXAdI8-6PgbutcXJqUmSfuuLXp82FcnlxNVjCJpqLU,2631
|
32
|
+
investfly/utils/PercentBasedPortfolioAllocator.py,sha256=oKaSjq5L_3NfRo2iayepCZtZnCevsx4XpsxFfZMTnV8,1684
|
33
|
+
investfly/utils/__init__.py,sha256=2BqXoOQElv-GIU6wvmf2aaAABAcNny2TETcj7kf9rzM,129
|
34
|
+
investfly_sdk-1.3.dist-info/LICENSE.txt,sha256=Jmd2U7G7Z1oNdnRERRzFXN6C--bEo_K56j4v9EpJSTg,1090
|
35
|
+
investfly_sdk-1.3.dist-info/METADATA,sha256=Nxdk0L1ZE5eKcJzRVbofnXgfkQs6NGEUAMRFOimKZqo,7507
|
36
|
+
investfly_sdk-1.3.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
|
37
|
+
investfly_sdk-1.3.dist-info/entry_points.txt,sha256=GDRF4baJQXTh90DvdJJx1DeRezWfPt26E567lTs3g6U,66
|
38
|
+
investfly_sdk-1.3.dist-info/top_level.txt,sha256=dlEJ2OGWA3prqMvXELeydS5RTdpSzh7hz1LwR3NMc7A,10
|
39
|
+
investfly_sdk-1.3.dist-info/RECORD,,
|
@@ -1,38 +0,0 @@
|
|
1
|
-
investfly/__init__.py,sha256=SpTl-Tg1B1HTyjNOE-8ue-N2wGnXN_2zl7RFUSxlkiM,33
|
2
|
-
investfly/api/InvestflyApiClient.py,sha256=WBbI6i3ma09oOy6nUQO1amahV3WeorCHR0WLyaDjzw8,1735
|
3
|
-
investfly/api/MarketDataApiClient.py,sha256=XmGIt5RK0-70cHTDF24g3YXqqxCNyzMzBIVhz1-7ZqY,460
|
4
|
-
investfly/api/PortfolioApiClient.py,sha256=FVPjFz_eVLVXs-yPRefIcPoNiR3_WWzG7L0Dht60Z2o,1693
|
5
|
-
investfly/api/RestApiClient.py,sha256=lnBJRlNKUZtBJ0x69oTDMHKxcZqdF2qevKjGUI0-ybo,3067
|
6
|
-
investfly/api/StrategyApiClient.py,sha256=jsZpuSDNj5HFTAquiRmx3TgG4sO1_Un0kHdmtfJuDdg,1342
|
7
|
-
investfly/api/__init__.py,sha256=tOUcAnM3zOUewVJ6jr-cZphc8hiFwWfR1kPVaWNJtwQ,152
|
8
|
-
investfly/cli/InvestflyCli.py,sha256=RI3DMInpKRMgtiacAymKuxcURNJ3-4ctDrSllJNu5ic,8144
|
9
|
-
investfly/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
investfly/models/CommonModels.py,sha256=OrwgqCiDL_HJfZTJeeVVsHUuRSDm6Ehk9xpnFy5AB5I,1663
|
11
|
-
investfly/models/Indicator.py,sha256=FqiuiJJXCHUQtxf1AGUhIv2oYy3705D9MT3aVrJpX_s,6879
|
12
|
-
investfly/models/MarketData.py,sha256=h7mefNLmAtLL8z75CQy0FZsSazKnsaWULt4B4Bh2uhw,5786
|
13
|
-
investfly/models/MarketDataIds.py,sha256=8IsJmQWpi-TooQMjKDMfbZzJjzYOXGss-g10WzoMQyg,3177
|
14
|
-
investfly/models/ModelUtils.py,sha256=pnrVIDM26LqK0Wuw7gTs0c97lCLIV_fm0EUtlEfT7j4,964
|
15
|
-
investfly/models/PortfolioModels.py,sha256=JvqZiH0g3w9qGqEXywpgqasM9olV3n6kjH7bfy9t0Z0,8462
|
16
|
-
investfly/models/SecurityFilterModels.py,sha256=4baTBBI-XOKh8uTpvqVvk3unD-xHoyooO3dd5lKWbXA,5806
|
17
|
-
investfly/models/SecurityUniverseSelector.py,sha256=z4V5Ng0DQ8DVy8FeuB_7swAYO7rmSK6HfJc7vA1NCq8,8111
|
18
|
-
investfly/models/StrategyModels.py,sha256=MjDziuLNoL25kzEYsOvqyetxGAMYNRFIYOB-I8OqJg8,4346
|
19
|
-
investfly/models/TradingStrategy.py,sha256=dbsQ0wAY7M6s3yUpjp5cXp2X3tZwaUFB48pW-79C5D8,2825
|
20
|
-
investfly/models/__init__.py,sha256=YVr7PakHt-g7pRmihFqYrvxNMwYE-rlDymFGp4neUdE,1071
|
21
|
-
investfly/samples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
investfly/samples/indicators/IndicatorTemplate.py,sha256=X0AlStLnL1SBSnTwrtW_sthm00tmuN5V7N-GrtiarmM,4454
|
23
|
-
investfly/samples/indicators/NewsSentiment.py,sha256=fcpAqOcNWmqYsP-xwJquCX_6G7Ntr3A1-m31eJHAUOE,65095
|
24
|
-
investfly/samples/indicators/RsiOfSma.py,sha256=kiLvMhrsbc_lV0EEpERGW2im19u5XmyJk88aTDGSBis,1719
|
25
|
-
investfly/samples/indicators/SmaEmaAverage.py,sha256=9pp3TtEKJADD_bfufwrWlwMswlTLoN7Nj6BC_jJ1sRs,1749
|
26
|
-
investfly/samples/indicators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
-
investfly/samples/strategies/SmaCrossOverStrategy.py,sha256=szjmnqQN8O7CRIS1vc-fRONKg4Dy1d59dZhiSEpEJAI,1699
|
28
|
-
investfly/samples/strategies/SmaCrossOverTemplate.py,sha256=HSyVh89_d-k1aAzqs_WFt8EDxs809OjWq6V3VJVUNQw,8200
|
29
|
-
investfly/samples/strategies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
-
investfly/utils/CommonUtils.py,sha256=lCXAdI8-6PgbutcXJqUmSfuuLXp82FcnlxNVjCJpqLU,2631
|
31
|
-
investfly/utils/PercentBasedPortfolioAllocator.py,sha256=oKaSjq5L_3NfRo2iayepCZtZnCevsx4XpsxFfZMTnV8,1684
|
32
|
-
investfly/utils/__init__.py,sha256=2BqXoOQElv-GIU6wvmf2aaAABAcNny2TETcj7kf9rzM,129
|
33
|
-
investfly_sdk-1.1.dist-info/LICENSE.txt,sha256=Jmd2U7G7Z1oNdnRERRzFXN6C--bEo_K56j4v9EpJSTg,1090
|
34
|
-
investfly_sdk-1.1.dist-info/METADATA,sha256=2kmXPPwk_sXD0ralI7pOGdkMi-KKCcl2CYTSzgFSfGk,6514
|
35
|
-
investfly_sdk-1.1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
|
36
|
-
investfly_sdk-1.1.dist-info/entry_points.txt,sha256=GDRF4baJQXTh90DvdJJx1DeRezWfPt26E567lTs3g6U,66
|
37
|
-
investfly_sdk-1.1.dist-info/top_level.txt,sha256=dlEJ2OGWA3prqMvXELeydS5RTdpSzh7hz1LwR3NMc7A,10
|
38
|
-
investfly_sdk-1.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|