diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index b5f5ea42..aa005ac9 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -47,14 +47,6 @@ pytest tests/test_.py::test_ flake8 investing_algorithm_framework ``` -### 3. Test if all type-hints are correct - -#### Run mypy - -``` bash -mypy investing_algorithm_framework -``` - ### Process: Your own code changes All code changes, regardless of who does them, need to be reviewed and merged by someone else. diff --git a/investing_algorithm_framework/__init__.py b/investing_algorithm_framework/__init__.py index b3bc7789..a8988420 100644 --- a/investing_algorithm_framework/__init__.py +++ b/investing_algorithm_framework/__init__.py @@ -2,3 +2,4 @@ VERSION = (0, 1, 0, 'alpha', 0) +__all__ = ['get_version'] diff --git a/investing_algorithm_framework/__main__.py b/investing_algorithm_framework/__main__.py index e50b38f1..78cfd87f 100644 --- a/investing_algorithm_framework/__main__.py +++ b/investing_algorithm_framework/__main__.py @@ -1,6 +1,8 @@ """ -Invokes investing_algorithm_framework-admin when the investing_algorithm_framework framework module is run as a script. -Example: python -m investing_algorithm_framework create_standard_algo SampleAlgorithm +Invokes investing_algorithm_framework-admin when the +investing_algorithm_framework framework module is run as a script. +Example: + python -m investing_algorithm_framework create_standard_algo SampleAlgorithm """ from investing_algorithm_framework.management import execute_from_command_line diff --git a/investing_algorithm_framework/configuration/__init__.py b/investing_algorithm_framework/configuration/__init__.py index 12a9b94e..cf156f87 100644 --- a/investing_algorithm_framework/configuration/__init__.py +++ b/investing_algorithm_framework/configuration/__init__.py @@ -4,9 +4,12 @@ from importlib import import_module from enum import Enum -from investing_algorithm_framework.core.exceptions import ImproperlyConfigured, OperationalException -from investing_algorithm_framework.configuration.config_constants import SETTINGS_MODULE_PATH_ENV_NAME, \ - SETTINGS_STRATEGY_REGISTERED_APPS, SETTINGS_DATA_PROVIDER_REGISTERED_APPS, SETTINGS_LOGGING_CONFIG +from investing_algorithm_framework.core.exceptions \ + import ImproperlyConfigured, OperationalException +from investing_algorithm_framework.configuration.config_constants \ + import SETTINGS_MODULE_PATH_ENV_NAME, \ + SETTINGS_STRATEGY_REGISTERED_APPS, SETTINGS_LOGGING_CONFIG, \ + SETTINGS_DATA_PROVIDER_REGISTERED_APPS class TimeUnit(Enum): @@ -30,13 +33,19 @@ def from_string(value: str): elif value.lower() in ('hr', 'hour', 'hours'): return TimeUnit.HOUR - elif value.lower() in ('always', 'every', 'continuous', 'every_time'): + elif value.lower() in ( + 'always', 'every', 'continuous', 'every_time' + ): return TimeUnit.ALWAYS else: - raise OperationalException('Could not convert value {} to a time_unit'.format(value)) + raise OperationalException( + 'Could not convert value {} to a time_unit'.format(value) + ) else: - raise OperationalException("Could not convert non string value to a time_unit") + raise OperationalException( + "Could not convert non string value to a time_unit" + ) def equals(self, other): @@ -55,7 +64,8 @@ def equals(self, other): class BaseSettings: """ - Base wrapper for settings module. It will load all the default settings for a given settings module + Base wrapper for settings module. It will load all the default settings + for a given settings module """ def __init__(self) -> None: @@ -66,7 +76,9 @@ def configure(self, settings_module: str = None) -> None: self._settings_module = settings_module if settings_module is None: - self.settings_module = os.environ.get(SETTINGS_MODULE_PATH_ENV_NAME) + self.settings_module = os.environ.get( + SETTINGS_MODULE_PATH_ENV_NAME + ) else: self.settings_module = settings_module @@ -88,8 +100,11 @@ def configure(self, settings_module: str = None) -> None: if setting.isupper(): setting_value = getattr(module, setting) - if setting in tuple_settings and not isinstance(setting_value, (list, tuple)): - raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting) + if setting in tuple_settings and \ + not isinstance(setting_value, (list, tuple)): + raise ImproperlyConfigured( + "The {} setting must be a list or a " + "tuple.".format(setting)) setattr(self, setting, setting_value) @@ -114,11 +129,16 @@ def __getitem__(self, item) -> Any: if isinstance(item, str): if not hasattr(self, item): - raise OperationalException("Setting object doesn't have the specific attribute {}".format(item)) + raise OperationalException( + "Setting object doesn't have the specific " + "attribute {}".format(item) + ) return self.__getattribute__(item) else: - raise OperationalException("Settings attributes can only be referenced by string") + raise OperationalException( + "Settings attributes can only be referenced by string" + ) def get(self, key: str, default: Any = None) -> Any: """ diff --git a/investing_algorithm_framework/configuration/setup/__init__.py b/investing_algorithm_framework/configuration/setup/__init__.py index 5369c0f7..dc77d08b 100644 --- a/investing_algorithm_framework/configuration/setup/__init__.py +++ b/investing_algorithm_framework/configuration/setup/__init__.py @@ -1 +1,4 @@ -from investing_algorithm_framework.configuration.setup.default_template_creators import DefaultProjectCreator +from investing_algorithm_framework.configuration.setup\ + .default_template_creators import DefaultProjectCreator + +__all__ = ['DefaultProjectCreator'] diff --git a/investing_algorithm_framework/configuration/setup/default_template_creators.py b/investing_algorithm_framework/configuration/setup/default_template_creators.py index 877de4c0..10d03be6 100644 --- a/investing_algorithm_framework/configuration/setup/default_template_creators.py +++ b/investing_algorithm_framework/configuration/setup/default_template_creators.py @@ -3,7 +3,8 @@ import investing_algorithm_framework from investing_algorithm_framework.core.exceptions import ImproperlyConfigured -from investing_algorithm_framework.configuration.setup.template_creator import TemplateCreator +from investing_algorithm_framework.configuration.setup.template_creator \ + import TemplateCreator class DefaultProjectCreator(TemplateCreator): @@ -15,12 +16,15 @@ def configure(self) -> None: bot_dir = os.path.join(self._bot_project_directory, self._bot_name) if os.path.exists(bot_dir): - raise ImproperlyConfigured("Project destination directory {} already exists".format(self._bot_name)) + raise ImproperlyConfigured("Project destination directory {} " + "already exists".format(self._bot_name)) def create(self) -> None: # Find the default template directory - template_dir = os.path.join(investing_algorithm_framework.__path__[0], self.TEMPLATE_ROOT_DIR) + template_dir = os.path.join( + investing_algorithm_framework.__path__[0], self.TEMPLATE_ROOT_DIR + ) for root, dirs, files in os.walk(template_dir): @@ -28,11 +32,16 @@ def create(self) -> None: # This is used as the basis for the copying path_rest = root[len(template_dir) + 1:] - # Replace template investing_algorithm_framework directory with given investing_algorithm_framework name - path_rest = path_rest.replace(self.PROJECT_TEMPLATE_DIR_NAME, self._bot_name) + # Replace template investing_algorithm_framework directory with + # given investing_algorithm_framework name + path_rest = path_rest.replace( + self.PROJECT_TEMPLATE_DIR_NAME, self._bot_name + ) # Create the directories if they don't exist - destination_dir = os.path.join(self._bot_project_directory, path_rest) + destination_dir = os.path.join( + self._bot_project_directory, path_rest + ) os.makedirs(destination_dir, exist_ok=True) for dirname in dirs[:]: @@ -54,14 +63,17 @@ def create(self) -> None: for old_suffix, new_suffix in self.rewrite_template_suffixes: if destination_path.endswith(old_suffix): - destination_path = destination_path[:-len(old_suffix)] + new_suffix + destination_path = \ + destination_path[:-len(old_suffix)] + new_suffix break # Only rewrite once if os.path.exists(destination_path): - raise ImproperlyConfigured ( + raise ImproperlyConfigured( "{} already exists. Overlaying {} {} into an existing " "directory won't replace conflicting " - "files.".format(destination_path, filename, destination_path) + "files.".format( + destination_path, filename, destination_path + ) ) copyfile(template_path, destination_path) @@ -72,20 +84,29 @@ def create(self) -> None: except OSError: raise ImproperlyConfigured( "Notice: Couldn't set permission bits on {}. You're " - "probably using an uncommon filesystem setup.".format(destination_path) + "probably using an uncommon filesystem setup.".format( + destination_path + ) ) # Format placeholders in file if needed - if filename in ['manage.py-template', 'settings.py-template', 'context.py-template']: + if filename in [ + 'manage.py-template', + 'settings.py-template', + 'context.py-template' + ]: # Read the file with open(destination_path, 'r') as file: file_data = file.read() - # Replace the placeholder with the investing_algorithm_framework name - file_data = file_data.replace(self.PROJECT_NAME_PLACEHOLDER, self._bot_name) + # Replace the placeholder with the + # investing_algorithm_framework name + file_data = file_data.replace( + self.PROJECT_NAME_PLACEHOLDER, self._bot_name + ) # Write the file out again with open(destination_path, 'w') as file: - file.write(file_data) \ No newline at end of file + file.write(file_data) diff --git a/investing_algorithm_framework/configuration/setup/template.py b/investing_algorithm_framework/configuration/setup/template.py index ce6214c2..9b0391e1 100644 --- a/investing_algorithm_framework/configuration/setup/template.py +++ b/investing_algorithm_framework/configuration/setup/template.py @@ -5,13 +5,15 @@ class Template: """ - A template class is responsible for creating a templates for the investing_algorithm_framework. + A template class is responsible for creating a templates for the + investing_algorithm_framework. """ def __init__(self, bot_project_directory: str, bot_name: str) -> None: """ - investing_algorithm_framework project directory is the root directory of the given - investing_algorithm_framework. The algorithm_name will be the same as the root project directory. For + investing_algorithm_framework project directory is the root + directory of the given investing_algorithm_framework. The + algorithm_name will be the same as the root project directory. For simplicity it is explicitly passed as a parameter """ diff --git a/investing_algorithm_framework/configuration/setup/template_creator.py b/investing_algorithm_framework/configuration/setup/template_creator.py index 2bfb62d3..a1b6293c 100644 --- a/investing_algorithm_framework/configuration/setup/template_creator.py +++ b/investing_algorithm_framework/configuration/setup/template_creator.py @@ -21,8 +21,8 @@ def __init__(self, bot_project_directory: str, bot_name: str) -> None: @abstractmethod def create(self) -> None: """ - Create here the template, it is recommended to first call the configure method, this will check if - everything is setup correctly. + Create here the template, it is recommended to first call the configure + method, this will check if everything is setup correctly. """ pass @@ -37,5 +37,3 @@ def make_writeable(filename): st = os.stat(filename) new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR os.chmod(filename, new_permissions) - - diff --git a/investing_algorithm_framework/core/context/__init__.py b/investing_algorithm_framework/core/context/__init__.py index 0dab92b7..47603f26 100644 --- a/investing_algorithm_framework/core/context/__init__.py +++ b/investing_algorithm_framework/core/context/__init__.py @@ -1 +1,3 @@ from investing_algorithm_framework.core.context.context import Context + +__all__ = ['Context'] diff --git a/investing_algorithm_framework/core/context/context.py b/investing_algorithm_framework/core/context/context.py index f7f78ef2..829198d8 100644 --- a/investing_algorithm_framework/core/context/context.py +++ b/investing_algorithm_framework/core/context/context.py @@ -8,8 +8,9 @@ class Context(metaclass=Singleton): """ - The Context defines the current state of the running algorithms. It also maintains a reference to an instance of a - state subclass, which represents the current state of the context instance. + The Context defines the current state of the running algorithms. It + also maintains a reference to an instance of a state subclass, which + represents the current state of the context instance. """ # A reference to the current state of the context. @@ -36,8 +37,9 @@ def _check_state(self, raise_exception: bool = False) -> bool: if raise_exception: raise OperationalException( - "Context doesn't have a state. Make sure that you set the state either " - "by initializing it or making sure that you transition to a new valid state." + "Context doesn't have a state. Make sure that you set " + "the state either by initializing it or making sure that " + "you transition to a new valid state." ) else: return False @@ -59,4 +61,3 @@ def _run_state(self) -> None: self._state.start() transition_state = self._state.get_transition_state_class() self.transition_to(transition_state) - diff --git a/investing_algorithm_framework/core/events/__init__.py b/investing_algorithm_framework/core/events/__init__.py index 33cf68b3..858903cb 100644 --- a/investing_algorithm_framework/core/events/__init__.py +++ b/investing_algorithm_framework/core/events/__init__.py @@ -1,2 +1,4 @@ from investing_algorithm_framework.core.events.observable import Observable from investing_algorithm_framework.core.events.observer import Observer + +__all__ = ['Observable', 'Observer'] diff --git a/investing_algorithm_framework/core/exceptions.py b/investing_algorithm_framework/core/exceptions.py index 8d757ccb..ee7e923a 100644 --- a/investing_algorithm_framework/core/exceptions.py +++ b/investing_algorithm_framework/core/exceptions.py @@ -1,6 +1,7 @@ class ImproperlyConfigured(Exception): """ - Class ImproperlyConfigured: Exception class indicating a problem with the configuration of the investing_algorithm_framework + Class ImproperlyConfigured: Exception class indicating a problem with + the configuration of the investing_algorithm_framework """ def __init__(self, message) -> None: super(ImproperlyConfigured, self).__init__(message) @@ -8,7 +9,8 @@ def __init__(self, message) -> None: class OperationalException(Exception): """ - Class OperationalException: Exception class indicating a problem occurred during running of the investing_algorithm_framework + Class OperationalException: Exception class indicating a problem occurred + during running of the investing_algorithm_framework """ def __init__(self, message) -> None: super(OperationalException, self).__init__(message) @@ -16,7 +18,8 @@ def __init__(self, message) -> None: class DatabaseOperationalException(Exception): """ - Class DatabaseOperationalException: Exception class indicating a problem occurred during usage of the database + Class DatabaseOperationalException: Exception class indicating a problem + occurred during usage of the database """ def __init__(self, message) -> None: super(DatabaseOperationalException, self).__init__(message) diff --git a/investing_algorithm_framework/core/executors/__init__.py b/investing_algorithm_framework/core/executors/__init__.py index 385bc8fe..fc6b58a6 100644 --- a/investing_algorithm_framework/core/executors/__init__.py +++ b/investing_algorithm_framework/core/executors/__init__.py @@ -1,2 +1,5 @@ from investing_algorithm_framework.core.executors.executor import Executor -from investing_algorithm_framework.core.executors.execution_scheduler import ExecutionScheduler +from investing_algorithm_framework.core.executors.execution_scheduler \ + import ExecutionScheduler + +__all__ = ['Executor', 'ExecutionScheduler'] diff --git a/investing_algorithm_framework/core/executors/execution_scheduler.py b/investing_algorithm_framework/core/executors/execution_scheduler.py index 658edfec..d1fa8921 100644 --- a/investing_algorithm_framework/core/executors/execution_scheduler.py +++ b/investing_algorithm_framework/core/executors/execution_scheduler.py @@ -10,8 +10,9 @@ class ExecutionScheduler: """ - Class ExecutionScheduler: This is a lazy scheduler. It only runs it's scheduling algorithm when it is asked to. - It will then evaluate all the time units and intervals and decide which executions it needs to return. + Class ExecutionScheduler: This is a lazy scheduler. It only runs it's + scheduling algorithm when it is asked to.It will then evaluate all the + time units and intervals and decide which executions it needs to return. At the initial run, it will schedule all the executions. """ @@ -19,7 +20,12 @@ class ExecutionScheduler: def __init__(self): self._planning: Dict[str, ExecutionTask] = {} - def add_execution_task(self, execution_id: str, time_unit: TimeUnit, interval: int = None) -> None: + def add_execution_task( + self, + execution_id: str, + time_unit: TimeUnit, + interval: int = None + ) -> None: """ Function that will add an appointment to the scheduler """ @@ -29,18 +35,28 @@ def add_execution_task(self, execution_id: str, time_unit: TimeUnit, interval: i if time_unit is not TimeUnit.ALWAYS: if interval is None: - raise OperationalException("Appoint must set an interval with the corresponding time unit") + raise OperationalException( + "Appoint must set an interval with the " + "corresponding time unit" + ) elif interval < 1: - raise OperationalException("Interval for task time unit is smaller then 1") + raise OperationalException( + "Interval for task time unit is smaller then 1" + ) - self._planning[execution_id] = ExecutionTask(time_unit=time_unit, interval=interval, last_run=None) + self._planning[execution_id] = ExecutionTask( + time_unit=time_unit, interval=interval, last_run=None + ) else: - raise OperationalException("Can't add execution task, execution id is already taken") + raise OperationalException( + "Can't add execution task, execution id is already taken" + ) def schedule_executions(self) -> List[str]: """ - Function that will return all appointments that have hit their time threshold + Function that will return all appointments that have hit their time + threshold """ appointments: List[str] = [] diff --git a/investing_algorithm_framework/core/executors/executor.py b/investing_algorithm_framework/core/executors/executor.py index 2d4ca153..23bea5e6 100644 --- a/investing_algorithm_framework/core/executors/executor.py +++ b/investing_algorithm_framework/core/executors/executor.py @@ -8,12 +8,14 @@ from investing_algorithm_framework.core.utils import StoppableThread from investing_algorithm_framework.core.events.observer import Observer from investing_algorithm_framework.core.events.observable import Observable -from investing_algorithm_framework.configuration.config_constants import DEFAULT_MAX_WORKERS +from investing_algorithm_framework.configuration.config_constants import \ + DEFAULT_MAX_WORKERS class Executor(Observable, Observer, ABC): """ - Executor class: functions as an abstract class that will handle the executions of workers in asynchronous order. + Executor class: functions as an abstract class that will handle the + executions of workers in asynchronous order. """ def __init__(self, max_workers: int = DEFAULT_MAX_WORKERS): @@ -57,7 +59,8 @@ def _initialize(self): workers = self.create_workers() if not workers or len(workers) == 0: - raise OperationalException("There where no workers initialized for the executor instance") + raise OperationalException("There where no workers initialized " + "for the executor instance") self._pending_workers = Queue() @@ -76,7 +79,9 @@ def run_jobs(self) -> None: Function that will start all the workers. """ - worker_iteration = self._max_workers - len(self._running_threads.keys()) + worker_iteration = self._max_workers - len( + self._running_threads.keys() + ) while worker_iteration > 0 and not self._pending_workers.empty(): worker = self._pending_workers.get() @@ -120,5 +125,7 @@ def processing(self) -> bool: Property that will show if the executor is running. """ - return (self._pending_workers is not None and not self._pending_workers.empty()) or \ - (self._running_threads is not None and len(self._running_threads.keys()) > 0) + return (self._pending_workers is not None + and not self._pending_workers.empty()) or \ + (self._running_threads is not None + and len(self._running_threads.keys()) > 0) diff --git a/investing_algorithm_framework/core/extensions.py b/investing_algorithm_framework/core/extensions.py index 7a12f9cb..8c5ca835 100644 --- a/investing_algorithm_framework/core/extensions.py +++ b/investing_algorithm_framework/core/extensions.py @@ -1,3 +1,3 @@ from investing_algorithm_framework.core.resolvers import DatabaseResolver -db = DatabaseResolver() \ No newline at end of file +db = DatabaseResolver() diff --git a/investing_algorithm_framework/core/resolvers/__init__.py b/investing_algorithm_framework/core/resolvers/__init__.py index 63d2bf7c..bbba282e 100644 --- a/investing_algorithm_framework/core/resolvers/__init__.py +++ b/investing_algorithm_framework/core/resolvers/__init__.py @@ -1,4 +1,6 @@ -from investing_algorithm_framework.core.resolvers.database_resolver import DatabaseResolver -from investing_algorithm_framework.core.resolvers.class_resolver import ClassResolver - +from investing_algorithm_framework.core.resolvers.database_resolver \ + import DatabaseResolver +from investing_algorithm_framework.core.resolvers.class_resolver \ + import ClassResolver +__all__ = ['DatabaseResolver', 'ClassResolver'] diff --git a/investing_algorithm_framework/core/resolvers/class_resolver.py b/investing_algorithm_framework/core/resolvers/class_resolver.py index 725a75cc..cd639ceb 100644 --- a/investing_algorithm_framework/core/resolvers/class_resolver.py +++ b/investing_algorithm_framework/core/resolvers/class_resolver.py @@ -6,11 +6,17 @@ class ClassResolver: """ - Class ClassCollector: This class will load all the classes of a specific type given the package_path. - You can specify a module name if you know where the class is located + Class ClassCollector: This class will load all the classes of a specific + type given the package_path. You can specify a module name if you + know where the class is located """ - def __init__(self, package_path: str, class_type: Type[Any], module_name: str = None) -> None: + def __init__( + self, + package_path: str, + class_type: Type[Any], + module_name: str = None + ) -> None: """ Constructor that initiates the reading of all available plugins when an instance of the PluginCollector object is created @@ -25,19 +31,24 @@ def __init__(self, package_path: str, class_type: Type[Any], module_name: str = else: self._find_classes(self.package_path) - def _find_classes(self, package_path: str, module_name: str = None) -> None: + def _find_classes(self, package_path: str, module_name: str = None) \ + -> None: """ - Functions that will search through all the files in a directory to locate the class matching the - given class name. + Functions that will search through all the files in a directory to + locate the class matching the given class name. """ loaded_package = import_module(package_path) - for _, package_name, is_package in pkgutil.iter_modules(loaded_package.__path__, loaded_package.__name__ + '.'): + for _, package_name, is_package in pkgutil.iter_modules( + loaded_package.__path__, loaded_package.__name__ + '.' + ): if not is_package: - if module_name is not None and not package_name == "{}.{}".format(package_path, module_name): + if module_name is not None and not \ + package_name == "{}.{}".format(package_path, + module_name): continue # Load the given module @@ -47,7 +58,8 @@ def _find_classes(self, package_path: str, module_name: str = None) -> None: for (name, class_definition) in cls_members: - # Only add classes that are a sub class of defined Plugin class, but NOT Plugin itself + # Only add classes that are a sub class of defined Plugin + # class, but NOT Plugin itself if issubclass(class_definition, self.class_type) \ and (class_definition is not self.class_type): self._class_instances.append(class_definition()) diff --git a/investing_algorithm_framework/core/resolvers/database_resolver.py b/investing_algorithm_framework/core/resolvers/database_resolver.py index 4afef0d0..6fc43e9d 100644 --- a/investing_algorithm_framework/core/resolvers/database_resolver.py +++ b/investing_algorithm_framework/core/resolvers/database_resolver.py @@ -2,23 +2,27 @@ from typing import Any from sqlalchemy import create_engine -from sqlalchemy.orm import Query, class_mapper, sessionmaker, scoped_session, Session +from sqlalchemy.orm import Query, class_mapper, sessionmaker, scoped_session, \ + Session from sqlalchemy.orm.exc import UnmappedClassError from sqlalchemy.ext.declarative import declarative_base, declared_attr from sqlalchemy.orm.exc import DetachedInstanceError from sqlalchemy.exc import DatabaseError from investing_algorithm_framework.configuration import settings -from investing_algorithm_framework.configuration.config_constants import BASE_DIR, DATABASE_NAME -from investing_algorithm_framework.core.exceptions import DatabaseOperationalException +from investing_algorithm_framework.configuration.config_constants import \ + BASE_DIR, DATABASE_NAME +from investing_algorithm_framework.core.exceptions import \ + DatabaseOperationalException class _SessionProperty: """ Wrapper for session property of a Model - To make sure that each thread gets an scoped session, a new scoped session is created if a new thread - accesses the session property of a Model. + To make sure that each thread gets an scoped session, a new scoped + session is created if a new thread accesses the session property of + a Model. """ def __init__(self, db): self.db = db @@ -31,7 +35,8 @@ class _QueryProperty: """ Wrapper for query property of a Model - This wrapper makes sure that each model gets a Query object with a correct session corresponding to its thread. + This wrapper makes sure that each model gets a Query object with a + correct session corresponding to its thread. """ def __init__(self, db): self.db = db @@ -139,7 +144,9 @@ def initialize(self): database_name = settings.get(DATABASE_NAME) if database_name is not None: - self.database_path = os.path.join(base_dir, database_name, '.sqlite3') + self.database_path = os.path.join( + base_dir, database_name, '.sqlite3' + ) else: self.database_path = os.path.join(base_dir, 'db.sqlite3') @@ -171,7 +178,3 @@ def model(self) -> Model: def initialize_tables(self): self._model.metadata.create_all(self.engine) - - - - diff --git a/investing_algorithm_framework/core/state/__init__.py b/investing_algorithm_framework/core/state/__init__.py index 7e6bd75e..06685c3f 100644 --- a/investing_algorithm_framework/core/state/__init__.py +++ b/investing_algorithm_framework/core/state/__init__.py @@ -1,2 +1,3 @@ from investing_algorithm_framework.core.state.state import State +__all__ = ['State'] diff --git a/investing_algorithm_framework/core/state/state.py b/investing_algorithm_framework/core/state/state.py index ecd2510f..409a856a 100644 --- a/investing_algorithm_framework/core/state/state.py +++ b/investing_algorithm_framework/core/state/state.py @@ -6,8 +6,9 @@ class State(ABC): """ - Represents a state of the Bot, these state are use by the BotContext. Each implemented state represents a work - mode for the investing_algorithm_framework. + Represents a state of the Bot, these state are use by the BotContext. + Each implemented state represents a work mode for the + investing_algorithm_framework. """ # Transition state for the next BotState @@ -64,8 +65,9 @@ def validate_state(self, pre_state: bool = False) -> bool: def get_transition_state_class(self): assert getattr(self, 'transition_state_class', None) is not None, ( - "{} should either include a transition_state_class attribute, or override the " - "`get_transition_state_class()`, method.".format(self.__class__.__name__) + "{} should either include a transition_state_class attribute, " + "or override the `get_transition_state_class()`, " + "method.".format(self.__class__.__name__) ) return self.transition_state_class @@ -74,14 +76,16 @@ def get_pre_state_validators(self) -> List[StateValidator]: if self.pre_state_validators is not None: return [ - state_validator() for state_validator in getattr(self, 'pre_state_validators') - if issubclass(state_validator, StateValidator) + state_validator() for state_validator in getattr( + self, 'pre_state_validators' + ) if issubclass(state_validator, StateValidator) ] def get_post_state_validators(self) -> List[StateValidator]: if self.post_state_validators is not None: return [ - state_validator() for state_validator in getattr(self, 'post_state_validators') - if issubclass(state_validator, StateValidator) + state_validator() for state_validator in getattr( + self, 'post_state_validators' + ) if issubclass(state_validator, StateValidator) ] diff --git a/investing_algorithm_framework/core/utils.py b/investing_algorithm_framework/core/utils.py index 353df2ec..0e36bd96 100644 --- a/investing_algorithm_framework/core/utils.py +++ b/investing_algorithm_framework/core/utils.py @@ -8,8 +8,8 @@ class Singleton(type): """ - Class Singleton: lets an instance that extends this class function as a Singleton. - Only use this in a necessarily case. + Class Singleton: lets an instance that extends this class function as a + Singleton. Only use this in a necessarily case. """ _instances = {} @@ -18,14 +18,17 @@ class Singleton(type): def __call__(cls, *args, **kwargs): if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + cls._instances[cls] = super(Singleton, cls).__call__( + *args, **kwargs + ) return cls._instances[cls] class StoppableThread(Thread): """ - Class StoppableThread: Functions as a wrapper around a thread to add stop function + Class StoppableThread: Functions as a wrapper around a thread to add + stop function """ def __init__(self, *args, **keywords): @@ -83,13 +86,19 @@ def from_string(value: str): elif value.lower() in ('hr', 'hour', 'hours'): return TimeUnit.HOUR - elif value.lower() in ('always', 'every', 'continuous', 'every_time'): + elif value.lower() in ( + 'always', 'every', 'continuous', 'every_time' + ): return TimeUnit.ALWAYS else: - raise OperationalException('Could not convert value {} to a time_unit'.format(value)) + raise OperationalException( + 'Could not convert value {} to a time_unit'.format(value) + ) else: - raise OperationalException("Could not convert non string value to a time_unit") + raise OperationalException( + "Could not convert non string value to a time_unit" + ) def equals(self, other): @@ -103,4 +112,4 @@ def equals(self, other): except OperationalException: pass - return other == self.value \ No newline at end of file + return other == self.value diff --git a/investing_algorithm_framework/core/validators/__init__.py b/investing_algorithm_framework/core/validators/__init__.py index e4c3db4f..cb76c236 100644 --- a/investing_algorithm_framework/core/validators/__init__.py +++ b/investing_algorithm_framework/core/validators/__init__.py @@ -1 +1,4 @@ -from investing_algorithm_framework.core.validators.state_validators import StateValidator +from investing_algorithm_framework.core.validators.state_validators \ + import StateValidator + +__all__ = ['StateValidator'] diff --git a/investing_algorithm_framework/core/validators/state_validators.py b/investing_algorithm_framework/core/validators/state_validators.py index 8c111ede..1f2d69bf 100644 --- a/investing_algorithm_framework/core/validators/state_validators.py +++ b/investing_algorithm_framework/core/validators/state_validators.py @@ -3,8 +3,9 @@ class StateValidator(ABC): """ - Class StateValidator: validates the given state. Use this class to change the transition process of a state. - Use it as a hook to decide if a state must transition, e.g. only change to a strategies state if all the + Class StateValidator: validates the given state. Use this class to change + the transition process of a state. Use it as a hook to decide if a + state must transition, e.g. only change to a strategies state if all the provided data_providers meets a certain threshold. """ diff --git a/investing_algorithm_framework/core/workers/__init__.py b/investing_algorithm_framework/core/workers/__init__.py index e469a020..7f006386 100644 --- a/investing_algorithm_framework/core/workers/__init__.py +++ b/investing_algorithm_framework/core/workers/__init__.py @@ -1,3 +1,5 @@ from investing_algorithm_framework.core.workers.worker import Worker -from investing_algorithm_framework.core.workers.scheduled_worker import ScheduledWorker +from investing_algorithm_framework.core.workers.scheduled_worker \ + import ScheduledWorker +__all__ = ['Worker', 'ScheduledWorker'] diff --git a/investing_algorithm_framework/core/workers/scheduled_worker.py b/investing_algorithm_framework/core/workers/scheduled_worker.py index f327fbe3..fb2fb00c 100644 --- a/investing_algorithm_framework/core/workers/scheduled_worker.py +++ b/investing_algorithm_framework/core/workers/scheduled_worker.py @@ -16,8 +16,9 @@ def get_time_unit(self) -> TimeUnit: def get_time_interval(self) -> int: assert getattr(self, 'time_interval', None) is not None, ( - "{} should either include a time_interval attribute, or override the " - "`get_time_interval()`, method.".format(self.__class__.__name__) + "{} should either include a time_interval attribute, or " + "override the `get_time_interval()`, " + "method.".format(self.__class__.__name__) ) return getattr(self, 'time_interval') diff --git a/investing_algorithm_framework/core/workers/worker.py b/investing_algorithm_framework/core/workers/worker.py index 6b086a2f..6a7a95e9 100644 --- a/investing_algorithm_framework/core/workers/worker.py +++ b/investing_algorithm_framework/core/workers/worker.py @@ -7,14 +7,16 @@ class Worker(Observable, ABC): """ - Class Worker: manages the execution of a task and the context around executing it. + Class Worker: manages the execution of a task and the context around + executing it. """ id = None def start(self, **kwargs: Dict[str, Any]) -> None: """ - Function that will start the worker, and notify its observers when it is finished + Function that will start the worker, and notify its observers when + it is finished """ self.work(**kwargs) @@ -40,4 +42,3 @@ def get_id(self) -> str: ) return getattr(self, 'id') - diff --git a/investing_algorithm_framework/management/__init__.py b/investing_algorithm_framework/management/__init__.py index 3da4365c..82b1e63a 100644 --- a/investing_algorithm_framework/management/__init__.py +++ b/investing_algorithm_framework/management/__init__.py @@ -1,3 +1,4 @@ -from investing_algorithm_framework.management.command import BaseCommand -from investing_algorithm_framework.management.command_manager import execute_from_command_line +from .command import BaseCommand +from .command_manager import execute_from_command_line +__all__ = ['BaseCommand', 'execute_from_command_line'] diff --git a/investing_algorithm_framework/management/command.py b/investing_algorithm_framework/management/command.py index d8e20a8a..e3712b30 100644 --- a/investing_algorithm_framework/management/command.py +++ b/investing_algorithm_framework/management/command.py @@ -2,9 +2,6 @@ from typing import Any from argparse import ArgumentParser from abc import abstractmethod, ABC -from colorama import Fore - -from investing_algorithm_framework.core.exceptions import ImproperlyConfigured class CommandError(Exception): @@ -25,7 +22,13 @@ class CommandParser(ArgumentParser): command is called programmatically. """ - def __init__(self, *, missing_args_message=None, called_from_command_line=None, **kwargs): + def __init__( + self, + *, + missing_args_message=None, + called_from_command_line=None, + **kwargs + ): self.missing_args_message = missing_args_message self.called_from_command_line = called_from_command_line super().__init__(**kwargs) @@ -33,7 +36,8 @@ def __init__(self, *, missing_args_message=None, called_from_command_line=None, def parse_args(self, args=None, namespace=None): # Catch missing argument for a better error message - if self.missing_args_message and not (args or any(not arg.startswith('-') for arg in args)): + if self.missing_args_message and not \ + (args or any(not arg.startswith('-') for arg in args)): self.error(self.missing_args_message) return super().parse_args(args, namespace) @@ -50,7 +54,8 @@ class BaseCommand(ABC): success_message = '' _called_from_command_line = False - def create_parser(self, program_name, sub_command, **kwargs) -> CommandParser: + def create_parser(self, program_name, sub_command, **kwargs) \ + -> CommandParser: """ Create and return the ``ArgumentParser`` which will be used to parse the arguments to this command. @@ -59,7 +64,9 @@ def create_parser(self, program_name, sub_command, **kwargs) -> CommandParser: prog='%s %s' % (os.path.basename(program_name), sub_command), description=self.help_message or None, missing_args_message=getattr(self, 'missing_args_message', None), - called_from_command_line=getattr(self, '_called_from_command_line', None), + called_from_command_line=getattr( + self, '_called_from_command_line', None + ), **kwargs ) diff --git a/investing_algorithm_framework/management/command_manager.py b/investing_algorithm_framework/management/command_manager.py index d6d13a89..74d5a48c 100644 --- a/investing_algorithm_framework/management/command_manager.py +++ b/investing_algorithm_framework/management/command_manager.py @@ -19,7 +19,8 @@ def __init__(self, argv=None) -> None: self.program_name = os.path.basename(self.argv[0]) if self.program_name == '__main__.py': - self.program_name = 'python -m investing-investing_algorithm_framework' + self.program_name = 'python -m ' \ + 'investing-investing_algorithm_framework' self.settings_exception = None def execute(self) -> None: @@ -31,23 +32,29 @@ def execute(self) -> None: try: sub_command = self.argv[1] except IndexError: - sub_command = 'help' # Display help by default if no arguments are given. + # Display help by default if no arguments are given. + sub_command = 'help' try: # Run the command if it is not a help command - if sub_command.lower() not in ['help', '-help', '--help', 'h', '-h']: - response = self.fetch_command(sub_command).run_from_argv(self.argv) + if sub_command.lower() not in [ + 'help', '-help', '--help', 'h', '-h' + ]: + response = self.fetch_command(sub_command).\ + run_from_argv(self.argv) else: # Help for sub command if len(self.argv) > 2: - # make the first argument the sub command and the second argument the help option + # make the first argument the sub command and the second + # argument the help option sub_command = self.argv[2] option = '--help' self.argv[1] = sub_command self.argv[2] = option - response = self.fetch_command(sub_command).run_from_argv(self.argv) + response = self.fetch_command(sub_command)\ + .run_from_argv(self.argv) else: # Show general help command command = HelpCommand() @@ -57,7 +64,8 @@ def execute(self) -> None: sys.stdout.write(response) - def fetch_command(self, sub_command: str) -> BaseCommand: + @staticmethod + def fetch_command(sub_command: str) -> BaseCommand: commands = get_commands() if sub_command not in commands: @@ -101,4 +109,4 @@ def execute_from_command_line(argv=None): """Run the ManagementUtility.""" utility = ManagementUtility(argv) - utility.execute() \ No newline at end of file + utility.execute() diff --git a/investing_algorithm_framework/management/commands/create_standard_algo.py b/investing_algorithm_framework/management/commands/create_standard_algo.py index be3e5582..7ed507a3 100644 --- a/investing_algorithm_framework/management/commands/create_standard_algo.py +++ b/investing_algorithm_framework/management/commands/create_standard_algo.py @@ -3,13 +3,16 @@ from importlib import import_module from investing_algorithm_framework.core.exceptions import ImproperlyConfigured -from investing_algorithm_framework.management.command import BaseCommand, CommandError -from investing_algorithm_framework.configuration.setup.default_template_creators import DefaultProjectCreator +from investing_algorithm_framework.management.command import BaseCommand, \ + CommandError +from investing_algorithm_framework.configuration.setup.\ + default_template_creators import DefaultProjectCreator class CreateStandardAlgorithmCommand(BaseCommand): help = ( - "Creates a project directory structure for the given investing_algorithm_framework instance in the current " + "Creates a project directory structure for the given " + "investing_algorithm_framework instance in the current " "directory or optionally in the given directory." ) @@ -18,10 +21,8 @@ class CreateStandardAlgorithmCommand(BaseCommand): def add_arguments(self, parser): parser.add_argument('name', help='Name of the algorithm/project.') - parser.add_argument('directory', nargs='?', help='Optional destination directory') parser.add_argument( - '--template_creator', - help='Optional template creator plugin, provided by third party libraries' + 'directory', nargs='?', help='Optional destination directory' ) def handle(self, **options) -> None: @@ -39,8 +40,9 @@ def handle(self, **options) -> None: if os.path.isdir(directory): raise ImproperlyConfigured( - "Directory {} already exists. Please make sure that the project " - "name does not correspond to an existing directory".format(str(directory)) + "Directory {} already exists. Please make sure that " + "the project name does not correspond to an existing " + "directory".format(str(directory)) ) os.mkdir(directory) @@ -50,7 +52,8 @@ def handle(self, **options) -> None: if not os.path.exists(directory): raise CommandError( - "Destination directory {} does not exist, please create it first.".format(str(directory)) + "Destination directory {} does not exist, please " + "create it first.".format(str(directory)) ) # Use default investing_algorithm_framework creator @@ -70,9 +73,12 @@ def validate_name(name: str) -> None: if name is None: raise CommandError("you must provide a project name") - if not re.match("^[a-zA-Z]+\w*$", name): - raise CommandError("{} is not allowed, value must begin with a letter and " - "only contains the characters of 0-9, A-Z, a-z and _".format(name)) + if not re.match("^[a-zA-Z_.-]+$", name): + raise CommandError( + "{} is not allowed, value must begin with a letter and " + "only contains the characters of A-Z, " + "a-z and _".format(name) + ) # Make sure it can't be imported try: diff --git a/investing_algorithm_framework/management/commands/help.py b/investing_algorithm_framework/management/commands/help.py index 6f13b596..49c494ce 100644 --- a/investing_algorithm_framework/management/commands/help.py +++ b/investing_algorithm_framework/management/commands/help.py @@ -3,7 +3,8 @@ from investing_algorithm_framework.management import BaseCommand from investing_algorithm_framework.management.utils import get_commands -from investing_algorithm_framework.configuration.config_constants import FRAMEWORK_CORE_MODULE_NAME, FRAMEWORK_NAME +from investing_algorithm_framework.configuration.config_constants import \ + FRAMEWORK_CORE_MODULE_NAME, FRAMEWORK_NAME class HelpCommand(BaseCommand): @@ -17,18 +18,21 @@ def add_arguments(self, parser) -> None: def handle(self) -> Any: usage = [ "", - "This is the command line management for the investing algorithm framework. \n" + "This is the command line management for the investing " + "algorithm framework. \n" "Type help ' for help on a specific sub command.", "", "Available sub commands:", ] - # The command list will be entries based on app name with corresponding commands list + # The command list will be entries based on app name with + # corresponding commands list commands_dict = defaultdict(lambda: []) for name, app in get_commands().items(): - # Change the module name to the framework name for nice presentation + # Change the module name to the framework name for + # nice presentation if app == FRAMEWORK_CORE_MODULE_NAME: app = FRAMEWORK_NAME diff --git a/investing_algorithm_framework/management/commands/run_algorithm.py b/investing_algorithm_framework/management/commands/run_algorithm.py index 2591bb1f..7e87b936 100644 --- a/investing_algorithm_framework/management/commands/run_algorithm.py +++ b/investing_algorithm_framework/management/commands/run_algorithm.py @@ -2,14 +2,17 @@ from investing_algorithm_framework.management.command import BaseCommand from investing_algorithm_framework.core.context import Context -from investing_algorithm_framework.configuration.config_constants import SETTINGS_CONTEXT_CONFIGURATION +from investing_algorithm_framework.configuration.config_constants import \ + SETTINGS_CONTEXT_CONFIGURATION from investing_algorithm_framework.configuration import settings class RunAlgorithmCommand(BaseCommand): help_message = ( - "Runs a instance of an algorithm created with the investing_algorithm_framework, by default it will run " - "until stopped, if cycles is specified it will run the according to the amount of cycles" + "Runs a instance of an algorithm created with the " + "investing_algorithm_framework, by default it will run " + "until stopped, if cycles is specified it will run the according " + "to the amount of cycles" ) success_message = ( @@ -27,10 +30,7 @@ def handle(self, *args, **options) -> Any: # Load the context configuration __import__(settings[SETTINGS_CONTEXT_CONFIGURATION]) - # Create an investing_algorithm_framework context of the investing_algorithm_framework and run it + # Create an investing_algorithm_framework context of the + # investing_algorithm_framework and run it context = Context() context.start() - - - - diff --git a/investing_algorithm_framework/management/utils.py b/investing_algorithm_framework/management/utils.py index 9991b0b1..31fc24bc 100644 --- a/investing_algorithm_framework/management/utils.py +++ b/investing_algorithm_framework/management/utils.py @@ -18,8 +18,10 @@ def find_commands(management_dir: str) -> List[str]: """ command_dir = os.path.join(management_dir, 'commands') - return [name for _, name, is_pkg in pkgutil.iter_modules([command_dir]) - if not is_pkg and not name.startswith('_')] + return [ + name for _, name, is_pkg in pkgutil.iter_modules([command_dir]) + if not is_pkg and not name.startswith('_') + ] @functools.lru_cache(maxsize=None) @@ -29,7 +31,10 @@ def get_commands() -> Dict[str, str]: """ # Base commands - commands = {name: 'investing_algorithm_framework.management' for name in find_commands(os.path.join(os.path.dirname(__file__)))} + commands = { + name: 'investing_algorithm_framework.management' + for name in find_commands(os.path.join(os.path.dirname(__file__))) + } if not settings.configured: return commands @@ -50,4 +55,4 @@ class instance. module_name=sub_command ) - return collector.instances[0] \ No newline at end of file + return collector.instances[0] diff --git a/investing_algorithm_framework/templates/data_providers/__init__.py b/investing_algorithm_framework/templates/data_providers/__init__.py index bf357f48..7516ab08 100644 --- a/investing_algorithm_framework/templates/data_providers/__init__.py +++ b/investing_algorithm_framework/templates/data_providers/__init__.py @@ -1 +1,4 @@ -from investing_algorithm_framework.templates.data_providers.data_provider import DataProvider +from investing_algorithm_framework.templates.data_providers.data_provider \ + import DataProvider + +__all__ = ['DataProvider'] diff --git a/investing_algorithm_framework/templates/data_providers/data_provider.py b/investing_algorithm_framework/templates/data_providers/data_provider.py index e582aa49..8ecb15d1 100644 --- a/investing_algorithm_framework/templates/data_providers/data_provider.py +++ b/investing_algorithm_framework/templates/data_providers/data_provider.py @@ -9,7 +9,8 @@ class DataProviderException(Exception): """ - Should be raised when an data_provider related error occurs, for example if an authorization for an API fails, + Should be raised when an data_provider related error occurs, for + example if an authorization for an API fails, i.e.: raise DataProviderException('Provided api token is false') """ @@ -23,11 +24,13 @@ def __str__(self) -> str: class DataProvider(ScheduledWorker): """ - Class DataProvider: An entity which responsibility is to provide data from an external source. - Where a data_providers source is defined as any third party service that provides data e.g cloud storage, + Class DataProvider: An entity which responsibility is to provide data + from an external source. Where a data_providers source is defined as + any third party service that provides data e.g cloud storage, REST API, or website. - A data_provider must always be run with the start function from it´s super class. Otherwise depended + A data_provider must always be run with the start function from it´s + super class. Otherwise depended observers will not be updated. """ @@ -38,5 +41,3 @@ def provide_data(self) -> None: def work(self, **kwargs: Dict[str, Any]) -> None: logger.info("Starting data provider {}".format(self.get_id())) self.provide_data() - - diff --git a/investing_algorithm_framework/templates/order_executors/__init__.py b/investing_algorithm_framework/templates/order_executors/__init__.py index f67e6d7e..c57da968 100644 --- a/investing_algorithm_framework/templates/order_executors/__init__.py +++ b/investing_algorithm_framework/templates/order_executors/__init__.py @@ -1 +1,4 @@ -from investing_algorithm_framework.templates.order_executors.order_executor import OrderExecutor +from investing_algorithm_framework.templates.order_executors.order_executor \ + import OrderExecutor + +__all__ = ['OrderExecutor'] diff --git a/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/context.py-template b/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/context.py-template index 7d613df5..e6189a8f 100644 --- a/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/context.py-template +++ b/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/context.py-template @@ -1,8 +1,12 @@ from investing_algorithm_framework.core.context import Context -from investing_algorithm_framework.templates.states.setup_state import SetupState -from investing_algorithm_framework.templates.states.data_providing_state import DataProvidingState -from investing_algorithm_framework.templates.states.strategy_state import StrategyState -from investing_algorithm_framework.templates.states.ordering_state import OrderingState +from investing_algorithm_framework.templates.states.setup_state \ + import SetupState +from investing_algorithm_framework.templates.states.data_providing_state \ + import DataProvidingState +from investing_algorithm_framework.templates.states.strategy_state \ + import StrategyState +from investing_algorithm_framework.templates.states.ordering_state \ + import OrderingState # Import custom components from {{ project_name }}.data_providers.data_providers import MyDataProvider diff --git a/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/settings.py-template b/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/settings.py-template index 24115cdc..d1996cb9 100755 --- a/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/settings.py-template +++ b/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/configuration/settings.py-template @@ -30,7 +30,8 @@ LOGGING = { 'disable_existing_loggers': False, 'formatters': { 'standard': { - 'format': '%(levelname)s %(asctime)s - [thread: %(threadName)-4s %(name)s] %(message)s', + 'format': '%(levelname)s %(asctime)s - ' + '[thread: %(threadName)-4s %(name)s] %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' } }, @@ -53,4 +54,4 @@ LOGGING = { 'handlers': ['console', 'file'], }, }, -} \ No newline at end of file +} diff --git a/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/order_executors/order_executors.py-template b/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/order_executors/order_executors.py-template index 2a04fba2..093377bb 100644 --- a/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/order_executors/order_executors.py-template +++ b/investing_algorithm_framework/templates/projects/algorithm_project_directory/algorithm_project_template/order_executors/order_executors.py-template @@ -1,4 +1,5 @@ -from investing_algorithm_framework.templates.order_executors import OrderExecutor +from investing_algorithm_framework.templates.order_executors \ + import OrderExecutor class MyOrderExecutor(OrderExecutor): diff --git a/investing_algorithm_framework/templates/projects/algorithm_project_directory/manage.py-template b/investing_algorithm_framework/templates/projects/algorithm_project_directory/manage.py-template index 697caa3c..c5cd1574 100755 --- a/investing_algorithm_framework/templates/projects/algorithm_project_directory/manage.py-template +++ b/investing_algorithm_framework/templates/projects/algorithm_project_directory/manage.py-template @@ -1,19 +1,27 @@ #!/usr/bin/env python -"""Investing algorithm framework's command-line utility for administrative tasks.""" +""" +Investing algorithm framework's command-line utility for administrative tasks. +""" + import os import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('INVESTING_ALGORITHM_FRAMEWORK_SETTINGS_MODULE', '{{ project_name }}.configuration.settings') + os.environ.setdefault( + 'INVESTING_ALGORITHM_FRAMEWORK_SETTINGS_MODULE', + '{{ project_name }}.configuration.settings' + ) try: - from investing_algorithm_framework.management import execute_from_command_line + from investing_algorithm_framework.management import \ + execute_from_command_line except ImportError as exc: raise ImportError( - "Couldn't import investing_bot_framework. Are you sure it's installed and available on your PYTHONPATH" - "environment variable? Did you forget to activate a virtual environment?" + "Couldn't import investing_bot_framework. Are you sure it's " + "installed and available on your PYTHONPATH environment variable? " + "Did you forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) diff --git a/investing_algorithm_framework/templates/states/data_providing_state.py b/investing_algorithm_framework/templates/states/data_providing_state.py index 33717f8b..6f05ebb1 100644 --- a/investing_algorithm_framework/templates/states/data_providing_state.py +++ b/investing_algorithm_framework/templates/states/data_providing_state.py @@ -11,7 +11,8 @@ from investing_algorithm_framework.core.workers import Worker from investing_algorithm_framework.templates.data_providers import DataProvider from investing_algorithm_framework.core.executors import Executor -from investing_algorithm_framework.configuration.config_constants import DEFAULT_MAX_WORKERS, SETTINGS_MAX_WORKERS +from investing_algorithm_framework.configuration.config_constants \ + import DEFAULT_MAX_WORKERS, SETTINGS_MAX_WORKERS logger = logging.getLogger(__name__) @@ -21,7 +22,11 @@ class DataProviderExecutor(Executor): Class DataProviderExecutor: is an executor for DataProvider instances. """ - def __init__(self, data_providers: List[DataProvider] = None, max_workers: int = DEFAULT_MAX_WORKERS): + def __init__( + self, + data_providers: List[DataProvider] = None, + max_workers: int = DEFAULT_MAX_WORKERS + ): super(DataProviderExecutor, self).__init__(max_workers=max_workers) self._registered_data_providers: List[DataProvider] = [] @@ -38,13 +43,14 @@ def registered_data_providers(self) -> List[DataProvider]: @property def configured(self): - return self._registered_data_providers is not None and len(self._registered_data_providers) > 0 + return self._registered_data_providers is not None \ + and len(self._registered_data_providers) > 0 class DataProviderScheduler(ExecutionScheduler): """ - Data Provider scheduler that will function as a scheduler to make sure it keeps it state across multiple state, - it is defined as a Singleton. + Data Provider scheduler that will function as a scheduler to make sure it + keeps it state across multiple state, it is defined as a Singleton. """ def __init__(self): @@ -70,15 +76,17 @@ def configured(self) -> bool: class DataProvidingState(State, Observer): """ - Represent the data_providers state of a bot. This state will load all the defined data_providers providers and will - run them. + Represent the data_providers state of a bot. This state will load all \ + the defined data_providers providers and will run them. - If you want to validate the state before transitioning, provide a state validator. + If you want to validate the state before transitioning, provide a + state validator. """ registered_data_providers: List = None - from investing_algorithm_framework.templates.states.strategy_state import StrategyState + from investing_algorithm_framework.templates.states.strategy_state \ + import StrategyState transition_state_class = StrategyState data_provider_scheduler: DataProviderScheduler = None @@ -88,18 +96,25 @@ def __init__(self, context: Context) -> None: self._updated = False self.data_provider_executor = None - if self.registered_data_providers is None or len(self.registered_data_providers) < 1: - raise OperationalException("Data providing state has not any data providers configured") + if self.registered_data_providers is None \ + or len(self.registered_data_providers) < 1: + raise OperationalException( + "Data providing state has not any data providers configured" + ) def _schedule_data_providers(self) -> List[DataProvider]: if not DataProvidingState.data_provider_scheduler: - DataProvidingState.data_provider_scheduler = DataProviderScheduler() + DataProvidingState.data_provider_scheduler = \ + DataProviderScheduler() if not DataProvidingState.data_provider_scheduler.configured: - DataProvidingState.data_provider_scheduler.configure(self.registered_data_providers) + DataProvidingState.data_provider_scheduler.configure( + self.registered_data_providers + ) - planning = DataProvidingState.data_provider_scheduler.schedule_executions() + planning = DataProvidingState.data_provider_scheduler.\ + schedule_executions() planned_data_providers = [] for data_provider in self.registered_data_providers: @@ -109,11 +124,14 @@ def _schedule_data_providers(self) -> List[DataProvider]: return planned_data_providers - def _start_data_providers(self, data_providers: List[DataProvider]) -> None: + def _start_data_providers(self, data_providers: List[DataProvider]) \ + -> None: self.data_provider_executor = DataProviderExecutor( data_providers=data_providers, - max_workers=self.context.settings.get(SETTINGS_MAX_WORKERS, DEFAULT_MAX_WORKERS) + max_workers=self.context.settings.get( + SETTINGS_MAX_WORKERS, DEFAULT_MAX_WORKERS + ) ) if self.data_provider_executor.configured: @@ -136,8 +154,11 @@ def run(self) -> None: time.sleep(1) # Collect all data_providers from the data_providers providers - for data_provider in self.data_provider_executor.registered_data_providers: - logger.info("Data provider: {} finished running".format(data_provider.get_id())) + for data_provider in self.data_provider_executor\ + .registered_data_providers: + logger.info("Data provider: {} finished running".format( + data_provider.get_id()) + ) @synchronized def update(self, observable, **kwargs) -> None: @@ -146,6 +167,3 @@ def update(self, observable, **kwargs) -> None: @staticmethod def register_data_providers(data_providers: List) -> None: DataProvidingState.registered_data_providers = data_providers - - - diff --git a/investing_algorithm_framework/templates/states/ordering_state.py b/investing_algorithm_framework/templates/states/ordering_state.py index 757a69ed..2f159bee 100644 --- a/investing_algorithm_framework/templates/states/ordering_state.py +++ b/investing_algorithm_framework/templates/states/ordering_state.py @@ -2,12 +2,14 @@ from investing_algorithm_framework.core.state import State from investing_algorithm_framework.core.exceptions import OperationalException -from investing_algorithm_framework.templates.order_executors import OrderExecutor +from investing_algorithm_framework.templates.order_executors \ + import OrderExecutor class OrderingState(State): - from investing_algorithm_framework.templates.states.data_providing_state import DataProvidingState + from investing_algorithm_framework.templates.states.data_providing_state \ + import DataProvidingState transition_state_class = DataProvidingState order_executors: List[OrderExecutor] = None @@ -16,7 +18,9 @@ def __init__(self, context) -> None: super(OrderingState, self).__init__(context) if self.order_executors is None or len(self.order_executors) < 1: - raise OperationalException("OrderingState state has not any order executors configured") + raise OperationalException( + "OrderingState state has not any order executors configured" + ) def run(self) -> None: @@ -26,5 +30,3 @@ def run(self) -> None: @staticmethod def register_order_executors(order_executors: List[OrderExecutor]) -> None: OrderingState.order_executors = order_executors - - diff --git a/investing_algorithm_framework/templates/states/setup_state.py b/investing_algorithm_framework/templates/states/setup_state.py index fea8d1c6..9de93a66 100644 --- a/investing_algorithm_framework/templates/states/setup_state.py +++ b/investing_algorithm_framework/templates/states/setup_state.py @@ -8,7 +8,8 @@ class SetupState(State): - from investing_algorithm_framework.templates.states.data_providing_state import DataProvidingState + from investing_algorithm_framework.templates.states.data_providing_state \ + import DataProvidingState transition_state_class = DataProvidingState def __init__(self, context): @@ -17,18 +18,13 @@ def __init__(self, context): def run(self) -> None: """ Running the setup state. - - During execution a validation will be performed on: - - - DataProviders """ # Load the settings if not self.context.settings.configured: raise ImproperlyConfigured( - "Settings module is not specified, make sure you have setup a investing_algorithm_framework project " - "and the investing_algorithm_framework is valid or that you have specified the settings module in your " - "manage.py-template file" + "Settings module is not specified, make sure you have setup " + "a investing_algorithm_framework project and the " + "investing_algorithm_framework is valid or that you have " + "specified the settings module in your manage.py-template file" ) - - diff --git a/investing_algorithm_framework/templates/states/strategy_state.py b/investing_algorithm_framework/templates/states/strategy_state.py index 8fe5c753..54edec0f 100644 --- a/investing_algorithm_framework/templates/states/strategy_state.py +++ b/investing_algorithm_framework/templates/states/strategy_state.py @@ -8,7 +8,8 @@ from investing_algorithm_framework.core.workers import Worker from investing_algorithm_framework.core.executors import Executor from investing_algorithm_framework.core.events import Observer -from investing_algorithm_framework.configuration.config_constants import DEFAULT_MAX_WORKERS, SETTINGS_MAX_WORKERS +from investing_algorithm_framework.configuration.config_constants \ + import DEFAULT_MAX_WORKERS, SETTINGS_MAX_WORKERS logger = logging.getLogger(__name__) @@ -18,7 +19,11 @@ class StrategyExecutor(Executor): Class StrategyExecutor: is an executor for Strategy instances. """ - def __init__(self, strategies: List = None, max_workers: int = DEFAULT_MAX_WORKERS): + def __init__( + self, + strategies: List = None, + max_workers: int = DEFAULT_MAX_WORKERS + ): super(StrategyExecutor, self).__init__(max_workers=max_workers) self._registered_strategies: List = [] @@ -35,7 +40,8 @@ def registered_strategies(self) -> List: @property def configured(self): - return self._registered_strategies is not None and len(self._registered_strategies) > 0 + return self._registered_strategies is not None \ + and len(self._registered_strategies) > 0 class StrategyScheduler(ExecutionScheduler): @@ -80,7 +86,9 @@ def _schedule_strategies(self) -> List: StrategyState.strategy_scheduler = StrategyScheduler() if not StrategyState.strategy_scheduler.configured: - StrategyState.strategy_scheduler.configure(self.registered_strategies) + StrategyState.strategy_scheduler.configure( + self.registered_strategies + ) planning = StrategyState.strategy_scheduler.schedule_executions() planned_strategies = [] @@ -96,7 +104,9 @@ def _start_strategies(self, strategies: List) -> None: self.strategy_executor = StrategyExecutor( strategies=strategies, - max_workers=self.context.settings.get(SETTINGS_MAX_WORKERS, DEFAULT_MAX_WORKERS) + max_workers=self.context.settings.get( + SETTINGS_MAX_WORKERS, DEFAULT_MAX_WORKERS + ) ) if self.strategy_executor.configured: @@ -109,7 +119,9 @@ def _start_strategies(self, strategies: List) -> None: def run(self) -> None: if self.registered_strategies is None: - raise OperationalException("Data providing state has not any data providers configured") + raise OperationalException( + "Data providing state has not any data providers configured" + ) # Schedule the strategies providers planned_strategies = self._schedule_strategies() @@ -123,7 +135,9 @@ def run(self) -> None: # Collect all strategies from the strategies providers for strategies in self.strategy_executor.registered_strategies: - logger.info("Strategy: {} finished running".format(strategies.get_id())) + logger.info("Strategy: {} finished running".format( + strategies.get_id()) + ) def update(self, observable, **kwargs) -> None: self._updated = True @@ -133,6 +147,6 @@ def register_strategies(strategies: List) -> None: StrategyState.registered_strategies = strategies def get_transition_state_class(self): - from investing_algorithm_framework.templates.states.ordering_state import OrderingState + from investing_algorithm_framework.templates.states.ordering_state \ + import OrderingState return OrderingState - diff --git a/investing_algorithm_framework/templates/strategies/__init__.py b/investing_algorithm_framework/templates/strategies/__init__.py index 5e8f00c2..62950d1a 100644 --- a/investing_algorithm_framework/templates/strategies/__init__.py +++ b/investing_algorithm_framework/templates/strategies/__init__.py @@ -1 +1,4 @@ -from investing_algorithm_framework.templates.strategies.strategy import Strategy +from investing_algorithm_framework.templates.strategies.strategy \ + import Strategy + +__all__ = ['Strategy'] diff --git a/investing_algorithm_framework/templates/strategies/strategy.py b/investing_algorithm_framework/templates/strategies/strategy.py index b5302493..61890a8a 100644 --- a/investing_algorithm_framework/templates/strategies/strategy.py +++ b/investing_algorithm_framework/templates/strategies/strategy.py @@ -19,5 +19,3 @@ def apply_strategy(self) -> None: def work(self, **kwargs: Dict[str, Any]) -> None: logger.info("Starting strategy {}".format(self.get_id())) self.apply_strategy() - - diff --git a/investing_algorithm_framework/utils/version.py b/investing_algorithm_framework/utils/version.py index 2750b711..65d2c5b4 100644 --- a/investing_algorithm_framework/utils/version.py +++ b/investing_algorithm_framework/utils/version.py @@ -13,7 +13,8 @@ def get_main_version(version=None): def get_complete_version(version=None): """ - Return a tuple of the investing algorithm framework version. If version argument is non-empty, + Return a tuple of the investing algorithm framework version. If version + argument is non-empty, check for correctness of the tuple provided. """ if version is None: diff --git a/setup.py b/setup.py index ec4acf46..2865d742 100644 --- a/setup.py +++ b/setup.py @@ -12,8 +12,8 @@ description="A framework for creating an investment algorithm", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/investing-algorithms/investing-algorithm-framework.git", - download_url='https://github.com/investing-algorithms/investing-algorithm-framework/archive/v0.1.tar.gz', + url="https://github.com/coding-kitties/investing-algorithm-framework.git", + download_url='https://github.com/coding-kitties/investing-algorithm-framework/archive/v0.1.tar.gz', packages=setuptools.find_packages(exclude=['tests', 'tests.*']), keywords=['INVESTING', 'BOT', 'ALGORITHM', 'FRAMEWORK'], classifiers=[ diff --git a/tests/management/test_create_standard_algo.py b/tests/management/test_create_standard_algo.py index f78889b8..32435b47 100644 --- a/tests/management/test_create_standard_algo.py +++ b/tests/management/test_create_standard_algo.py @@ -1,7 +1,10 @@ import os +import pytest from tempfile import TemporaryDirectory from tests.resources.utils import random_string -from investing_algorithm_framework.management.commands.create_standard_algo import CreateStandardAlgorithmCommand +from investing_algorithm_framework.management.commands.create_standard_algo \ + import CreateStandardAlgorithmCommand +from investing_algorithm_framework.management.command import CommandError class TestCreateAlgorithm: @@ -22,24 +25,110 @@ def test(self): assert os.path.isfile(os.path.join(tempdir, 'manage.py')) # Check if all directories are present - assert os.path.isdir(os.path.join(tempdir, self.project_name, 'data_providers')) - assert os.path.isdir(os.path.join(tempdir, self.project_name, 'order_executors')) - assert os.path.isdir(os.path.join(tempdir, self.project_name, 'strategies')) - assert os.path.isdir(os.path.join(tempdir, self.project_name, 'configuration')) + assert os.path.isdir( + os.path.join(tempdir, self.project_name, 'data_providers') + ) + assert os.path.isdir( + os.path.join(tempdir, self.project_name, 'order_executors') + ) + assert os.path.isdir( + os.path.join(tempdir, self.project_name, 'strategies') + ) + assert os.path.isdir( + os.path.join(tempdir, self.project_name, 'configuration') + ) # Check if all configuration files are present - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'configuration', '__init__.py')) - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'configuration', 'context.py')) - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'configuration', 'settings.py')) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'configuration', + '__init__.py') + ) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'configuration', + 'context.py') + ) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'configuration', + 'settings.py') + ) # Check if all data provider files are present - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'data_providers', '__init__.py')) - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'data_providers', 'data_providers.py')) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'data_providers', + '__init__.py') + ) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'data_providers', + 'data_providers.py') + ) # Check if all strategies files are present - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'strategies', '__init__.py')) - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'strategies', 'strategies.py')) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'strategies', + '__init__.py') + ) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'strategies', + 'strategies.py' + ) + ) # Check if all order_executors files are present - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'order_executors', '__init__.py')) - assert os.path.isfile(os.path.join(tempdir, self.project_name, 'order_executors', 'order_executors.py')) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'order_executors', + '__init__.py' + ) + ) + assert os.path.isfile( + os.path.join( + tempdir, + self.project_name, + 'order_executors', + 'order_executors.py' + ) + ) + + def test_project_name(self): + + with TemporaryDirectory() as tempdir: + command = CreateStandardAlgorithmCommand() + command.handle(name=random_string(10), directory=tempdir) + + with TemporaryDirectory() as tempdir: + command = CreateStandardAlgorithmCommand() + command.handle( + name=random_string(10) + '_' + random_string(10), + directory=tempdir + ) + + with pytest.raises(CommandError): + with TemporaryDirectory() as tempdir: + command = CreateStandardAlgorithmCommand() + command.handle( + name=random_string(10) + ' ' + random_string(10), + directory=tempdir + )