Compare commits

...

No commits in common. "main" and "main-old" have entirely different histories.

14 changed files with 811 additions and 270 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
__pycache__/
/config.json
/secret.json

View File

@ -1,111 +0,0 @@
# 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

376
bitbank.py Normal file
View File

@ -0,0 +1,376 @@
import asyncio
import datetime
import logging
import pybotters
import logic
import util.candlestick
import util.depth
import util.pair
import util.ticker
PUBLIC_API_ENTRYPOINT = "https://public.bitbank.cc"
PRIVATE_API_ENTRYPOINT = "https://api.bitbank.cc/v1"
ORDER_UNITS = {
"btc_jpy": 0.0001,
"eth_jpy": 0.0001,
}
PRICE_UNITS = {
"btc_jpy": 1,
"eth_jpy": 1,
}
logger = logging.getLogger(__name__)
_k = asyncio.Lock()
async def init(cli):
#pair = Pair(cli, "mona_jpy")
#logic.Hige(
# logging.getLogger("bitbank/mona/Hige"),
# pair, 4, 0.2)
pair = Pair(cli, "btc_jpy")
logic.EMA_Chicken(
logging.getLogger("bitbank/btc/EMA_Chicken"),
pair, pair.candlestick_1m, 0.001, 0.005)
pair = Pair(cli, "eth_jpy")
logic.MM(
logging.getLogger("bitbank/eth/MM"),
pair, 0.01, 1, 0.15)
logic.EMA_Chicken(
logging.getLogger("bitbank/eth/EMA_Chicken"),
pair, pair.candlestick_1m, 0.01, 0.005)
class Pair(util.pair.Pair):
def __init__(self, cli, name):
super().__init__(name)
self._cli = cli
self._store = pybotters.bitbankDataStore()
self.ticker = Ticker(self._store, name)
self.order_unit = ORDER_UNITS[name]
self.price_unit = PRICE_UNITS[name]
#self.candlestick_1s = Candlestick(cli, self.ticker, None, 1)
self.candlestick_1m = Candlestick(cli, self.ticker, "1min", 60)
#self.candlestick_1h = Candlestick(cli, self.ticker, "1hour", 60*60)
self.depth = Depth(self._store, self.order_unit)
asyncio.create_task(cli.ws_connect(
'wss://stream.bitbank.cc/socket.io/?EIO=3&transport=websocket',
send_str=[
f'42["join-room","ticker_{name}"]',
f'42["join-room","transactions_{name}"]',
f'42["join-room","depth_whole_{name}"]',
f'42["join-room","depth_diff_{name}"]',
],
hdlr_str = self._store.onmessage,
))
async def _order(self, amount, side, type, trigger = None, price = None, post_only = None):
data = {
"pair" : self.name,
"amount": str(amount),
"side" : side,
"type" : type,
}
if price is not None: data["price"] = price
if trigger is not None: data["trigger_price"] = trigger
if post_only is not None: data["post_only"] = post_only
async with _k:
res = await self._cli.post(PRIVATE_API_ENTRYPOINT+"/user/spot/order", data=data)
res = await res.json()
_check_response(res)
order = Order(self._cli, self, res["data"]["order_id"])
order.amount = amount
order.price = price
return order
def buy_market(self, amount):
return self._order(amount, "buy", "market")
def sell_market(self, amount):
return self._order(amount, "sell", "market")
def buy_limit(self, amount, price, post_only = False):
return self._order(amount, "buy", "limit", None, price, post_only)
def sell_limit(self, amount, price, post_only = False):
return self._order(amount, "sell", "limit", None, price, post_only)
def buy_stop(self, amount, trigger):
return self._order(amount, "buy", "stop", trigger)
def sell_stop(self, amount, trigger):
return self._order(amount, "sell", "stop", trigger)
def buy_stop_limit(self, amount, trigger, price):
return self._order(amount, "buy", "stop", trigger, price)
def sell_stop_limit(self, amount, trigger, price):
return self._order(amount, "sell", "stop", trigger, price)
class Order(util.pair.Order):
def __init__(self, cli, pair, id):
super().__init__()
self._cli = cli
self._pair = pair
self._id = id
async def update(self):
if self.done: return
for i in range(5):
async with _k:
res = await self._cli.get(PRIVATE_API_ENTRYPOINT+"/user/spot/order", params={
"pair": self._pair.name,
"order_id": self._id,
})
res = await res.json()
if res["success"] == 1: break
# sometimes fails without a reason, so it does retry :(
if res["data"]["code"] != 50009 and str(res["data"]["code"]) in ERROR_CODES:
_check_response(res)
await asyncio.sleep(1)
if res["success"] != 1: raise Exception("failed to update")
res = res["data"]
self.amount = float(res["start_amount"])
self.remain = float(res["remaining_amount"])
self.price = float(res["average_price"])
self.done = \
res["status"] == "FULLY_FILLED" or \
res["status"] == "CANCELED_UNFILLED" or \
res["status"] == "CANCELED_PARTIALLY_FILLED"
async def cancel(self):
for i in range(5):
async with _k:
res = await self._cli.post(PRIVATE_API_ENTRYPOINT+"/user/spot/cancel_order", data={
"pair": self._pair.name,
"order_id": self._id,
})
res = await res.json()
if res["success"] == 1:
res = res["data"]
self.amount = float(res["start_amount"])
self.remain = float(res["remaining_amount"])
self.price = float(res["average_price"])
self.done = True
return
code = res["data"]["code"]
if code == 50027:
await self.update()
return
# sometimes fails without a reason, so it does retry :(
retry = \
code == 50009 or \
str(code) not in ERROR_CODES
if not retry:
_check_response(res)
await asyncio.sleep(1)
raise Exception("failed to cancel")
class Ticker(util.ticker.Ticker):
def __init__(self, store, pair):
super().__init__(pair)
self._store = store
asyncio.create_task(self._main())
async def _main(self):
while True:
await self._store.ticker.wait()
ticker = self._store.ticker.find()
for i in range(len(ticker)):
if ticker[i]["pair"] == self.pair:
self.price = float(ticker[i]["last"])
self.sell = float(ticker[i]["sell"])
self.buy = float(ticker[i]["buy"])
self.volume = float(ticker[i]["vol"])
self.ts = int(ticker[i]["timestamp"])
self._event.set()
self._event.clear()
break
class Candlestick(util.candlestick.Candlestick):
def __init__(self, cli, ticker, unit, interval):
super().__init__(ticker, interval)
self._cli = cli
self._unit = unit
async def _fetch(self, date, depth = 0, rem = util.candlestick.MAX_CANDLES):
if self._unit is None:
return []
long_term = \
self._unit == "4hour" or \
self._unit == "8hour" or \
self._unit == "12hour" or \
self._unit == "1day" or \
self._unit == "1week" or \
self._unit == "1month"
if long_term:
strdate = date.strftime("%Y")
else:
strdate = date.strftime("%Y%m%d")
pair = self._ticker.pair
async with _k:
data = await self._cli.get(PUBLIC_API_ENTRYPOINT+f"/{pair}/candlestick/{self._unit}/{strdate}")
data = await data.json()
_check_response(data)
data = data["data"]["candlestick"][0]["ohlcv"]
first_index = 0
if depth == 0: first_index = 1
ret = []
for i in range(first_index, len(data)):
ret.append([
float(data[i][0]),
float(data[i][1]),
float(data[i][2]),
float(data[i][3]),
])
if len(ret) < rem:
back = datetime.timedelta(days=1)
if long_term:
back = datetime.timedelta(years=1)
ret = [*ret, *await self._fetch(date-back, depth+1, rem-len(ret))]
return ret
class Depth(util.depth.Depth):
def __init__(self, store, order_unit):
super().__init__(order_unit)
self._store = store
asyncio.create_task(self._main())
async def _main(self):
while True:
await self._store.depth.wait()
items = self._store.depth.sorted()
asks = items["asks"]
bids = items["bids"]
self.asks = []
for i in range(len(asks)):
self.asks.append([float(asks[i][0]), float(asks[i][1])])
self.bids = []
for i in range(len(bids)):
self.bids.append([float(bids[i][0]), float(bids[i][1])])
self._event.set()
self._event.clear()
def _check_response(res):
if res["success"] == 1:
return
code = str(res["data"]["code"])
if code in ERROR_CODES:
raise Exception(ERROR_CODES[code])
raise Exception("unknown error")
ERROR_CODES = {
'10000': 'URLが存在しません',
'10001': 'システムエラーが発生しました。サポートにお問い合わせ下さい',
'10002': '不正なJSON形式です。送信内容をご確認下さい',
'10003': 'システムエラーが発生しました。サポートにお問い合わせ下さい',
'10005': 'タイムアウトエラーが発生しました。しばらく間をおいて再度実行して下さい',
'20001': 'API認証に失敗しました',
'20002': 'APIキーが不正です',
'20003': 'APIキーが存在しません',
'20004': 'API Nonceが存在しません',
'20005': 'APIシグネチャが存在しません',
'20011': '2段階認証に失敗しました',
'20014': 'SMS認証に失敗しました',
'20023': '2段階認証コードを入力して下さい',
'20024': 'SMS認証コードを入力して下さい',
'20025': '段階認証コードとSMS認証コードを入力して下さい',
'20026': '一定回数以上段階認証に失敗したためロックしました。60秒待ってから再度お試しください',
'30001': '注文数量を指定して下さい',
'30006': '注文IDを指定して下さい',
'30007': '注文ID配列を指定して下さい',
'30009': '銘柄を指定して下さい',
'30012': '注文価格を指定して下さい',
'30013': '売買どちらかを指定して下さい',
'30015': '注文タイプを指定して下さい',
'30016': 'アセット名を指定して下さい',
'30019': 'uuidを指定して下さい',
'30039': '出金額を指定して下さい',
'30101': 'トリガー価格を指定してください',
'40001': '注文数量が不正です',
'40006': 'count値が不正です',
'40007': '終了時期が不正です',
'40008': 'end_id値が不正です',
'40009': 'from_id値が不正です',
'40013': '注文IDが不正です',
'40014': '注文ID配列が不正です',
'40015': '指定された注文が多すぎます',
'40017': '銘柄名が不正です',
'40020': '注文価格が不正です',
'40021': '売買区分が不正です',
'40022': '開始時期が不正です',
'40024': '注文タイプが不正です',
'40025': 'アセット名が不正です',
'40028': 'uuidが不正です',
'40048': '出金額が不正です',
'40112': 'トリガー価格が不正です',
'40113': 'post_only値が不正です',
'40114': 'Post Onlyはご指定の注文タイプでは指定できません',
'50003': '現在、このアカウントはご指定の操作を実行できない状態となっております。サポートにお問い合わせ下さい',
'50004': '現在、このアカウントは仮登録の状態となっております。アカウント登録完了後、再度お試し下さい',
'50005': '現在、このアカウントはロックされております。サポートにお問い合わせ下さい',
'50006': '現在、このアカウントはロックされております。サポートにお問い合わせ下さい',
'50008': 'ユーザの本人確認が完了していません',
'50009': 'ご指定の注文は存在しません',
'50010': 'ご指定の注文はキャンセルできません',
'50011': 'APIが見つかりません',
'50026': 'ご指定の注文は既にキャンセル済みです',
'50027': 'ご指定の注文は既に約定済みです',
'60001': '保有数量が不足しています',
'60002': '成行買い注文の数量上限を上回っています',
'60003': '指定した数量が制限を超えています',
'60004': '指定した数量がしきい値を下回っています',
'60005': '指定した価格が上限を上回っています',
'60006': '指定した価格が下限を下回っています',
'60011': '同時発注制限件数(30件)を上回っています',
'60016': '指定したトリガー価格が上限を上回っています',
'70001': 'システムエラーが発生しました。サポートにお問い合わせ下さい',
'70002': 'システムエラーが発生しました。サポートにお問い合わせ下さい',
'70003': 'システムエラーが発生しました。サポートにお問い合わせ下さい',
'70004': '現在取引停止中のため、注文を承ることができません',
'70005': '現在買い注文停止中のため、注文を承ることができません',
'70006': '現在売り注文停止中のため、注文を承ることができません',
'70009': 'ただいま成行注文を一時的に制限しています。指値注文をご利用ください',
'70010': 'ただいまシステム負荷が高まっているため、最小注文数量を一時的に引き上げています',
'70011': 'ただいまリクエストが混雑してます。しばらく時間を空けてから再度リクエストをお願いします',
'70012': 'システムエラーが発生しました。サポートにお問い合わせ下さい',
'70013': 'ただいまシステム負荷が高まっているため、注文および注文キャンセルを一時的に制限しています',
'70014': 'ただいまシステム負荷が高まっているため、出金申請および出金申請キャンセルを一時的に制限しています',
'70015': 'ただいまシステム負荷が高まっているため、貸出申請および貸出申請キャンセルを一時的に制限しています',
'70016': '貸出申請および貸出申請キャンセル停止中のため、リクエストを承ることができません',
'70017': '指定された銘柄は注文停止中のため、リクエストを承ることができません',
'70018': '指定された銘柄は注文およびキャンセル停止中のため、リクエストを承ることができません',
'70019': '注文はキャンセル中です',
'70020': '現在成行注文停止中のため、注文を承ることができません',
'70021': '指値注文価格が乖離率を超過しています',
'70022': '現在逆指値指値注文停止中のため、注文を承ることができません',
'70023': '現在逆指値成行注文停止中のため、注文を承ることができません'
}

