Skip to content

Commit 63c6843

Browse files
author
investingbots
authored
Merge pull request #7 from investingbots/feature_performance_mode
Feature performance mode
2 parents de8f94f + ead8645 commit 63c6843

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+964
-994
lines changed

README.md

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,46 @@
1-
# Value investing bot
1+
# Investing bot
22

3-
The value investing bot is a free and open source investing bot written in Python. It is designed to support all
4-
major exchanges and be controlled via Telegram. It contains backtesting, plotting and money management tools.
3+
The investing bot is a free and open source investing bot written in Python. The goal is to give you a configurable bot
4+
where you can decide on how you implement your data providers, strategies, and brokers/exchanges. Also we want to allow
5+
you to let your bot facilitate multiple users.
6+
7+
It is designed to be controlled via Telegram. As of now, we are aiming to make the configuration of the different
8+
components by the use of plugins. Please see the documentation on how to make your own plugin.
59

610
### Disclaimer
7-
This software is for educational purposes only. Do not risk money which you are afraid to lose.
11+
This software is for educational purposes only. Do not risk money which you are afraid to lose. We can't stress this
12+
enough: BEFORE YOU START USING MONEY WITH THE BOT, MAKE SURE THAT YOU TESTED YOU STRATEGIES AND DATA PROVIDERS.
813
USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
914

1015
Always start by running a investing bot in Dry-run and do not engage money before you understand how it works and what profit/loss you should expect.
1116

12-
We strongly recommend you to have coding and Python knowledge. Do not hesitate to read the source code and understand the mechanism of this bot.
17+
We strongly recommend you to have coding and Python knowledge, or trust the people that created the plugins your using.
18+
Do not hesitate to read the source code and understand the mechanism of this bot or the plugin you're using.
1319

14-
Exchange marketplaces supported
20+
Brokers/Exchange marketplaces supported
1521
------
1622
Will be updated in the future
1723

24+
1825
Documentation
1926
------
2027
Will be updated in the future
2128

2229
## Features
2330

24-
- [x] **Based on Python 3.6+**: For botting on any operating system - Windows, macOS and Linux.
31+
- [x] **Based on Python 3.6+**: Support for all operating systems - Windows, macOS and Linux.
2532
- [x] **Persistence**: Persistence is achieved through sqlite.
2633
- [ ] **Dry-run**: Run the bot without playing money.
34+
- [ ] **REST API**: Manage the bot with the use of a REST API.
2735
- [ ] **Backtesting**: Run a simulation of your buy/sell strategy.
28-
- [x] **Tracking of companies**: Add, remove and track companies to track.
29-
- [ ] **Stock selection**: Select which stocks you want to trade or want to track by creating a subselection.
30-
- [x] **Manageable via Telegram**: Manage the bot with Telegram.
36+
- [ ] **Manageable via Telegram**: Manage the bot with Telegram.
3137
- [ ] **Display profit/loss**: Display your profit/loss.
3238
- [ ] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss.
3339
- [ ] **Performance status report**: Provide a performance status of your current trades.
3440

3541
## Quick start
3642

37-
The value investing bot provides a Linux/macOS script to install all dependencies and help you to configure the bot.
43+
The investing bot provides a Linux/macOS script to install all dependencies and help you to configure the bot.
3844

3945
The script will come as a future update
4046

@@ -58,10 +64,6 @@ optional arguments:
5864

5965
Telegram is not mandatory. However, this is a great way to control your bot.
6066

61-
- `/help`: Show help message
62-
- `/version`: Show version
63-
- `/add_tickers`: Add tickers to the registry, so they can be analyzed
64-
6567

6668
## Development branches
6769

bot/configuration/resolvers.py

Lines changed: 40 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,66 @@
11
from typing import Dict, Any, List
22

