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 CHANGED
@@ -1,3 +1,5 @@
1
1
  """
2
2
  .. include:: ../README.md
3
- """
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
+
investfly/api/__init__.py CHANGED
@@ -1,3 +1,17 @@
1
- """ This package contains REST API Client classes to make REST API calls to Investfly Backend.
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
  """
@@ -1,11 +1,12 @@
1
1
  import argparse
2
2
  import pickle
3
3
  import os.path
4
- from typing import List
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 __listStrategies(self, args: argparse.Namespace) -> str:
33
- strategies: List[TradingStrategyModel] = self.investflyApi.strategyApi.getStrategies()
34
- strategiesDictList = list(map(lambda model: str({'strategyId': model.strategyId, 'strategyName': model.strategyName}), strategies))
35
- return "\n".join(strategiesDictList)
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 __exitAction(self, args: argparse.Namespace|None) -> None:
62
- if self.investflyApi.isLoggedIn():
63
- self.investflyApi.logout()
64
- self.running = False
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
- parser_listStrategies = subparser.add_parser('listStrategies', help='List Python Strategies')
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
- parser_createStrategy = subparser.add_parser('createStrategy', help='Create a new trading strategy')
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
- parser_updateCode = subparser.add_parser('updateStrategyCode', help='Update strategy Python Code')
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='Python File Path relative to the project root that contains strategy code')
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
- parser_exit = subparser.add_parser('exit', help='Stop and Exit CLI')
96
- parser_exit.set_defaults(func = self.__exitAction)
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
- #main()
194
- print(inspect.getfile(samples))
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
@@ -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
- # Indicator ValueType can possibly used by Investfly to validate expression and optimize experience for users
43
- # For e.g, all Indicators of same valueType can be plotted in the same y-axis
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
- # Return IndicatorDefinition with name, description, required params, and valuetype
104
- # See IndicatorDefinition abstract class for more details
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
- # Return the DataSource that this indicator is based on. Possible values are:
109
- # DataSource.BARS, DataSource.QUOTE, DataSource.NEWS, DataSource.FINANCIAL
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
- # Indicator series is needed for backtest and plotting on stock chart
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
@@ -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
- # fundamental metrics below
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
- customList: CustomSecurityList | None = None
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]:
@@ -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.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
 
@@ -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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (70.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,