View File

@ -1,9 +0,0 @@
version: '3'
services:
env:
image: python:3-alpine
command: sh -c "pip3 install -q -r requirements.txt && python3 main.py"
working_dir: /repo/
volumes:
- ./:/repo/

3
logic/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from logic.mm import MM
from logic.hige import Hige
from logic.ema_chicken import EMA_Chicken

66
logic/ema_chicken.py Normal file
View File

@ -0,0 +1,66 @@
import asyncio
class EMA_Chicken:
def __init__(self, logger, pair, candle, lot, limit_rate):
self._logger = logger
self._pair = pair
self._candle = candle
self._remain = 0
self._total = 0
self._lot = lot
self._limit_rate = limit_rate
self._total = 0
asyncio.create_task(self._main())
async def _main(self):
self._logger.info("started")
while True:
await self._pair.ticker.wait()
if len(self._candle.values) == 0:
continue
price = self._pair.ticker.price
ema = self._candle.EMA(200)
if price > ema:
if self._remain < self._lot*3:
buy = await self._pair.buy_market(self._lot)
await asyncio.sleep(0.5)
await buy.update()
outgo = buy.price * self._lot
self._total -= outgo
self._remain += self._lot
self._logger.info(f"<BUY> {self._lot} / {outgo} ({self._remain} / {self._total})")
limit_price = buy.price*(1+self._limit_rate)
stop_price = buy.price*(1-self._limit_rate)
sell = await self._pair.sell_limit(self._lot, limit_price)
while True:
await asyncio.sleep(0.5)
await sell.update()
if sell.done:
amount = sell.amount - sell.remain
income = sell.price * amount
self._total += income
self._remain -= amount
if amount > 0:
self._logger.info(f"[WIN] <SELL> {amount} / {income} ({self._remain} / {self._total})")
break
if self._pair.ticker.price < stop_price:
await sell.cancel()
sell = await self._pair.sell_market(self._lot)
await asyncio.sleep(0.5)
await sell.update()
amount = sell.amount
income = sell.price * sell.amount
self._total += income
self._remain -= amount
if amount > 0:
self._logger.info(f"[LOSE] <SELL> {amount} / {income} ({self._remain} / {self._total})")
break

