diff --git a/README.md b/README.md index 7310027..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,6 +92,9 @@ 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 (up to 5 digits).
+ 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/__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..ff7d396 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 (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/krakendca/dca.py b/krakendca/dca.py index a9311fc..d6f7b1d 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}.") @@ -62,12 +65,13 @@ def handle_dca_logic(self) -> None: # 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, self.pair.name, self.amount, - pair_ask_price, + limit_price, self.pair.lot_decimals, self.pair.quote_decimals, ) @@ -79,6 +83,22 @@ def handle_dca_logic(self) -> None: else: print("Already DCA.") + 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 + """ + if round(self.limit_factor, 5) == 1.0: + limit_price = pair_ask_price + else: + 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: """ 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/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/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..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") @@ -195,3 +197,10 @@ 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(3896.01) == 3506.41 + self.dca.limit_factor = 0.999999 + assert self.dca.get_limit_price(3896.01) == 3896.01