3-
from bot.strategies import Strategy
43
from bot.data import DataProvider
5-
from bot import DependencyException, OperationalException
6-
from bot.remote_loaders import StrategyRemoteLoader, DataProviderRemoteLoader
4+
from bot.constants import TimeUnit
5+
from bot import DependencyException
6+
from bot.remote_loaders import DataProviderRemoteLoader
77

88

99
def get_data_provider_configurations(config: Dict[str, Any]) -> List[Dict[str, Any]]:
10-
data_provider_configurations = config.get('data_providers', {})
10+
"""
11+
Function that validates and loads all the data provider definitions for the given configuration
12+
"""
13+
data_providers_configuration = config.get('data_providers', None)
1114

12-
entries = []
15+
if data_providers_configuration is None:
16+
raise DependencyException("Data providers are not defined in the config. Please make sure that you "
17+
"define some data providers. If you have difficulties creating a data provider, "
18+
"please see the documentation for examples.")
1319

14-
if not data_provider_configurations:
15-
raise DependencyException(
16-
"Could not resolve data providers, please provide the data provider configurations in your config file. "
17-
"You could also use de default data providers, that can be found in the data/data_provider/templates"
18-
" directory. If you have difficulties creating your own data provider, please see the documentation"
19-
)
20-
21-
for data_provider_entry in data_provider_configurations.keys():
22-
entry = {
23-
'key': data_provider_entry,
24-
'class_name': data_provider_configurations[data_provider_entry].get('class_name', None),
25-
'enabled': bool(data_provider_configurations[data_provider_entry].get('enabled', False)),
26-
'plugin': bool(data_provider_configurations[data_provider_entry].get('plugin', False))
27-
}
28-
entries.append(entry)
29-
30-
return entries
31-
32-
33-
def get_strategy_configurations(config: Dict[str, Any]) -> List[Dict[str, Any]]:
34-
strategy_configurations = config.get('strategies', {})
20+
for data_provider_config in data_providers_configuration:
3521