76
logic/hige.py Normal file
View File

@ -0,0 +1,76 @@
import asyncio
class Hige:
def __init__(self, logger, pair, lot, delta):
self._logger = logger
self._pair = pair
self._lot = lot
self._delta = delta
self._remain = 0
self._sell_price = 0
self._buy_price = 0
self._sell = None
self._buy = None
self._total = 0
asyncio.create_task(self._main())
async def _main(self):
self._logger.info("started")
while True:
pair = self._pair
depth = pair.depth
if len(depth.bids) == 0 or len(depth.asks) == 0:
await depth.wait()
continue
expected_sell_price = depth.asks[0][0] + self._delta
expected_buy_price = depth.bids[0][0] - self._delta
if self._sell is not None:
await self._sell.update()
if self._sell.done:
amount = self._sell.amount - self._sell.remain
income = amount * self._sell.price
self._remain -= amount
self._total += income
self._sell = None
if amount > 0:
self._logger.info(f"<SELL> {amount} / {income} ({self._remain} / {self._total})")
continue
elif self._sell_price != expected_sell_price:
await self._sell.cancel()
continue
elif self._remain > pair.order_unit:
amount = min(self._lot, self._remain)
self._sell_price = expected_sell_price
self._sell = await pair.sell_limit(amount, self._sell_price)
if self._buy is not None:
await self._buy.update()
if self._buy.done:
amount = self._buy.amount - self._buy.remain
outgo = amount * self._buy.price
self._total -= outgo
self._remain += amount
self._buy = None
if amount > 0:
self._logger.info(f"<BUY> {amount} / {outgo} ({self._remain} / {self._total})")
continue
elif self._buy_price != expected_buy_price:
await self._buy.cancel()
continue
elif self._remain < self._lot*3:
self._buy_price = expected_buy_price
self._buy = await pair.buy_limit(self._lot, self._buy_price)
await asyncio.sleep(5)
async def _reset_order(order, price):
if not order.done and order.price != price:
await order.cancel()

