Skip to content

Commit

Permalink
* restructure repo
Browse files Browse the repository at this point in the history
* introduce price command
* add lykkex service
  • Loading branch information
pfeffer90 committed Dec 3, 2017
1 parent 5efb484 commit 74f5b1e
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 83 deletions.
16 changes: 3 additions & 13 deletions README.rst
@@ -1,23 +1,13 @@
skele-cli
lykkex-cli
=========

*A skeleton command line program in Python.*
*Lykke exchange command line tool.*


Purpose
-------

This is a skeleton application which demonstrates how to properly structure a
Python CLI application.

I've done my best to structure this in a way that makes sense for *most* users,
but if you have any feedback, please open a Github issue and I'll take a look.

The idea with this project is that you should be able to use this as a template
for building new CLI apps.

You can fork this project and customize it to your liking, or just use it as a
reference.
Interested in Lykke and loving the command line? This is your project. Interact seamlessly with the Lykkex API via lykkex-cli. Check your balance and send trading orders in one line or automate your trading procedures.


Usage
Expand Down
File renamed without changes.
27 changes: 14 additions & 13 deletions skele/cli.py → lykke/cli.py
@@ -1,24 +1,24 @@
"""
skele
lykkex
Usage:
skele hello
skele -h | --help
skele --version
lykkex balance
lykkex price <asset_id>
lykkex -h | --help
lykkex --version
Options:
-h --help Show this screen.
--version Show version.
Examples:
skele hello
lykkex balance
Help:
For help using this tool, please open an issue on the Github repository:
https://github.com/rdegges/skele-cli
https://github.com/pfeffer90/lykkex-cli
"""


from inspect import getmembers, isclass

from docopt import docopt
Expand All @@ -28,15 +28,16 @@

def main():
"""Main CLI entrypoint."""
import skele.commands
import lykke.commands
options = docopt(__doc__, version=VERSION)

# Here we'll try to dynamically match the command the user is trying to run
# with a pre-defined command class we've already created.
for (k, v) in options.items():
if hasattr(skele.commands, k) and v:
module = getattr(skele.commands, k)
skele.commands = getmembers(module, isclass)
command = [command[1] for command in skele.commands if command[0] != 'Base'][0]
# Note that the below procedure assumes that the command files only hold two classes the base class and the command class, so that importing other classes destroys the scheme.
for (k, v) in options.items():
if hasattr(lykke.commands, k) and v:
module = getattr(lykke.commands, k)
lykke.commands = getmembers(module, isclass)
command = [command[1] for command in lykke.commands if command[0] != 'Base'][0]
command = command(options)
command.run()
2 changes: 2 additions & 0 deletions lykke/commands/__init__.py
@@ -0,0 +1,2 @@
from .balance import *
from .price import *
10 changes: 10 additions & 0 deletions lykke/commands/balance.py
@@ -0,0 +1,10 @@
"""The balance command."""

from .base import Base


class Balance(Base):
"""Get lykkex balance"""

def run(self):
print('Getting balance...')
File renamed without changes.
16 changes: 16 additions & 0 deletions lykke/commands/price.py
@@ -0,0 +1,16 @@
"""The price command."""

import services.lykkex_service as ly
from .base import Base


class Price(Base):
"""Get asset price"""

def run(self):
asset_id = self.options["<asset_id>"]
_, buy_price, buy_volume = ly.LykkexService.get_price(asset_id, 'BUY')
_, sell_price, sell_volume = ly.LykkexService.get_price(asset_id, 'SELL')
print("{} Price Volume".format(asset_id))
print("BUY: {:10.4f} {:10.2f}".format(buy_price, buy_volume))
print("SELL: {:10.4f} {:10.2f}".format(sell_price, -sell_volume))
Empty file.
106 changes: 106 additions & 0 deletions lykke/commands/services/lykkex_service.py
@@ -0,0 +1,106 @@
import datetime
import logging as log

import lykkex

from lykke.commands.services.time_service import get_current_time


class LykkexService(object):
@staticmethod
def get_balance(api_key):
log.info("Retrieve current balance.")
time_stamp = get_current_time()
balance = lykkex.get_balance(api_key)
log.info("Number of assets: {}".format(len(balance)))
for x in range(0, len(balance)):
log.info(format('Current wealth ' + balance[x]['AssetId'].encode() + ': ' + str(balance[x]['Balance'])))
return time_stamp, balance

@staticmethod
def get_pending_orders(api_key):
log.info("Get pending orders.")
time_stamp = get_current_time()
pending_orders = lykkex.get_pending_orders(api_key)
if not pending_orders:
log.info("No pending orders")
return time_stamp, pending_orders

@staticmethod
def send_market_order(api_key, asset_pair, asset, order_action, volume):
log.info("Send market order - {}".format(asset))
time_stamp = get_current_time()
response = lykkex.send_market_order(api_key, asset_pair, asset, order_action, volume)
if response['Error']:
log.info("Error: Market order not successful")
raise RuntimeError("Error in sending market order. Check response {}".format(response))
final_price = response['Result']
log.info("Trade successful at price {}".format(final_price))
return time_stamp, final_price

@staticmethod
def send_limit_order(api_key, asset_pair, asset, price, order_action='BUY', volume='0.1'):
log.info("Send market order - {}".format(asset))
time_stamp = get_current_time()
response = lykkex.send_limit_order(api_key, asset_pair, asset, price, order_action, volume)
log.info("Limit order placed")
order_id = str(response)
return time_stamp, order_id