36-
entries = []
37-
38-
if not strategy_configurations:
39-
raise DependencyException(
40-
"Could not resolve strategies, please provide the strategy configurations in your config file. "
41-
"You could also use de default strategies, that can be found in the "
42-
"strategies/strategy/templates directory. If you have difficulties creating "
43-
"your own strategies, please see the documentation"
22+
assert data_provider_config.get('class_name', None) is not None, (
23+
"Expected data provider to define a class_name. Please fix your data provider configuration."
4424
)
4525

46-
for strategy_entry in strategy_configurations.keys():
47-
entry = {
48-
'key': strategy_entry,
49-
'class_name': strategy_configurations[strategy_entry].get('class_name', None),
50-
'enabled': bool(strategy_configurations[strategy_entry].get('enabled', False)),
51-
'plugin': bool(strategy_configurations[strategy_entry].get('plugin', False))
52-
}
53-
entries.append(entry)
54-
55-
return entries
56-
57-
58-
def get_data_provider_plugins(data_provider_configurations: List[Dict[str, Any]]) -> List[DataProvider]:
59-
remote_loader = DataProviderRemoteLoader()
60-
data_providers = []
61-
62-
for entry in data_provider_configurations:
63-
64-
if not entry.get('enabled', False):
65-
continue
26+
assert data_provider_config.get('schedule', None) is not None, (
27+
"Expected data provider {} to define an schedule. Please fix your data provider "
28+
"configuration.".format(data_provider_config.get('class_name'))
29+
)
6630

67-
if not entry.get('key', None):
68-
raise DependencyException("Configured data provider must specify a identifier name")
31+
if not TimeUnit.ALWAYS.equals(data_provider_config.get('schedule')):
6932

70-
if not entry.get('class_name', None):
71-
raise DependencyException(
72-
"Configured data provider {} must specify a class name corresponding "
73-
"to the data provider implementation".format(entry)
33+
assert data_provider_config.get('schedule').get('time_unit', None) is not None, (
34+
"Expected data provider {} to define an schedule time_unit. Please fix your data provider "
35+
"configuration.".format(data_provider_config.get('class_name'))
7436
)
7537

76-
if not bool(entry.get('plugin', False)):
77-
continue
78-
79-
data_provider: DataProvider = remote_loader.load_data_provider(entry.get('class_name'))
80-
data_providers.append(data_provider)
81-
82-
return data_providers
83-
84-
85-
def get_data_provider_templates(data_provider_configurations: List[Dict[str, Any]]) -> List[DataProvider]:
86-
data_providers = []
87-
88-
for entry in data_provider_configurations:
89-
90-
if not entry.get('key', None):
91-
raise DependencyException("Configured data provider must specify a identifier name")
92-
93-
if bool(entry.get('plugin', False)):
94-
continue
95-
96-
if entry.get("key") == "":
97-
continue
98-
99-
# data_provider: DataProvider = remote_loader.load_data_provider(entry.get('class_name'))
100-
# data_providers.append(data_provider)
101-
102-
return None
103-
104-
105-
def get_strategy_plugins(strategy_configurations: List[Dict[str, Any]]) -> List[Strategy]:
106-
remote_loader = StrategyRemoteLoader()
107-
strategies = []
108-
109-
for entry in strategy_configurations:
110-
111-
if not entry.get('enabled', False):
112-
continue
113-
114-
if not entry.get('key', None):
115-
raise DependencyException("Configured strategy must specify a identifier name")
116-
117-
if not entry.get('class_name', None):
118-
raise DependencyException(
119-
"Configured strategy {} must specify a class name corresponding "
120-
"to the strategy implementation".format(entry)
38+
assert data_provider_config.get('schedule').get('interval', None) is not None, (
39+
"Expected data provider {} to define an schedule interval. Please fix your data provider "
40+
"configuration.".format(data_provider_config.get('class_name'))
12141
)
12242

123-
if not bool(entry.get('plugin', False)):
124-
continue
125-
126-
strategy: Strategy = remote_loader.load_strategy(entry.get('class_name'))
127-
strategies.append(strategy)
128-
129-
return strategies
130-
131-
132-
def get_strategy_templates(strategy_configurations: List[Dict[str, Any]]) -> List[Strategy]:
133-
strategies = []
43+
assert data_provider_config.get('plugin', None) is not None, (
44+
"Expected provider for data provider {} to define plugin flag. Please fix your data provider "
45+
"configuration. If you make use of one of the templates set the flag to "
46+
"false.".format(data_provider_config.get('class_name'))
47+
)
13448

135-
for entry in strategy_configurations:
49+
return data_providers_configuration
13650

137-
if not entry.get('key', None):
138-
raise DependencyException("Configured data provider must specify a identifier name")
13951

140-
if bool(entry.get('plugin', False)):
141-
continue
52+
def load_data_provider(data_provider_configuration: Dict[str, str]) -> DataProvider:
14253

143-
if entry.get("key") == "":
144-
continue
54+
class_name = data_provider_configuration.get('class_name')
55+
plugin = bool(data_provider_configuration.get('plugin'))
56+
data_provider: DataProvider = None
14557

146-
# data_provider: DataProvider = remote_loader.load_data_provider(entry.get('class_name'))
147-
# data_providers.append(data_provider)
58+
if plugin:
59+
remote_loader = DataProviderRemoteLoader()
60+
return remote_loader.load_data_provider(class_name)
61+
else:
62+
return None
14863

149-
return None
15064

15165

15266

bot/constants.py

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,74 @@
11
from enum import Enum
2-
from bot.settings import BASE_DIR, PLUGIN_STRATEGIES_DIR, PLUGIN_DATA_PROVIDERS_DIR
2+
3+
from bot import OperationalException
4+
from bot.settings import BASE_DIR, PLUGIN_STRATEGIES_DIR, PLUGIN_DATA_PROVIDERS_DIR, TEST_CONFIGURATION_RESOURCES, \
5+
TEST_DATA_PROVIDER_RESOURCES, TEST_RESOURCES
36
"""
47
bot constants
58
"""
9+
10+
# Configuration
611
DEFAULT_CONFIG = 'config.json'
7-
DEFAULT_MAX_WORKERS = 2
812
BASE_DIR = BASE_DIR
13+
14+
# Executor configuration
15+
DEFAULT_MAX_WORKERS = 2
16+
17+
# Strategies
918
PLUGIN_STRATEGIES_DIR = PLUGIN_STRATEGIES_DIR
19+
20+
# Data providers
1021
PLUGIN_DATA_PROVIDERS_DIR = PLUGIN_DATA_PROVIDERS_DIR
1122

23+
# Test resources
24+
TEST_CONFIGURATION_RESOURCES = TEST_CONFIGURATION_RESOURCES
25+
TEST_DATA_PROVIDER_RESOURCES = TEST_DATA_PROVIDER_RESOURCES
26+
TEST_RESOURCES = TEST_RESOURCES
27+
1228

1329
class TimeUnit(Enum):
1430

15-
second = 'SEC',
16-
minute = 'MIN',
17-
hour = 'HR'
31+
SECOND = 'SEC',
32+
MINUTE = 'MIN',
33+
HOUR = 'HR',
34+
ALWAYS = 'ALWAYS'
1835

19-
# Static factory method to convert a string to TimeUnit
36+
# Static factory method to convert a string to time_unit
2037
@staticmethod
2138
def from_string(value: str):
2239

23-
if value in ('SEC' 'sec', 'SECOND', 'second', 'SECONDS', 'seconds'):
24-
return TimeUnit.second
40+
if isinstance(value, str):
41+
42+
if value.lower() in ('sec', 'second', 'seconds'):
43+
return TimeUnit.SECOND
44+
45+
elif value.lower() in ('min', 'minute', 'minutes'):
46+
return TimeUnit.MINUTE
47+
48+
elif value.lower() in ('hr', 'hour', 'hours'):
49+
return TimeUnit.HOUR
50+
51+
elif value.lower() in ('always', 'every', 'continuous', 'every_time'):
52+
return TimeUnit.ALWAYS
53+
else:
54+
raise OperationalException('Could not convert value {} to a time_unit'.format(value))
55+
56+
else:
57+
raise OperationalException("Could not convert non string value to a time_unit")
58+
59+
def equals(self, other):
60+
61+
if isinstance(other, Enum):
62+
return self.value == other.value
63+
else:
64+
65+
try:
66+
time_unit = TimeUnit.from_string(other)
67+
return time_unit == self
68+
except OperationalException:
69+
pass
70+
71+
return other == self.value
72+
2573

26-
elif value in ('MIN', 'min', 'MINUTE', 'minute', 'MINUTES', 'minutes'):
27-
return TimeUnit.minute
2874

29-
elif value in ('HR', 'hr', 'HOUR', 'hour', 'HOURS', 'hour'):
30-
return

bot/context/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
from bot.context.bot_context import BotContext
1+
# from bot.context.setup_state import SetupState
2+
# from bot.context.data_providing_state import DataProvidingState
3+
from bot.context.bot_context import BotContext, ExecutionScheduler

bot/context/analyzing_state.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
import logging
22

33
from bot.context.bot_state import BotState
4-
from bot.context.bot_context import BotContext
54

65
logger = logging.getLogger(__name__)
76

87

98
class AnalyzingState(BotState):
109

11-
def __init__(self):
10+
def __init__(self, context):
1211
logger.info("Initializing analyzing state ...")
1312

13+
super(AnalyzingState, self).__init__(context)
14+
1415
def run(self) -> None:
1516
logger.info("Analyzing state started ...")
1617

1718
from bot.context.data_providing_state import DataProvidingState
18-
context = BotContext()
19-
context.transition_to(DataProvidingState)
20-
context.run()
19+
self.context.transition_to(DataProvidingState)
20+
self.context.run()
2121

2222
def stop(self) -> None:
2323
pass

0 commit comments

Comments
 (0)