From 3b0abd39a5c249f4e25fa5dd2415c6aa97fd258c Mon Sep 17 00:00:00 2001 From: Joerg Date: Tue, 2 Nov 2021 22:13:02 +0100 Subject: [PATCH 1/4] #7 setting for specifying a limit other than the market price --- README.md | 2 + krakendca/dca.py | 24 +++++++-- krakendca/krakendca.py | 3 +- .../vcr_cassettes/test_limit_factor.yaml | 50 +++++++++++++++++++ tests/test_dca.py | 5 ++ 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/vcr_cassettes/test_limit_factor.yaml diff --git a/README.md b/README.md index 7310027..8ddf961 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ dca_pairs: - Available pairs for pair field can be found [here](https://api.kraken.com/0/public/AssetPairs) on *altname*. - Amount is the amount of quote asset to sell to buy base asset. - You can specify as many pairs as you want in the dca_pairs list. +- Set a `limit_factor` if you want to place the buy order that is different from the current market price. + E.g., `limit_factor: 0.95` would set the limit price 5% below the market price. More information on [Kraken API official documentation](https://support.kraken.com/hc/en-us/articles/360000920306-Ticker-pairs). diff --git a/krakendca/dca.py b/krakendca/dca.py index a9311fc..ecdddd6 100644 --- a/krakendca/dca.py +++ b/krakendca/dca.py @@ -19,6 +19,7 @@ class DCA: delay: int pair: Pair amount: float + limit_factor: float orders_filepath: str def __init__( @@ -28,6 +29,7 @@ def __init__( pair: Pair, amount: float, orders_filepath: str = "orders.csv", + limit_factor: float = 1 ) -> None: """ Initialize the DCA object. @@ -41,6 +43,7 @@ def __init__( self.delay = delay self.pair = pair self.amount = float(amount) + self.limit_factor = float(limit_factor) self.orders_filepath = orders_filepath print(f"Pair: {self.pair.name}, delay: {self.delay}, amount: {self.amount}.") @@ -59,15 +62,13 @@ def handle_dca_logic(self) -> None: daily_pair_orders = self.count_pair_daily_orders() if daily_pair_orders == 0: print("Didn't DCA already today.") - # Get current pair ask price. - pair_ask_price = self.pair.get_pair_ask_price(self.ka, self.pair.name) - print(f"Current {self.pair.name} ask price: {pair_ask_price}.") + limit_price = self.get_limit_price() # Create the Order object. order = Order.buy_limit_order( current_date, self.pair.name, self.amount, - pair_ask_price, + limit_price, self.pair.lot_decimals, self.pair.quote_decimals, ) @@ -79,6 +80,21 @@ def handle_dca_logic(self) -> None: else: print("Already DCA.") + def get_limit_price(self): + """Calculates the wanted limit price from the current ask price and the limit_factor + + :return: The limit price + """ + # Get current pair ask price. + pair_ask_price = self.pair.get_pair_ask_price(self.ka, self.pair.name) + print(f"Current {self.pair.name} ask price: {pair_ask_price}.") + if round(self.limit_factor, 5) == 1.0: + limit_price = pair_ask_price + else: + limit_price = pair_ask_price * self.limit_factor + print(f"Calculated limit price (with limit_factor={self.limit_factor:.4f}): {limit_price}") + return limit_price + def get_system_time(self) -> datetime: """ Compare system and Kraken time. diff --git a/krakendca/krakendca.py b/krakendca/krakendca.py index 370f941..66caa4f 100644 --- a/krakendca/krakendca.py +++ b/krakendca/krakendca.py @@ -36,7 +36,8 @@ def initialize_pairs_dca(self): for dca_pair in self.config.dca_pairs: pair = Pair.get_pair_from_kraken(self.ka, asset_pairs, dca_pair.get("pair")) self.dcas_list.append(DCA( - self.ka, dca_pair.get("delay"), pair, dca_pair.get("amount") + self.ka, dca_pair.get("delay"), pair, dca_pair.get("amount"), + limit_factor=dca_pair.get("limit_factor", 1) )) def handle_pairs_dca(self): diff --git a/tests/fixtures/vcr_cassettes/test_limit_factor.yaml b/tests/fixtures/vcr_cassettes/test_limit_factor.yaml new file mode 100644 index 0000000..2d9bf84 --- /dev/null +++ b/tests/fixtures/vcr_cassettes/test_limit_factor.yaml @@ -0,0 +1,50 @@ +interactions: + - request: + body: pair=XETHZEUR + headers: + Connection: + - close + Content-Length: + - '13' + Content-Type: + - application/x-www-form-urlencoded + Host: + - api.kraken.com + User-Agent: + - Python-urllib/3.8 + method: POST + uri: https://api.kraken.com/0/public/Ticker + response: + body: + string: '{"error":[],"result":{"XETHZEUR":{"a":["3896.01000","54","54.000"],"b":["3896.00000","1","1.000"],"c":["3896.00000","0.01289350"],"v":["13252.90999721","14339.17068685"],"p":["3838.30816","3829.30482"],"t":[20470,22511],"l":["3700.00000","3690.00000"],"h":["3913.02000","3913.02000"],"o":"3729.23000"}}}' + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 6a8036490b280746-FRA + Connection: + - close + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 02 Nov 2021 20:46:24 GMT + Expect-CT: + - max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" + Server: + - cloudflare + Set-Cookie: + - __cf_bm=yvnjJaYTlqqvrgbQUObBpPK_9B7DPalBHlf.kAwetDA-1635885984-0-ATG59S5EJ4tqwg2AtcDhs/509EHUsJWZ3F7YTvUSykyTB928Ps+tANmqYn+/200ttJBUBHiJUpEbQoH9lLixOww=; + path=/; expires=Tue, 02-Nov-21 21:16:24 GMT; domain=.kraken.com; HttpOnly; + Secure; SameSite=None + Transfer-Encoding: + - chunked + cache-control: + - no-cache,max-age=0 + referrer-policy: + - no-referrer-when-downgrade + strict-transport-security: + - max-age=15768000 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_dca.py b/tests/test_dca.py index 629d1ac..4549e8b 100644 --- a/tests/test_dca.py +++ b/tests/test_dca.py @@ -195,3 +195,8 @@ def test_send_buy_limit_order(self, capfd): "OUHXFN-RTP6W-ART4VP\nDescription: buy 0.01029256 ETHEUR @ limit 1938.11\n" ) assert captured.out == test_output + + @vcr.use_cassette("tests/fixtures/vcr_cassettes/test_limit_factor.yaml") + def test_limit_factor(self): + self.dca.limit_factor = 0.9 + assert self.dca.get_limit_price() == 3506.409 From 0618ffd0cacc03a7864cee48d94af4c0176ba5d4 Mon Sep 17 00:00:00 2001 From: Joerg Date: Tue, 2 Nov 2021 22:24:21 +0100 Subject: [PATCH 2/4] #7 pair price allows only one decimal digit --- krakendca/dca.py | 2 +- tests/test_dca.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/krakendca/dca.py b/krakendca/dca.py index ecdddd6..385cccc 100644 --- a/krakendca/dca.py +++ b/krakendca/dca.py @@ -91,7 +91,7 @@ def get_limit_price(self): if round(self.limit_factor, 5) == 1.0: limit_price = pair_ask_price else: - limit_price = pair_ask_price * self.limit_factor + limit_price = round(pair_ask_price * self.limit_factor, 1) print(f"Calculated limit price (with limit_factor={self.limit_factor:.4f}): {limit_price}") return limit_price diff --git a/tests/test_dca.py b/tests/test_dca.py index 4549e8b..d3c68e4 100644 --- a/tests/test_dca.py +++ b/tests/test_dca.py @@ -199,4 +199,4 @@ def test_send_buy_limit_order(self, capfd): @vcr.use_cassette("tests/fixtures/vcr_cassettes/test_limit_factor.yaml") def test_limit_factor(self): self.dca.limit_factor = 0.9 - assert self.dca.get_limit_price() == 3506.409 + assert self.dca.get_limit_price() == 3506.4 From 327b265a7992383ac0f5440573173fabaff4331a Mon Sep 17 00:00:00 2001 From: Avel Docquin Date: Sat, 6 Nov 2021 19:39:56 +0100 Subject: [PATCH 3/4] DCA: get_limit_price now takes as parameters pair_ask_price output moved to handle_dca_logic and unit test updated. README and configuration file sample updated. Small typo fixes. --- README.md | 7 ++++++- __main__.py | 1 - config.yaml | 5 ++++- krakendca/dca.py | 22 +++++++++++++--------- tests/fixtures/config.yaml | 5 ++++- tests/test_dca.py | 6 +++++- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8ddf961..852387b 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,14 @@ api: # pair: Name of the pair (list of available pairs: https://api.kraken.com/0/public/AssetPairs) # delay: Delay in days between each buy limit order. # amount: Amount of the order in quote asset. +# limit_factor (optional): Create the limit order at a price of current price +# multiplied by specified factor (up to 5 digits). +# E.g., limit_factor = 0.95 creates a limit order 5% below market price dca_pairs: - pair: "XETHZEUR" delay: 1 amount: 15 + limit_factor: 0.98 - pair: "XXBTZEUR" delay: 3 amount: 20 @@ -88,7 +92,8 @@ dca_pairs: - Available pairs for pair field can be found [here](https://api.kraken.com/0/public/AssetPairs) on *altname*. - Amount is the amount of quote asset to sell to buy base asset. - You can specify as many pairs as you want in the dca_pairs list. -- Set a `limit_factor` if you want to place the buy order that is different from the current market price. +- Set a `limit_factor` if you want to place the buy order that is different from the + current market price (up to 5 digits).
E.g., `limit_factor: 0.95` would set the limit price 5% below the market price. More information on diff --git a/__main__.py b/__main__.py index a5b89aa..4d428e8 100644 --- a/__main__.py +++ b/__main__.py @@ -11,4 +11,3 @@ kdca = KrakenDCA(config, ka) kdca.initialize_pairs_dca() kdca.handle_pairs_dca() - diff --git a/config.yaml b/config.yaml index 869ce6a..ee8a155 100644 --- a/config.yaml +++ b/config.yaml @@ -7,11 +7,14 @@ api: # pair: Name of the pair (list of available pairs: https://api.kraken.com/0/public/AssetPairs) # delay: Delay in days between each buy limit order. # amount: Amount of the order in quote asset. +# limit_factor (optional): Create the limit order at a price of current price +# multiplied by specified factor. +# E.g.: limit_factor = 0.95 creates a limit order 5% below market price dca_pairs: - pair: "XETHZEUR" delay: 1 amount: 15 + limit_factor: 0.985 - pair: "XXBTZEUR" delay: 3 amount: 20 - diff --git a/krakendca/dca.py b/krakendca/dca.py index 385cccc..d6f7b1d 100644 --- a/krakendca/dca.py +++ b/krakendca/dca.py @@ -29,7 +29,7 @@ def __init__( pair: Pair, amount: float, orders_filepath: str = "orders.csv", - limit_factor: float = 1 + limit_factor: float = 1, ) -> None: """ Initialize the DCA object. @@ -62,7 +62,10 @@ def handle_dca_logic(self) -> None: daily_pair_orders = self.count_pair_daily_orders() if daily_pair_orders == 0: print("Didn't DCA already today.") - limit_price = self.get_limit_price() + # Get current pair ask price. + pair_ask_price = self.pair.get_pair_ask_price(self.ka, self.pair.name) + print(f"Current {self.pair.name} ask price: {pair_ask_price}.") + limit_price = self.get_limit_price(pair_ask_price) # Create the Order object. order = Order.buy_limit_order( current_date, @@ -80,19 +83,20 @@ def handle_dca_logic(self) -> None: else: print("Already DCA.") - def get_limit_price(self): - """Calculates the wanted limit price from the current ask price and the limit_factor + def get_limit_price(self, pair_ask_price: float) -> float: + """ + Calculates wanted limit price from current ask price and limit_factor. + :param pair_ask_price: Pair ask price to adjust li;it price from. :return: The limit price """ - # Get current pair ask price. - pair_ask_price = self.pair.get_pair_ask_price(self.ka, self.pair.name) - print(f"Current {self.pair.name} ask price: {pair_ask_price}.") if round(self.limit_factor, 5) == 1.0: limit_price = pair_ask_price else: - limit_price = round(pair_ask_price * self.limit_factor, 1) - print(f"Calculated limit price (with limit_factor={self.limit_factor:.4f}): {limit_price}") + limit_price = round(pair_ask_price * self.limit_factor, 2) + print( + f"Factor adjusted limit price ({self.limit_factor:.4f}): {limit_price}." + ) return limit_price def get_system_time(self) -> datetime: diff --git a/tests/fixtures/config.yaml b/tests/fixtures/config.yaml index 869ce6a..ff7d396 100644 --- a/tests/fixtures/config.yaml +++ b/tests/fixtures/config.yaml @@ -7,11 +7,14 @@ api: # pair: Name of the pair (list of available pairs: https://api.kraken.com/0/public/AssetPairs) # delay: Delay in days between each buy limit order. # amount: Amount of the order in quote asset. +# limit_factor (optional): Create the limit order at a price of current price +# multiplied by specified factor (up to 5 digits). +# E.g., limit_factor = 0.95 creates a limit order 5% below market price dca_pairs: - pair: "XETHZEUR" delay: 1 amount: 15 + limit_factor: 0.985 - pair: "XXBTZEUR" delay: 3 amount: 20 - diff --git a/tests/test_dca.py b/tests/test_dca.py index d3c68e4..eea710a 100644 --- a/tests/test_dca.py +++ b/tests/test_dca.py @@ -30,6 +30,8 @@ def test_init(self): assert type(self.dca.pair) == Pair assert type(self.dca.amount) == float assert self.dca.amount == 20 + assert type(self.dca.limit_factor) == float + assert self.dca.limit_factor == 1 assert self.dca.orders_filepath == self.test_orders_filepath @freeze_time("2021-04-15 21:33:28.069731") @@ -199,4 +201,6 @@ def test_send_buy_limit_order(self, capfd): @vcr.use_cassette("tests/fixtures/vcr_cassettes/test_limit_factor.yaml") def test_limit_factor(self): self.dca.limit_factor = 0.9 - assert self.dca.get_limit_price() == 3506.4 + assert self.dca.get_limit_price(3896.01) == 3506.41 + self.dca.limit_factor = 0.999999 + assert self.dca.get_limit_price(3896.01) == 3896.01 From 677f80f0fcd57a0a426baee9efbb6915b2c35443 Mon Sep 17 00:00:00 2001 From: Avel Docquin Date: Sat, 6 Nov 2021 19:44:11 +0100 Subject: [PATCH 4/4] Fixed configuration file sample and test fixture following previous commit. --- config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config.yaml b/config.yaml index ee8a155..ff7d396 100644 --- a/config.yaml +++ b/config.yaml @@ -8,8 +8,8 @@ api: # delay: Delay in days between each buy limit order. # amount: Amount of the order in quote asset. # limit_factor (optional): Create the limit order at a price of current price -# multiplied by specified factor. -# E.g.: limit_factor = 0.95 creates a limit order 5% below market price +# multiplied by specified factor (up to 5 digits). +# E.g., limit_factor = 0.95 creates a limit order 5% below market price dca_pairs: - pair: "XETHZEUR" delay: 1