Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
language: python
python:
- "3.5"
install:
- pip install -r requirements.txt
script: make test
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.PHONY: test test-register test-release register release

test:
@python3 -m unittest discover -s ./duka/tests -p "test_*"

test-register:
python setup.py register -r pypitest

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@ pip install duka

optional arguments:
-h show help message and exit
-v show program's version number and exit
-d DAY specific day format YYYY-MM-DD (default today)
-s STARTDATE start date format YYYY-MM-DD (default today)
-e ENDDATE end date format YYYY-MM-DD (default today)
-c CANDLE use candles instead of ticks. Accepted values 1M 5M 10M 15M 30M 1H 4H 1D
-c CANDLE use candles instead of ticks. Accepted values M1 M2 M5 M10 M15 M30 H1 H4 D1
-f FOLDER the dowloaded data will be saved in FOLDER (default '.')
-t THREAD number of threads (default 10)
--header include CSV header (default false)
--local-time use local time (default GMT)
```

## Examples
Expand Down
21 changes: 3 additions & 18 deletions duka/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,7 @@ def avg(fetch_times):
return -1


def name(symbol, timeframe, start, end):
ext = ".csv"

for x in dir(TimeFrame):
if getattr(TimeFrame, x) == timeframe:
ts_str = x

name = symbol + "_" + ts_str + "_" + str(start)

if start != end:
name += "_" + str(end)

return name + ext


def app(symbols, start, end, threads, timeframe, folder, header):
def app(symbols, start, end, threads, timeframe, folder, header, local_time):
if start > end:
return
lock = threading.Lock()
Expand All @@ -85,7 +70,7 @@ def do_work(symbol, day, csv):
star_time = time.time()
Logger.info("Fetching day {0}".format(day))
try:
csv.append(day, decompress(day, fetch_day(symbol, day)))
csv.append(day, decompress(day, local_time, fetch_day(symbol, day)))
except Exception as e:
print("ERROR for {0}, {1} Exception : {2}".format(day, symbol, str(e)))
elapsed_time = time.time() - star_time
Expand All @@ -98,7 +83,7 @@ def do_work(symbol, day, csv):

with concurrent.futures.ThreadPoolExecutor(max_workers=threads) as executor:

files = {symbol: CSVDumper(symbol, timeframe, start, end, folder, header) for symbol in symbols}
files = {symbol: CSVDumper(symbol, timeframe, start, end, folder, header, local_time) for symbol in symbols}

for symbol in symbols:
for day in days(start, end):
Expand Down
13 changes: 10 additions & 3 deletions duka/core/csv_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .candle import Candle
from .utils import TimeFrame, stringify, Logger

TEMPLATE_FILE_NAME = "{}-{}_{:02d}_{:02d}-{}_{:02d}_{:02d}.csv"
TEMPLATE_FILE_NAME = "{}-{}_{:02d}_{:02d}-{}_{:02d}_{:02d}-{}.csv"


def format_float(number):
Expand Down Expand Up @@ -39,14 +39,15 @@ def write_candle(writer, candle):


class CSVDumper:
def __init__(self, symbol, timeframe, start, end, folder, header=False):
def __init__(self, symbol, timeframe, start, end, folder, header=False, local_time=False):
self.symbol = symbol
self.timeframe = timeframe
self.start = start
self.end = end
self.folder = folder
self.include_header = header
self.buffer = {}
self.local_time = local_time

def get_header(self):
if self.timeframe == TimeFrame.TICK:
Expand Down Expand Up @@ -75,10 +76,16 @@ def append(self, day, ticks):
if self.timeframe != TimeFrame.TICK:
self.buffer[day].append(Candle(self.symbol, previous_key, self.timeframe, current_ticks))

def get_timezone(self):
if self.local_time:
return "local_time"
else:
return "GMT"

def dump(self):
file_name = TEMPLATE_FILE_NAME.format(self.symbol,
self.start.year, self.start.month, self.start.day,
self.end.year, self.end.month, self.end.day)
self.end.year, self.end.month, self.end.day, self.get_timezone())

Logger.info("Writing {0}".format(file_name))

Expand Down
10 changes: 6 additions & 4 deletions duka/core/processor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import struct
from datetime import timedelta, datetime
from dateutil import tz
from lzma import LZMADecompressor, LZMAError, FORMAT_AUTO
from .utils import is_dst

Expand Down Expand Up @@ -57,16 +58,17 @@ def add_hour(ticks):
return ticks


def normalize(day, ticks):
def normalize(day, local_time, ticks):
def norm(time, ask, bid, volume_ask, volume_bid):
date = datetime(day.year, day.month, day.day) + timedelta(milliseconds=time)
# date.replace(tzinfo=datetime.tzinfo("UTC"))
if local_time:
date.replace(tzinfo=tz.tzlocal())
return date, ask / 100000, bid / 100000, round(volume_ask * 1000000), round(volume_bid * 1000000)

return add_hour(list(map(lambda x: norm(*x), ticks)))


def decompress(day, compressed_buffer):
def decompress(day, local_time, compressed_buffer):
if compressed_buffer.nbytes == 0:
return compressed_buffer
return normalize(day, tokenize(decompress_lzma(compressed_buffer)))
return normalize(day, local_time, tokenize(decompress_lzma(compressed_buffer)))
7 changes: 4 additions & 3 deletions duka/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from duka.core import valid_date, set_up_signals
from duka.core.utils import valid_timeframe, TimeFrame

VERSION = '0.2.0'
VERSION = '0.2.1'


def main():
Expand All @@ -23,9 +23,10 @@ def main():
parser.add_argument('-t', '--thread', type=int, help='number of threads (default 20)', default=5)
parser.add_argument('-f', '--folder', type=str, help='destination folder (default .)', default='.')
parser.add_argument('-c', '--candle', type=valid_timeframe,
help='use candles instead of ticks. Accepted values 1M 5M 10M 15M 30M 1H 4H',
help='use candles instead of ticks. Accepted values M1 M2 M5 M10 M15 M30 H1 H4',
default=TimeFrame.TICK)
parser.add_argument('--header', action='store_true', help='include CSV header (default false)', default=False)
parser.add_argument('--local-time', action='store_true', help='use local time (default GMT)', default=False)
args = parser.parse_args()

if args.startdate is not None:
Expand All @@ -39,7 +40,7 @@ def main():
end = args.day

set_up_signals()
app(args.symbols, start, end, args.thread, args.candle, args.folder, args.header)
app(args.symbols, start, end, args.thread, args.candle, args.folder, args.header, args.local_time)


if __name__ == '__main__':
Expand Down
2 changes: 1 addition & 1 deletion duka/tests/test_dates_generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest
from datetime import date
from ..app.app import days
from duka.app.app import days


class TestDateGenerator(unittest.TestCase):
Expand Down
10 changes: 5 additions & 5 deletions duka/tests/test_find_sunday.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,21 @@ def test_dst_2015(self):
self.assertEqual(end.month, 11)

def test_is_dst(self):
day = datetime.datetime(2015, 4, 5)
day = datetime.date(2015, 4, 5)
self.assertTrue(is_dst(day))

def test_is_not_dst(self):
day = datetime.datetime(2015, 1, 1)
day = datetime.date(2015, 1, 1)
self.assertFalse(is_dst(day))

def test_day_change_is_dst(self):
day = datetime.datetime(2015, 3, 8)
day = datetime.date(2015, 3, 8)
self.assertTrue(is_dst(day))

def test_day_change_back_is_not_dst(self):
day = datetime.datetime(2015, 11, 1)
day = datetime.date(2015, 11, 1)
self.assertFalse(is_dst(day))

def test_is_dst(self):
day = datetime.datetime(2013, 11, 3)
day = datetime.date(2013, 11, 3)
self.assertFalse(is_dst(day))
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests>=2.9.1
python-dateutil
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
from setuptools import setup, find_packages

NAME = "duka"
VERSION = '0.2.0'
VERSION = '0.2.1'

setup(
name=NAME,
packages=find_packages(),
install_requires=['requests>=2.9.1'],
install_requires=['requests>=2.9.1', 'python-dateutil'],
version=VERSION,
description='Dukascopy Bank SA historical data downloader',
author='Giuseppe Pes',
Expand Down