@staticmethod
def control_limit_order(api_key, order_id):
log.info("Check status of limit order {}", order_id)
time_stamp = get_current_time()
content = lykkex.get_order_status(api_key, order_id)
status = content['Status']
return time_stamp, status

@staticmethod
def get_price(asset_pair_id, side='BUY'):
log.info("Retrieve price: {}".format(side))
time_stamp = get_current_time()
order_book = lykkex.get_order_book(asset_pair_id)
price = LykkexService.get_asset_price(order_book, side)
volume = LykkexService.get_asset_trading_volume(order_book, side)

log.info("Timestamp: {}".format(time_stamp))
log.info("Price: {}".format(price))
return time_stamp, price, volume

def get_latency(self, asset_pair_id):
time_stamp = get_current_time()
order_book = lykkex.get_order_book(asset_pair_id)
time_ob = self.get_time_stamp_from_order_books(order_book)
time_delta = (time_stamp - time_ob).total_seconds()
log.info("System latency: {} secs".format(time_delta))

@staticmethod
def get_asset_trading_volume(order_books, side):
if side == 'BUY':
return order_books[1]['Prices'][0]['Volume']
elif side == 'SELL':
return order_books[0]['Prices'][0]['Volume']
else:
return log.error('No valid input')

@staticmethod
def get_time_stamp_from_order_books(order_books):
time_stamp_ob = order_books[1]['Timestamp']
val = datetime.datetime.strptime(time_stamp_ob, '%Y-%m-%dT%H:%M:%S.%f')
return val

@staticmethod
def get_asset_price(order_books, side):
try:
if side == 'BUY':
price = order_books[1]['Prices'][-1]['Price']
elif side == 'SELL':
price = order_books[0]['Prices'][0]['Price']
except IndexError as e:
log.error("Could not extract price from order books.")
log.error("{}".format(order_books))
raise RuntimeError(e.message)
return price

def __init__(self):
log.info("Initialize Lykkex connector.")
10 changes: 10 additions & 0 deletions lykke/commands/services/time_service.py
@@ -0,0 +1,10 @@
import datetime

TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"


def get_current_time():
return datetime.datetime.now()

def get_current_time_as_string():
return get_current_time().strftime(TIME_FORMAT)
40 changes: 18 additions & 22 deletions setup.py
@@ -1,14 +1,12 @@
"""Packaging settings."""


from codecs import open
from os.path import abspath, dirname, join
from subprocess import call

from setuptools import Command, find_packages, setup

from skele import __version__

from lykke import __version__

this_dir = abspath(dirname(__file__))
with open(join(this_dir, 'README.rst'), encoding='utf-8') as file:
Expand All @@ -28,20 +26,20 @@ def finalize_options(self):

def run(self):
"""Run all tests!"""
errno = call(['py.test', '--cov=skele', '--cov-report=term-missing'])
errno = call(['py.test'])
raise SystemExit(errno)


setup(
name = 'skele',
version = __version__,
description = 'A skeleton command line program in Python.',
long_description = long_description,
url = 'https://github.com/rdegges/skele-cli',
author = 'Randall Degges',
author_email = 'r@rdegges.com',
license = 'UNLICENSE',
classifiers = [
name='lykke',
version=__version__,
description='A skeleton command line program in Python.',
long_description=long_description,
url='https://github.com/rdegges/lykke-cli',
author='Randall Degges',
author_email='r@rdegges.com',
license='UNLICENSE',
classifiers=[
'Intended Audience :: Developers',
'Topic :: Utilities',
'License :: Public Domain',
Expand All @@ -55,16 +53,14 @@ def run(self):
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
],
keywords = 'cli',
packages = find_packages(exclude=['docs', 'tests*']),
install_requires = ['docopt'],
extras_require = {
'test': ['coverage', 'pytest', 'pytest-cov'],
},
entry_points = {
keywords='cli',
packages=find_packages(exclude=['docs', 'tests*']),
install_requires=['docopt'],
tests_require=['coverage', 'pytest', 'pytest-cov'],
entry_points={
'console_scripts': [
'skele=skele.cli:main',
'lykke=lykke.cli:main',
],
},
cmdclass = {'test': RunTests},
cmdclass={'test': RunTests},
)
1 change: 0 additions & 1 deletion skele/commands/__init__.py

This file was deleted.

14 changes: 0 additions & 14 deletions skele/commands/hello.py

This file was deleted.

16 changes: 0 additions & 16 deletions tests/commands/test_hello.py

This file was deleted.

8 changes: 4 additions & 4 deletions tests/test_cli.py
Expand Up @@ -4,19 +4,19 @@
from subprocess import PIPE, Popen as popen
from unittest import TestCase

from skele import __version__ as VERSION
from lykke import __version__ as VERSION


class TestHelp(TestCase):
def test_returns_usage_information(self):
output = popen(['skele', '-h'], stdout=PIPE).communicate()[0]
output = popen(['lykke', '-h'], stdout=PIPE).communicate()[0]
self.assertTrue('Usage:' in output)

output = popen(['skele', '--help'], stdout=PIPE).communicate()[0]
output = popen(['lykke', '--help'], stdout=PIPE).communicate()[0]
self.assertTrue('Usage:' in output)


class TestVersion(TestCase):
def test_returns_version_information(self):
output = popen(['skele', '--version'], stdout=PIPE).communicate()[0]
output = popen(['lykke', '--version'], stdout=PIPE).communicate()[0]
self.assertEqual(output.strip(), VERSION)

0 comments on commit 74f5b1e

Please sign in to comment.