135
logic/mm.py Normal file
View File

@ -0,0 +1,135 @@
import asyncio
class MM:
def __init__(self, logger, pair, lot, spread_amount, spread_band):
self._logger = logger
self._pair = pair
self._lot = lot
self._remain = 0
self._buy_price = None
self._sell_price = None
self._buy = None
self._sell = None
self._spread_amount = spread_amount
self._spread_band = spread_band
self._total = 0
asyncio.create_task(self._main())
async def _main(self):
self._logger.info("started")
while True:
pair = self._pair
depth = pair.depth
await depth.wait()
if len(depth.bids) == 0 or len(depth.asks) == 0:
continue
# calculate best amount to buy/sell
pos = self._remain / self._lot - 2
buy_amount = _quad( pos) * self._lot
sell_amount = _quad(-pos) * self._lot
# calculate ideal spread
ask_delta_amount = self._spread_amount * (_quad( pos) + self._spread_band)
bid_delta_amount = self._spread_amount * (_quad(-pos) + self._spread_band)
# calculate best sell price
if self._sell is not None and self._sell.done:
ask_delta_amount += self._sell.amount
ask = 0
prev = -1e100
for i in range(len(depth.asks)):
if (depth.asks[i][0]-prev) > pair.price_unit*1.5:
ask = depth.asks[i][0] - pair.price_unit
my_order = \
self._sell is not None and \
not self._sell.done and \
abs(self._sell_price-depth.asks[i][0]) < pair.price_unit
if my_order:
ask_delta_amount -= depth.asks[i][1] - self._sell.amount
prev = -1e100
else:
ask_delta_amount -= depth.asks[i][1]
prev = depth.asks[i][0]
if ask_delta_amount < 0:
break
# calculate best buy price
if self._buy is not None and self._buy.done:
bid_delta_amount += self._buy.amount
bid = 0
prev = 1e100
for i in range(len(depth.bids)):
if (prev-depth.bids[i][0]) > pair.price_unit*1.5:
bid = depth.bids[i][0] + pair.price_unit
my_order = \
self._buy is not None and \
not self._buy.done and \
abs(self._buy_price-depth.bids[i][0]) < pair.price_unit
if my_order:
bid_delta_amount -= depth.bids[i][1] - self._buy.amount
prev = 1e100
else:
bid_delta_amount -= depth.bids[i][1]
prev = depth.bids[i][0]
if bid_delta_amount < 0:
break
if self._sell is not None:
# check current SELL order
await asyncio.sleep(0.5)
await self._sell.update()
if self._sell.done:
amount = self._sell.amount - self._sell.remain
self._remain -= amount
if amount > 0:
income = amount*self._sell.price
self._total += income
self._logger.info(f"<SELL> {amount} / {income} ({self._remain} / {self._total})")
self._sell = None
elif abs(self._sell_price-ask) >= pair.price_unit:
await self._sell.cancel()
elif sell_amount >= pair.order_unit:
# order SELL
self._sell_price = ask
self._sell = await self._order_sell(sell_amount)
if self._buy is not None:
# check current BUY order
await self._buy.update()
if self._buy.done:
amount = self._buy.amount - self._buy.remain
self._remain += amount
if amount > 0:
outgo = amount*self._buy.price
self._total -= outgo
self._logger.info(f"<BUY> {amount} / {outgo} ({self._remain} / {self._total})")
self._buy = None
elif abs(self._buy_price-bid) >= pair.price_unit:
await self._buy.cancel()
elif buy_amount >= pair.order_unit:
# order BUY
self._buy_price = bid
self._buy = await self._order_buy(buy_amount)
async def _order_sell(self, amount):
return await self._pair.sell_limit(amount, self._sell_price, True)
async def _order_buy(self, amount):
return await self._pair.buy_limit(amount, self._buy_price, True)
# 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

