112 lines
3.4 KiB
Python
112 lines
3.4 KiB
Python
# No copyright
|
|
import asyncio
|
|
import logging
|
|
|
|
class MM:
|
|
def __init__(self, player, pair, config):
|
|
self._player = player
|
|
self._pair = pair
|
|
|
|
self._lastBuy = ""
|
|
self._lastSell = ""
|
|
self._lastPrice = None
|
|
|
|
self._delta = float(config["delta"])
|
|
self._positiveCut = float(config["+cut"])
|
|
self._negativeCut = float(config["-cut"])
|
|
|
|
async def update(self):
|
|
sales = self._player.assets[self._pair.names[0]]
|
|
crncy = self._player.assets[self._pair.names[1]]
|
|
price = self._pair.ticker.price
|
|
|
|
lot = (sales.amount + crncy.amount / price) / 2
|
|
pos = sales.amount / lot - 1
|
|
|
|
# evaluate if cutting should be executed
|
|
negCut = False
|
|
posCut = False
|
|
if self._lastPrice is not None:
|
|
change = (price - self._lastPrice) / self._lastPrice
|
|
negCut = pos > 0.5 and change < -abs(self._negativeCut)
|
|
posCut = pos < -0.5 and change > abs(self._positiveCut)
|
|
cutting = negCut or posCut
|
|
|
|
# check if the past orders are active
|
|
lastBuyAgreed = (self._lastBuy is not None and
|
|
self._lastBuy not in self._player.orders)
|
|
lastSellAgreed = (self._lastSell is not None and
|
|
self._lastSell not in self._player.orders)
|
|
|
|
if not lastBuyAgreed and not lastSellAgreed and not cutting:
|
|
return
|
|
|
|
# cancel active orders
|
|
cancel_orders = []
|
|
if not lastBuyAgreed and self._lastBuy is not None:
|
|
logging.info(f"cancel buy")
|
|
cancel_orders.append(self._lastBuy)
|
|
elif lastBuyAgreed:
|
|
logging.info(f"agree buy")
|
|
|
|
if not lastSellAgreed and self._lastSell is not None:
|
|
logging.info(f"cancel sell")
|
|
cancel_orders.append(self._lastSell)
|
|
elif lastSellAgreed:
|
|
logging.info(f"agree sell")
|
|
|
|
if len(cancel_orders) != 0:
|
|
await self._player.cancel(self._pair.names, cancel_orders)
|
|
self._lastBuy = None
|
|
self._lastSell = None
|
|
|
|
# execute cutting
|
|
if cutting:
|
|
if negCut:
|
|
logging.info(f"-cut (change: {change}, pos: {pos} -> 0)")
|
|
if posCut:
|
|
logging.info(f"+cut (change: {change}, pos: {pos} -> 0)")
|
|
self._lastBuy = ""
|
|
self._lastSell = ""
|
|
sell_amount = pos * lot
|
|
if sell_amount > 0:
|
|
await self._player.orderMarketSell(self._pair.names, amount=sell_amount)
|
|
else:
|
|
await self._player.orderMarketBuy(self._pair.names, amount=-buy_amount)
|
|
return
|
|
|
|
# execute new cycle
|
|
self._lastPrice = None
|
|
buy_price = price * (1 - self._delta);
|
|
sell_price = price * (1 + self._delta);
|
|
|
|
buy_amount = min(_quad( pos) * lot, crncy.amount / buy_price)
|
|
sell_amount = min(_quad(-pos) * lot, sales.amount)
|
|
order_unit = pow(10, -sales.precision)
|
|
|
|
logging.info(f"new cycle (pos: {pos}, price: {price}, buy: {buy_amount}, sell: {sell_amount})")
|
|
|
|
async def buy():
|
|
self._lastBuy = (
|
|
None if order_unit > buy_amount else
|
|
await self._player.orderLimitBuy(
|
|
self._pair.names, amount = buy_amount, price = buy_price))
|
|
|
|
async def sell():
|
|
self._lastSell = (
|
|
None if order_unit > sell_amount else
|
|
await self._player.orderLimitSell(
|
|
self._pair.names, amount = sell_amount, price = sell_price))
|
|
|
|
await asyncio.gather(buy(), sell())
|
|
self._lastPrice = price
|
|
|
|
# https://kijitora-2018.hatenablog.com/entry/2018/12/23/102913
|
|
def _quad(x):
|
|
if x < -1:
|
|
return 1
|
|
if x <= 1:
|
|
return -1/4 * (x+1)**2 + 1
|
|
return 0
|
|
|