Skip to content

Commit

Permalink
Merge pull request #116 from Trading-Bot/master - Version 0.0.9-alpha
Browse files Browse the repository at this point in the history
[Alpha Version] Version 0.0.9-alpha
  • Loading branch information
Herklos authored Apr 30, 2018
2 parents 9b55490 + 41bacbf commit 6e4a5f5
Show file tree
Hide file tree
Showing 54 changed files with 1,361 additions and 361 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ ENV/
.mypy_cache/

# config
config.json
config/config.json

# modules
/.gitmodules
Expand Down
4 changes: 1 addition & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ notifications:
email: false
install:
- ./docs/install/install-ta-lib.sh
- cp ./docs/install/config_test.json ./config/config.json
- cp ./docs/install/evaluator_config_test.json ./config/evaluator_config.json
- python -m pip install -r requirements.txt
script: pytest tests/
script: pytest --cov-report term --cov=. tests/


18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CryptoBot [![Codacy Badge](https://api.codacy.com/project/badge/Grade/c83a127c42ba4a389ca86a92fba7c53c)](https://www.codacy.com/app/paul.bouquet/CryptoBot?utm_source=github.com&utm_medium=referral&utm_content=Trading-Bot/CryptoBot&utm_campaign=Badge_Grade) [![Build Status](https://api.travis-ci.org/Trading-Bot/CryptoBot.svg?branch=dev)](https://travis-ci.org/Trading-Bot/CryptoBot)

#### Version 0.0.8-alpha ([changelog](https://github.com/Trading-Bot/CryptoBot/tree/master/docs/CHANGELOG.md))
#### Version 0.0.9-alpha ([changelog](https://github.com/Trading-Bot/CryptoBot/tree/master/docs/CHANGELOG.md))

## Disclaimer
This software is for educational purposes only. Do not risk money which
Expand Down Expand Up @@ -36,15 +36,15 @@ Rename config/default_config.json to config/config.json

### More configuration
See [Configuration Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Configuration)
```
```json
"crypto_currencies": {
"Bitcoin": {
"pairs" : ["BTC/USDT"]
}
}
```
See [Exchanges Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Exchanges)
```
```json
"exchanges": {
"binance": {
"api-key": "",
Expand All @@ -53,21 +53,21 @@ See [Exchanges Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Exchanges)
}
```
See [Notifications Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Notifications)
```
```json
"notification":{
"enabled": true,
"type": [1, 2]
}
```
See [Trader Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Trader)
```
```json
"trader":{
"enabled": false,
"risk": 0.5
}
```
See [Simulator Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Simulator)
```
```json
"simulator":{
"enabled": true,
"risk": 0.5,
Expand All @@ -78,12 +78,12 @@ See [Simulator Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Simulator)
}
```
See [Services Wiki](https://github.com/Herklos-Bots/CryptoBot/wiki/Services)
```
```json
"services": {}
```

## Usage
```
```bash
python main.py
```
## Customize you CryptoBot !
Expand All @@ -110,7 +110,7 @@ More information and examples on the [wiki](https://github.com/Trading-Bot/Crypt

## Testing
Use *pytest* command in the root folder :
```
```bash
pytest
```

Expand Down
12 changes: 11 additions & 1 deletion backtesting/backtesting.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
from config.cst import CONFIG_BACKTESTING, CONFIG_ENABLED_OPTION
import time

from config.cst import *


class Backtesting:
def __init__(self, config):
self.config = config
self.begin_time = time.time()

def end(self):
# TODO : temp
raise Exception("End of simulation in {0} sec".format(time.time() - self.begin_time))

@staticmethod
def enabled(config):
return CONFIG_BACKTESTING in config and config[CONFIG_BACKTESTING][CONFIG_ENABLED_OPTION]
65 changes: 33 additions & 32 deletions backtesting/collector/data_collector.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
import logging
import threading
import time
from logging.config import fileConfig

import ccxt

from config.config import load_config
from config.cst import CONFIG_ENABLED_OPTION, CONFIG_DATA_COLLECTOR, CONFIG_EXCHANGES, CONFIG_DATA_PATH, \
DATA_COLLECTOR_REFRESHER_TIME, TimeFrames, TimeFramesMinutes, MINUTE_TO_SECONDS
from config.cst import *
from trading import Exchange


Expand Down Expand Up @@ -48,33 +45,39 @@ def enabled(config):
return CONFIG_DATA_COLLECTOR in config and config[CONFIG_DATA_COLLECTOR][CONFIG_ENABLED_OPTION]


class DataCollectorParser:
@staticmethod
def parse(file):
with open(CONFIG_DATA_COLLECTOR_PATH + file) as file_to_parse:
file_content = json.loads(file_to_parse.read())

return file_content


class ExchangeDataCollector(threading.Thread):
def __init__(self, config, exchange):
super().__init__()
self.config = config
self.exchange = exchange
self.symbol = self.config[CONFIG_DATA_COLLECTOR][CONFIG_SYMBOL]
self.keep_running = True
self.file = None
self.file_content = None
self.file_name = "{0}_{1}.data".format(self.exchange.get_name(), time.strftime("%Y%m%d_%H%M%S"))
self._data_updated = False
self.file_name = "{0}_{1}_{2}.data".format(self.exchange.get_name(),
self.symbol.replace("/", "_"),
time.strftime("%Y%m%d_%H%M%S"))
self.time_frame_update = {}
self.logger = logging.getLogger(self.__class__.__name__)

# TEMP
self.symbol = "BTC/USDT"

def stop(self):
self.keep_running = False

def update_file(self):
file_content_json = {}
for time_frame in self.file_content:
file_content_json[time_frame] = self.file_content[time_frame].to_json()

json.dump(file_content_json, self.file)
with open(CONFIG_DATA_COLLECTOR_PATH + self.file_name, 'w') as json_file:
json.dump(self.file_content, json_file)

def prepare_file(self):
self.file = open(CONFIG_DATA_PATH + self.file_name, 'w')
self.file_content = {}
for time_frame in TimeFrames:
self.file_content[time_frame.value] = None
Expand All @@ -87,8 +90,8 @@ def prepare(self):
# write all available data for this time frame
self.file_content[time_frame.value] = self.exchange.get_symbol_prices(self.symbol,
time_frame,
None)

limit=None,
data_frame=False)
self.time_frame_update[time_frame] = time.time()
self.update_file()

Expand All @@ -100,22 +103,20 @@ def run(self):

for time_frame in TimeFrames:
if self.exchange.time_frame_exists(time_frame.value):
if (time.time() - now) >= TimeFramesMinutes[time_frame] * MINUTE_TO_SECONDS:
self.file_content[time_frame.value].concat(self.exchange.get_symbol_prices(self.symbol,
time_frame,
1))
self.time_frame_update[time_frame] = time.time()
if now - self.time_frame_update[time_frame] >= TimeFramesMinutes[time_frame] * MINUTE_TO_SECONDS:
result_df = self.exchange.get_symbol_prices(self.symbol,
time_frame,
limit=1,
data_frame=False)[0]

self.file_content[time_frame.value].append(result_df)
self._data_updated = True
self.time_frame_update[time_frame] = now
self.logger.info("{0} : {1} updated".format(self.exchange.get_name(), time_frame))

self.update_file()
time.sleep(DATA_COLLECTOR_REFRESHER_TIME)


if __name__ == '__main__':
fileConfig('config/logging_config.ini')
global_config = load_config()
if self._data_updated:
self.update_file()
self._data_updated = False

if DataCollector.enabled(global_config):
data_collector_inst = DataCollector(global_config)
# data_collector_inst.stop()
data_collector_inst.join()
final_sleep = DATA_COLLECTOR_REFRESHER_TIME - (time.time() - now)
time.sleep(final_sleep if final_sleep >= 0 else 0)
161 changes: 160 additions & 1 deletion backtesting/exchange_simulator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,166 @@
import random

from backtesting.backtesting import Backtesting
from backtesting.collector.data_collector import DataCollectorParser
from config.cst import *
from trading import Exchange


class ExchangeSimulator(Exchange):
def __init__(self, config, exchange_type):
super().__init__(config, exchange_type)
super().__init__(config, exchange_type, connect=False)

if CONFIG_BACKTESTING not in self.config:
raise Exception("Backtesting config not found")

if CONFIG_DATA_COLLECTOR not in self.config:
raise Exception("Data collector config not found")

self.data = DataCollectorParser.parse(self.config[CONFIG_BACKTESTING][CONFIG_BACKTESTING_DATA_FILE])
self.backtesting = Backtesting(config)

# todo temp
self.symbol = self.config[CONFIG_DATA_COLLECTOR][CONFIG_SYMBOL]
self.config_time_frames = self.get_config_time_frame(config)

self.time_frame_get_times = {}
self.tickers = {}
self.fetched_trades = {}
self.fetched_trades_counter = {}

self.DEFAULT_LIMIT = 100
self.MIN_LIMIT = 20

self.MIN_TIME_FRAME_ENABLED = self.find_config_min_time_frame()
self.DEFAULT_TIME_FRAME_RECENT_TRADE_CREATOR = self.MIN_TIME_FRAME_ENABLED
self.CREATED_TRADES_BY_TIME_FRAME = 10
self.DEFAULT_TIME_FRAME_TICKERS_CREATOR = self.MIN_TIME_FRAME_ENABLED
self.CREATED_TICKER_BY_TIME_FRAME = 1

self.prepare()

def prepare(self):
# create symbol tickers
self.tickers[self.symbol] = self.create_tickers()

# create symbol last trades
self.fetched_trades[self.symbol] = self.create_recent_trades()
self.fetched_trades_counter[self.symbol] = 0

# create get times
for time_frame in TimeFrames:
self.time_frame_get_times[time_frame.value] = 0

def should_update_data(self, time_frame):
previous_time_frame = self.get_previous_time_frame(time_frame, time_frame)
previous_time_frame_sec = TimeFramesMinutes[previous_time_frame]
previous_time_frame_updated_times = self.time_frame_get_times[previous_time_frame.value]
current_time_frame_sec = TimeFramesMinutes[time_frame]
current_time_frame_updated_times = self.time_frame_get_times[time_frame.value]

if previous_time_frame_updated_times - (
current_time_frame_updated_times * (current_time_frame_sec / previous_time_frame_sec)) >= 0:
return True
else:
return False

def should_update_recent_trades(self, symbol):
if symbol in self.fetched_trades_counter:
if self.fetched_trades_counter[symbol] < self.time_frame_get_times[self.MIN_TIME_FRAME_ENABLED.value]:
self.fetched_trades_counter[symbol] += 1
return True
return False

def get_previous_time_frame(self, time_frame, origin_time_frame):
current_time_frame_index = TimeFramesRank.index(time_frame)

if current_time_frame_index > 0:
previous = TimeFramesRank[current_time_frame_index - 1]
if previous in self.config_time_frames:
return previous
else:
return self.get_previous_time_frame(previous, origin_time_frame)
else:
if time_frame in self.config_time_frames:
return time_frame
else:
return origin_time_frame

def find_config_min_time_frame(self):
for tf in TimeFramesRank:
if tf in self.config_time_frames:
try:
return TimeFrames(tf)
except ValueError:
pass
return TimeFrames.ONE_MINUTE

def get_symbol_prices(self, symbol, time_frame, limit=None, data_frame=True):
result = self.extract_data_with_limit(time_frame)
self.time_frame_get_times[time_frame.value] += 1

if data_frame:
return self.candles_array_to_data_frame(result)
else:
return result

# Will use the One Minute time frame
def create_tickers(self):
full = self.data[self.DEFAULT_TIME_FRAME_TICKERS_CREATOR.value]
created_tickers = []
for tf in full:
max_price = max(tf[PriceIndexes.IND_PRICE_OPEN.value], tf[PriceIndexes.IND_PRICE_CLOSE.value])
min_price = min(tf[PriceIndexes.IND_PRICE_OPEN.value], tf[PriceIndexes.IND_PRICE_CLOSE.value])
for i in range(0, self.CREATED_TICKER_BY_TIME_FRAME):
created_tickers.append(random.uniform(min_price, max_price))
return created_tickers

def create_recent_trades(self):
full = self.data[self.DEFAULT_TIME_FRAME_RECENT_TRADE_CREATOR.value]
created_trades = []
for tf in full:
max_price = max(tf[PriceIndexes.IND_PRICE_OPEN.value], tf[PriceIndexes.IND_PRICE_CLOSE.value])
min_price = min(tf[PriceIndexes.IND_PRICE_OPEN.value], tf[PriceIndexes.IND_PRICE_CLOSE.value])
for i in range(0, self.CREATED_TRADES_BY_TIME_FRAME):
created_trades.append(
{
"price": random.uniform(min_price, max_price)
}
)
return created_trades

def extract_indexes(self, array, index, factor=1, max_value=None):
max_limit = len(array)
index *= factor
max_count = max_value if max_value is not None else self.DEFAULT_LIMIT
min_count = max_value if max_value is not None else self.MIN_LIMIT

if max_limit - (index + max_count) <= min_count:
self.backtesting.end()

elif index + max_count >= max_limit:
return array[index::]
else:
return array[index:index + max_count]

def extract_data_with_limit(self, time_frame):
return self.extract_indexes(self.data[time_frame.value], self.time_frame_get_times[time_frame.value])

def get_recent_trades(self, symbol):
return self.extract_indexes(self.fetched_trades[symbol],
self.time_frame_get_times[
self.DEFAULT_TIME_FRAME_RECENT_TRADE_CREATOR.value],
factor=self.CREATED_TRADES_BY_TIME_FRAME)

def get_all_currencies_price_ticker(self):
return {
self.symbol: {
"symbol": self.symbol,
ExchangeConstantsTickersColumns.LAST.value:
self.extract_indexes(self.tickers[self.symbol],
self.time_frame_get_times[
self.DEFAULT_TIME_FRAME_TICKERS_CREATOR.value],
factor=self.CREATED_TICKER_BY_TIME_FRAME,
max_value=1)[0]
}
}
Loading

0 comments on commit 6e4a5f5

Please sign in to comment.