29
main.py
View File

@ -1,18 +1,11 @@
# No copyright
import asyncio
import json
import logging
import pybotters
import signal
from util.pair import Pair
from util.player import Player
from algo.mm import MM
import bitbank
with open("config.json", "r") as file:
config = json.load(file)
async def main():
alive = True
logging.basicConfig(level=logging.DEBUG)
@ -27,21 +20,9 @@ async def main():
loop.add_signal_handler(signal.SIGTERM, teardown)
loop.add_signal_handler(signal.SIGINT, teardown)
logging.info("#### TMM ####")
async with pybotters.Client(apis={"bitbank":config["auth"]}) as pb:
player = Player(pb)
pair = Pair(pb, config["pair"])
algo = MM(player, pair, config["algorithm"]["mm"])
while alive:
try:
await asyncio.gather(
player.update(),
pair.update())
await algo.update()
except Exception as e:
print(e)
logging.error(e)
await asyncio.sleep(int(config["interval"]))
async with pybotters.Client(apis="secret.json") as cli:
await asyncio.gather(bitbank.init(cli))
while alive: await asyncio.sleep(0.5)
if __name__ == "__main__":
try:

74
util/candlestick.py Normal file
View File

@ -0,0 +1,74 @@
import asyncio
import datetime
MAX_CANDLES = 200
OPEN = 0
HIGH = 0
LOW = 0
CLOSE = 0
class Candlestick:
def __init__(self, ticker, interval):
self.values = []
self.latest = None
self.interval = interval
self._ticker = ticker
self._event = asyncio.Event()
asyncio.create_task(self._refresh_and_start())
async def _refresh_and_start(self):
self.values = await self._fetch(datetime.datetime.now(tz=datetime.timezone.utc))
asyncio.create_task(self._ticker_watcher())
asyncio.create_task(self._main())
async def _fetch(self, data, depth = 0, rem = MAX_CANDLES):
assert(False)
async def _ticker_watcher(self):
while True:
await self._ticker.wait()
v = self._ticker.price
if self.latest is None:
self.latest = [v, -1e100, 1e100, None]
self.latest[1] = max(self.latest[1], v)
self.latest[2] = min(self.latest[2], v)
self.latest[3] = v
async def _main(self):
while True:
await asyncio.sleep(self.interval)
if self.latest is None:
if len(self.values) == 0:
continue
self.latest = self.values[0]
n = min(MAX_CANDLES-1, len(self.values))
self.values = [self.latest, *self.values[0:n]]
self.latest = None
self._event.set()
self._event.clear()
async def wait(self):
await self._event.wait()
def SMA(self, period, offset=0, type = CLOSE):
sum = 0
end = min(period+offset, len(self.values))
for i in range(offset, end):
sum += self.values[i][type]
return sum / (end - offset)
def EMA(self, period, offset=0, type = CLOSE):
end = min(period+offset, len(self.values))
n = end - offset
ret = 0
for i in range(offset, end):
ret += self.values[i][type]
ret /= n
for i in range(offset, end):
ret += (2/(n+1)) * (self.values[i][type] - ret)
return ret

