Skip to content

Commit

Permalink
Merge pull request #1 from FuturBroke/dca-delay
Browse files Browse the repository at this point in the history
Config & DCA: Added DCA delay in days. Few fixes & improvements.
  • Loading branch information
adocquin authored May 2, 2021
2 parents 4c2c857 + d544233 commit a3d9c2d
Show file tree
Hide file tree
Showing 10 changed files with 49 additions and 19 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Kraken-DCA is a python program to automate
[Dollar Cost Averaging](https://www.investopedia.com/terms/d/dollarcostaveraging.asp) on
[Kraken](https://kraken.com) exchange.<br>
At every launch, if no DCA pair order was already passed in the current day for the specified pair in the configuration
file, it will create a buy limit order at current pair ask price for the specified amount in configurationn file.
At every launch, if no DCA pair order was already passed for the pair and delay in configuration
file, it will create a buy limit order at current pair ask price for the specified amount.

Order history is saved in CSV format

Expand All @@ -16,7 +16,7 @@ The program will need a Kraken public and private API key with permissions to:
API keys can be created from the [API page](https://www.kraken.com/u/security/api) of your Kraken account.

# Orders
The pair and the amount to DCA per day need to be specified in configuration file.
The pair and the amount to buy need to be specified in the configuration file.

## What are the order settings ?
A buy limit taker order is created by the program at its execution, 0.26% fee are assumed.<br>
Expand Down Expand Up @@ -71,11 +71,13 @@ api:
public_key: "KRAKEN_API_PUBLIC_KEY"
private_key: "KRAKEN_API_PRIVATE_KEY"

# Pair to DCA and corresponding amount per day.
# DCA days delay, pair to DCA and corresponding amount to buy.
dca:
delay: 2
pair: "XETHZEUR"
amount: 20
```
- Delay is the number of days between buy orders. Set to 1 to DCA each day, 7 once per week.
- Available pairs for pair field can be found [here](https://api.kraken.com/0/public/AssetPairs) on *altname*.
- Amount of quote asset to sell to buy base asset.
Expand Down
2 changes: 1 addition & 1 deletion __main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# configuration file and data from Kraken.
pair = Pair.get_pair_from_kraken(ka, config.pair)
# Initialize the DCA object.
dca = DCA(ka, pair, config.amount)
dca = DCA(ka, config.delay, pair, config.amount)
# Execute DCA logic.
dca.handle_dca_logic()
except Exception as e:
Expand Down
3 changes: 2 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ api:
public_key: "KRAKEN_API_PUBLIC_KEY"
private_key: "KRAKEN_API_PRIVATE_KEY"

# Pair to DCA and corresponding amount per day.
# DCA days delay, pair to DCA and corresponding amount to buy.
dca:
delay: 1
pair: "XETHZEUR"
amount: 20
8 changes: 6 additions & 2 deletions kraken_dca/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Config:

api_public_key: str
api_private_key: str
delay: int
pair: str
amount: float

Expand All @@ -23,19 +24,22 @@ def __init__(self, config_file: str) -> None:
config = yaml.load(stream, Loader=yaml.SafeLoader)
self.api_public_key = config.get("api").get("public_key")
self.api_private_key = config.get("api").get("private_key")
self.delay = config.get("dca").get("delay")
self.pair = config.get("dca").get("pair")
self.amount = float(config.get("dca").get("amount"))
except (TypeError, AttributeError, yaml.YAMLError) as e:
except (ValueError, TypeError, AttributeError, yaml.YAMLError) as e:
raise ValueError(f"Configuration file incorrectly formatted: {e}")
except EnvironmentError:
raise FileNotFoundError("Configuration file not found.")
if not self.api_public_key:
raise TypeError("Please provide your Kraken API public key.")
elif not self.api_private_key:
raise TypeError("Please provide your Kraken API private key.")
elif not self.delay or type(self.delay) is not int or self.delay <= 0:
raise TypeError("Please set the DCA days delay as a number > 0.")
elif not self.pair:
raise TypeError("Please provide the pair to dollar cost average.")
elif not self.amount or self.amount <= 0:
elif not self.amount or type(self.amount) is not float or self.amount <= 0:
raise TypeError(
"Please provide an amount > 0 to daily dollar cost average."
)
14 changes: 9 additions & 5 deletions kraken_dca/dca.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
current_utc_day_datetime,
datetime_as_utc_unix,
)
from datetime import datetime
from datetime import datetime, timedelta


class DCA:
Expand All @@ -16,13 +16,15 @@ class DCA:
"""

ka: KrakenApi
delay: int
pair: Pair
amount: float
orders_filepath: str

def __init__(
self,
ka: KrakenApi,
delay: int,
pair: Pair,
amount: float,
orders_filepath: str = "orders.csv",
Expand All @@ -31,10 +33,12 @@ def __init__(
Initialize the DCA object.
:param ka: KrakenApi object.
:param delay: DCA days delay between buy orders.
:param pair: Pair to dollar cost average as string.
:param amount: Amount to dollar cost average as float.
"""
self.ka = ka
self.delay = delay
self.pair = pair
self.amount = float(amount)
self.orders_filepath = orders_filepath
Expand Down Expand Up @@ -75,7 +79,7 @@ def handle_dca_logic(self) -> None:
order.save_order_csv(self.orders_filepath)
print("Order information saved to CSV.")
else:
print("Already DCA today.")
print("Already DCA.")

def get_system_time(self) -> datetime:
"""
Expand Down Expand Up @@ -135,10 +139,10 @@ def count_pair_daily_orders(self) -> int:
)

# Get daily closed orders.
day_datetime = current_utc_day_datetime()
current_day_unix = datetime_as_utc_unix(day_datetime)
start_day_datetime = current_utc_day_datetime() - timedelta(days=self.delay - 1)
start_day_unix = datetime_as_utc_unix(start_day_datetime)
closed_orders = self.ka.get_closed_orders(
{"start": current_day_unix, "closetime": "open"}
{"start": start_day_unix, "closetime": "open"}
)
daily_closed_orders = len(
self.extract_pair_orders(closed_orders, self.pair.name, self.pair.alt_name)
Expand Down
16 changes: 14 additions & 2 deletions kraken_dca/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(
volume: float,
price: float,
fee: float,
total_price: float,
) -> None:
"""
Initialize the Order object.
Expand All @@ -52,6 +53,7 @@ def __init__(
:param price: Order price.
:param fee: Order fee.
:param pair_price: Order pair price.
:param total_price: Total price of the order (order price + fee).
"""
self.date = date
self.pair = pair
Expand All @@ -62,7 +64,7 @@ def __init__(
self.volume = volume
self.price = price
self.fee = fee
self.total_price = price + fee
self.total_price = total_price

@classmethod
def buy_limit_order(
Expand Down Expand Up @@ -92,8 +94,18 @@ def buy_limit_order(
order_type = "limit"
# Pay fee in quote asset.
o_flags = "fciq"
total_price = round(price + fee, quote_decimals)
return cls(
date, pair, type, order_type, o_flags, pair_price, volume, price, fee
date,
pair,
type,
order_type,
o_flags,
pair_price,
volume,
price,
fee,
total_price,
)

def send_order(self, ka: KrakenApi) -> None:
Expand Down
1 change: 1 addition & 0 deletions kraken_dca/pair.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Pair:
quote: str
pair_decimals: int
lot_decimals: int
quote_decimals: int
order_min: float

def __init__(
Expand Down
3 changes: 2 additions & 1 deletion tests/fixtures/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ api:
public_key: "KRAKEN_API_PUBLIC_KEY"
private_key: "KRAKEN_API_PRIVATE_KEY"

# Pair to DCA and corresponding amount per day.
# DCA days delay, pair to DCA and corresponding amount to buy.
dca:
delay: 1
pair: "XETHZEUR"
amount: 20
8 changes: 6 additions & 2 deletions tests/test_dca.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ def setup(self):
# Initialize the Pair object.
pair = Pair("XETHZEUR", "ETHEUR", "XETH", "ZEUR", 2, 8, 4, 0.005)
# Initialize the DCA object.
self.dca = DCA(ka, pair, 20, self.test_orders_filepath)
self.dca = DCA(ka, 1, pair, 20, self.test_orders_filepath)

def test_init(self):
assert type(self.dca.ka) == KrakenApi
assert type(self.dca.delay) == int
assert self.dca.delay == 1
assert type(self.dca.pair) == Pair
assert type(self.dca.amount) == float
assert self.dca.amount == 20
Expand Down Expand Up @@ -61,7 +63,7 @@ def test_handle_dca_logic_error(self, capfd):
test_output = (
"Hi, current configuration: DCA pair: XETHZEUR, DCA amount: 20.0.\nIt's 2021-04-16 18:54:53 on "
"Kraken, 2021-04-16 18:54:53 on system.\nCurrent trade balance: 16524.7595 ZUSD.\nPair "
"balances: 359.728 ZEUR, 0.128994332 XETH.\nAlready DCA today.\n"
"balances: 359.728 ZEUR, 0.128994332 XETH.\nAlready DCA.\n"
)
assert captured.out == test_output

Expand Down Expand Up @@ -157,6 +159,7 @@ def test_send_buy_limit_order_error(self):
0.001,
1.9608,
0.0052,
20.0,
)
with pytest.raises(ValueError) as e_info:
self.dca.send_buy_limit_order(order)
Expand All @@ -179,6 +182,7 @@ def test_send_buy_limit_order(self, capfd):
0.01029256,
19.9481,
0.0519,
20.0,
)
self.dca.send_buy_limit_order(order)
captured = capfd.readouterr()
Expand Down
3 changes: 2 additions & 1 deletion tests/test_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def setup(self):
0.00957589,
19.9481,
0.0519,
round(19.9481 + 0.0519, 4),
)
self.ka = KrakenApi(
"R6/OvXmIQEv1E8nyJd7+a9Zmaf84yJ7uifwe2yj5BgV1N+lgqURsxQwQ",
Expand Down Expand Up @@ -49,7 +50,7 @@ def test_init(self):
assert type(self.order.fee) == float
assert self.order.fee == 0.0519
assert type(self.order.total_price) == float
assert self.order.total_price == 19.9481 + 0.0519
assert self.order.total_price == 20.0

def test_buy_limit_order(self):
self.order = Order.buy_limit_order(
Expand Down

0 comments on commit a3d9c2d

Please sign in to comment.