15
util/depth.py Normal file
View File

@ -0,0 +1,15 @@
import asyncio
MAX_ITEMS = 10
class Depth:
def __init__(self, order_unit):
self.bids = []
self.asks = []
self._event = asyncio.Event()
self._order_unit = order_unit
async def wait(self):
await self._event.wait()

View File

@ -1,23 +1,51 @@
# No copyright
import logging
import asyncio
class Pair:
def __init__(self, pb, names):
self.names = names
self._pb = pb
def __init__(self, name):
self.name = name
self.ticker = None
self.candlestick_1s = None
self.candlestick_1m = None
self.candlestick_1h = None
self.order_unit = None
self.price_unit = None
def buy_market(amount):
assert(False)
def sell_market(amount):
assert(False)
def buy_limit(amount, price):
assert(False)
def sell_limit(amount, price):
assert(False)
def buy_stop(amount, price):
assert(False)
def sell_stop(amount, price):
assert(False)
def buy_stop_limit(amount, trigger, price):
assert(False)
def sell_stop_limit(amount, trigger, price):
assert(False)
class Order:
def __init__(self):
self.price = None
self.amount = None
self.remain = None
self.done = False
async def update(self):
ticker = (await self._get("ticker"))["data"]
self.ticker = Ticker(ticker)
assert(False)
async def _get(self, suffix):
res = await self._pb.get(f"https://public.bitbank.cc/{self.names[0]}_{self.names[1]}/{suffix}")
json = await res.json()
if "success" not in json or 1 != json["success"]:
code = json["data"]["code"]
raise Exception(f"API error: {code}")
return json
class Ticker:
def __init__(self, json):
self.price = float(json["last"])
async def cancel(self):
assert(False)

View File

@ -1,107 +0,0 @@
# No copyright
import asyncio
import logging
class Player:
def __init__(self, pb):
self._pb = pb
self.assets = {}
self.orders = {}
async def cancel(self, pair, orders):
res = await self._post("user/spot/cancel_orders", data={
"pair": f"{pair[0]}_{pair[1]}",
"order_ids": orders
})
async def orderMarketSell(self, pair, amount):
res = await self._post("user/spot/order", data={
"pair": f"{pair[0]}_{pair[1]}",
"amount": str(amount),
"side": "sell",
"type": "market",
})
return res["data"]["order_id"]
async def orderMarketBuy(self, pair, amount):
res = await self._post("user/spot/order", data={
"pair": f"{pair[0]}_{pair[1]}",
"amount": str(amount),
"side": "buy",
"type": "market",
})
return res["data"]["order_id"]
async def orderLimitSell(self, pair, amount, price):
res = await self._post("user/spot/order", data={
"pair": f"{pair[0]}_{pair[1]}",
"amount": str(amount),
"price": str(price),
"side": "sell",
"type": "limit",
})
return res["data"]["order_id"]
async def orderLimitBuy(self, pair, amount, price):
res = await self._post("user/spot/order", data={
"pair": f"{pair[0]}_{pair[1]}",
"amount": str(amount),
"price": str(price),
"side": "buy",
"type": "limit",
})
return res["data"]["order_id"]
async def update(self):
self.assets = {}
self.orders = {}
assets = (await self._get("user/assets"))["data"]["assets"]
for asset in assets:
self.assets[asset["asset"]] = Asset(asset)
orders = (await self._get("user/spot/active_orders"))["data"]["orders"]
for order in orders:
self.orders[order["order_id"]] = Order(order)
async def _post(self, suffix, data):
for i in range(10):
try:
res = await self._pb.post(f"https://api.bitbank.cc/v1/{suffix}", data=data)
return self._check(await res.json())
except Exception as e:
err = e
await asyncio.sleep(1)
raise Exception(f"API error: {suffix} ({err})")
async def _get(self, suffix):
for i in range(10):
try:
res = await self._pb.get(f"https://api.bitbank.cc/v1/{suffix}")
return self._check(await res.json())
except Exception as e:
err = e
await asyncio.sleep(1)
raise Exception(f"API error: {suffix} ({err})")
def _check(self, json):
if "success" not in json or 1 != json["success"]:
code = json["data"]["code"]
raise Exception(f"API error: {code}")
return json
class Asset:
def __init__(self, json):
self.name = json["asset"]
self.amount = float(json["onhand_amount"])
self.locked = float(json["locked_amount"])
self.precision = int(json["amount_precision"])
class Order:
def __init__(self, json):
self.id = json["order_id"]
self.pair = json["pair"]
self.amount = float(json["start_amount"])
self.remain = float(json["remaining_amount"])
self.price = float(json["average_price"])

14
util/ticker.py Normal file
View File

@ -0,0 +1,14 @@
import asyncio
class Ticker:
def __init__(self, pair):
self.pair = pair
self.price = None
self.sell = None
self.buy = None
self.volume = None
self.ts = None
self._event = asyncio.Event()
async def wait(self):
await self._event.wait()