Remove asset filter and begin implementing posting trades

This commit is contained in:
Mark Veidemanis 2022-11-10 07:20:14 +00:00
parent 47384aed5f
commit 40f6330a13
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
7 changed files with 164 additions and 86 deletions

View File

@ -29,61 +29,6 @@ STRIPE_ADMIN_COUPON = getenv("STRIPE_ADMIN_COUPON", "")
REGISTRATION_OPEN = getenv("REGISTRATION_OPEN", "false").lower() in trues REGISTRATION_OPEN = getenv("REGISTRATION_OPEN", "false").lower() in trues
ASSET_FILTER = [
"LINK/USDT",
"PAXG/USD",
"PAXG/USDT",
"SHIB/USD",
"TRX/USD",
"TRX/USDT",
"UNI/BTC",
"UNI/USD",
"UNI/USDT",
"USDT/USD",
"WBTC/USD",
"YFI/BTC",
"NEAR/USDT",
"SUSHI/USDT",
"DOGE/USDT",
"LINK/BTC",
"LINK/USD",
"GRT/USD",
"AVAX/BTC",
"AVAX/USD",
"AVAX/USDT",
"SOL/BTC",
"SOL/USD",
"SOL/USDT",
"BTC/USDT",
"SUSHI/BTC",
"SUSHI/USD",
"BCH/BTC",
"BCH/USD",
"YFI/USD",
"ETH/USD",
"ETH/USDT",
"YFI/USDT",
"AAVE/USD",
"AAVE/USDT",
"ALGO/USD",
"BAT/USD",
"DAI/USDT",
"ALGO/USDT",
"MATIC/BTC",
"MATIC/USD",
"DOGE/USD",
"MKR/USD",
"BTC/USD",
"DOGE/BTC",
"LTC/BTC",
"LTC/USD",
"LTC/USDT",
"ETH/BTC",
"BCH/USDT",
"DAI/USD",
"NEAR/USD",
]
# Hook URL, do not include leading or trailing slash # Hook URL, do not include leading or trailing slash
HOOK_PATH = "hook" HOOK_PATH = "hook"

View File

@ -1,4 +1,6 @@
from alpaca.common.exceptions import APIError
from glom import glom from glom import glom
from oandapyV20.exceptions import V20Error
from core.lib import schemas from core.lib import schemas
from core.util import logs from core.util import logs
@ -135,7 +137,11 @@ class BaseExchange(object):
:raises NoSchema: If the method is not in the schema mapping :raises NoSchema: If the method is not in the schema mapping
:raises ValidationError: If the response cannot be validated :raises ValidationError: If the response cannot be validated
""" """
try:
response = self.call_method(method, *args, **kwargs) response = self.call_method(method, *args, **kwargs)
except (APIError, V20Error) as e:
log.error(f"Error calling method {method}: {e}")
raise GenericAPIError(e)
try: try:
response_valid = self.validate_response(response, method) response_valid = self.validate_response(response, method)
except NoSchema as e: except NoSchema as e:
@ -147,6 +153,7 @@ class BaseExchange(object):
except NoSchema as e: except NoSchema as e:
log.error(f"{e} - {response}") log.error(f"{e} - {response}")
response_converted = response_valid response_converted = response_valid
# return (True, response_converted) # return (True, response_converted)
return response_converted return response_converted

View File

@ -1,5 +1,5 @@
from oandapyV20 import API from oandapyV20 import API
from oandapyV20.endpoints import accounts, positions from oandapyV20.endpoints import accounts, orders, positions
from core.exchanges import BaseExchange from core.exchanges import BaseExchange
@ -26,16 +26,37 @@ class OANDAExchange(BaseExchange):
return [x["name"] for x in response["itemlist"]] return [x["name"] for x in response["itemlist"]]
def get_balance(self): def get_balance(self):
raise NotImplementedError r = accounts.AccountSummary(self.account_id)
response = self.call(r)
return float(response["balance"])
def get_market_value(self, symbol): def get_market_value(self, symbol):
raise NotImplementedError raise NotImplementedError
def post_trade(self, trade): def post_trade(self, trade):
raise NotImplementedError if trade.direction == "sell":
# r = orders.OrderCreate(accountID, data=data) amount = -trade.amount
# self.client.request(r) else:
# return r.response amount = trade.amount
data = {
"order": {
# "price": "1.5000", - added later
"stopLossOnFill": {"timeInForce": "GTC", "price": str(trade.stop_loss)},
"takeProfitOnFill": {"price": str(trade.take_profit)},
"timeInForce": "GTC",
"instrument": trade.symbol,
"units": str(amount),
"type": trade.type.upper(),
"positionFill": "DEFAULT",
}
}
print("SENDINGF ORDER", data)
if trade.type == "limit":
data["order"]["price"] = str(trade.price)
r = orders.OrderCreate(self.account_id, data=data)
response = self.call(r)
print("POSTED TRADE", response)
return response
def get_trade(self, trade_id): def get_trade(self, trade_id):
r = accounts.TradeDetails(accountID=self.account_id, tradeID=trade_id) r = accounts.TradeDetails(accountID=self.account_id, tradeID=trade_id)

View File

@ -1,3 +1,5 @@
from decimal import Decimal as D
from alpaca.common.exceptions import APIError from alpaca.common.exceptions import APIError
from core.models import Strategy, Trade from core.models import Strategy, Trade
@ -33,11 +35,14 @@ def execute_strategy(callback, strategy):
base = callback.base base = callback.base
quote = callback.quote quote = callback.quote
direction = hook.direction direction = hook.direction
if account.exchange == "alpaca":
if quote not in ["usd", "usdt", "usdc", "busd"]: if quote not in ["usd", "usdt", "usdc", "busd"]:
log.error(f"Quote not compatible with Dollar: {quote}") log.error(f"Quote not compatible with Dollar: {quote}")
return False return False
quote = "usd" # TODO: MASSIVE HACK quote = "usd" # TODO: MASSIVE HACK
symbol = f"{base.upper()}/{quote.upper()}" symbol = f"{base.upper()}/{quote.upper()}"
elif account.exchange == "oanda":
symbol = f"{base.upper()}_{quote.upper()}"
if symbol not in account.supported_symbols: if symbol not in account.supported_symbols:
log.error(f"Symbol not supported by account: {symbol}") log.error(f"Symbol not supported by account: {symbol}")
@ -53,32 +58,32 @@ def execute_strategy(callback, strategy):
# type = "limit" # type = "limit"
type = "market" type = "market"
trade_size_as_ratio = strategy.trade_size_percent / 100 trade_size_as_ratio = D(strategy.trade_size_percent) / D(100)
log.debug(f"Trade size as ratio: {trade_size_as_ratio}") log.debug(f"Trade size as ratio: {trade_size_as_ratio}")
amount_usd = trade_size_as_ratio * cash_balance amount_usd = D(trade_size_as_ratio) * D(cash_balance)
log.debug(f"Trade size: {amount_usd}") log.debug(f"Trade size: {amount_usd}")
price = callback.price price = round(D(callback.price), 8)
if not price: if not price:
return return
log.debug(f"Extracted price of quote: {price}") log.debug(f"Extracted price of quote: {price}")
# We can do this because the quote IS in $ or equivalent # We can do this because the quote IS in $ or equivalent
trade_size_in_quote = amount_usd / price trade_size_in_quote = D(amount_usd) / D(price)
log.debug(f"Trade size in quote: {trade_size_in_quote}") log.debug(f"Trade size in quote: {trade_size_in_quote}")
# calculate sl/tp # calculate sl/tp
stop_loss_as_ratio = strategy.stop_loss_percent / 100 stop_loss_as_ratio = D(strategy.stop_loss_percent) / D(100)
take_profit_as_ratio = strategy.take_profit_percent / 100 take_profit_as_ratio = D(strategy.take_profit_percent) / D(100)
log.debug(f"Stop loss as ratio: {stop_loss_as_ratio}") log.debug(f"Stop loss as ratio: {stop_loss_as_ratio}")
log.debug(f"Take profit as ratio: {take_profit_as_ratio}") log.debug(f"Take profit as ratio: {take_profit_as_ratio}")
stop_loss_subtract = price * stop_loss_as_ratio stop_loss_subtract = D(price) * D(stop_loss_as_ratio)
take_profit_add = price * take_profit_as_ratio take_profit_add = D(price) * D(take_profit_as_ratio)
log.debug(f"Stop loss subtract: {stop_loss_subtract}") log.debug(f"Stop loss subtract: {stop_loss_subtract}")
log.debug(f"Take profit add: {take_profit_add}") log.debug(f"Take profit add: {take_profit_add}")
stop_loss = price - stop_loss_subtract stop_loss = D(price) - D(stop_loss_subtract)
take_profit = price + take_profit_add take_profit = D(price) + D(take_profit_add)
log.debug(f"Stop loss: {stop_loss}") log.debug(f"Stop loss: {stop_loss}")
log.debug(f"Take profit: {take_profit}") log.debug(f"Take profit: {take_profit}")
@ -90,15 +95,15 @@ def execute_strategy(callback, strategy):
symbol=symbol, symbol=symbol,
type=type, type=type,
# amount_usd=amount_usd, # amount_usd=amount_usd,
amount=trade_size_in_quote, amount=float(round(trade_size_in_quote, 2)),
# price=price, # price=price,
stop_loss=stop_loss, stop_loss=float(round(stop_loss, 2)),
take_profit=take_profit, take_profit=float(round(take_profit, 2)),
direction=direction, direction=direction,
) )
new_trade.save() new_trade.save()
info = new_trade.post() info = new_trade.post()
log.debug(f"Posted trade: {posted} - {info}") log.debug(f"Posted trade: {info}")
def process_callback(callback): def process_callback(callback):

View File

@ -179,6 +179,106 @@ AccountDetailsSchema = {
} }
{
"account": {
"marginCloseoutNAV": "35454.4740",
"marginUsed": "10581.5000",
"currency": "EUR",
"resettablePL": "-13840.3525",
"NAV": "35454.4740",
"marginCloseoutMarginUsed": "10581.5000",
"marginCloseoutPositionValue": "211630.0000",
"openTradeCount": 2,
"id": "101-004-1435156-001",
"hedgingEnabled": False,
"marginCloseoutPercent": "0.14923",
"marginCallMarginUsed": "10581.5000",
"openPositionCount": 1,
"positionValue": "211630.0000",
"pl": "-13840.3525",
"lastTransactionID": "2123",
"marginAvailable": "24872.9740",
"marginRate": "0.05",
"marginCallPercent": "0.29845",
"pendingOrderCount": 0,
"withdrawalLimit": "24872.9740",
"unrealizedPL": "0.0000",
"alias": "hootnotv20",
"createdByUserID": 1435156,
"marginCloseoutUnrealizedPL": "0.0000",
"createdTime": "2016-06-24T21:03:50.914647476Z",
"balance": "35454.4740",
},
"lastTransactionID": "2123",
}
class AccountSummaryNested(BaseModel):
marginCloseoutNAV: str
marginUsed: str
currency: str
resettablePL: str
NAV: str
marginCloseoutMarginUsed: str
marginCloseoutPositionValue: str
openTradeCount: int
id: str
hedgingEnabled: bool
marginCloseoutPercent: str
marginCallMarginUsed: str
openPositionCount: int
positionValue: str
pl: str
lastTransactionID: str
marginAvailable: str
marginRate: str
marginCallPercent: str
pendingOrderCount: int
withdrawalLimit: str
unrealizedPL: str
alias: str
createdByUserID: int
marginCloseoutUnrealizedPL: str
createdTime: str
balance: str
class AccountSummary(BaseModel):
account: AccountSummaryNested
lastTransactionID: str
AccountSummarySchema = {
"marginCloseoutNAV": "account.marginCloseoutNAV",
"marginUsed": "account.marginUsed",
"currency": "account.currency",
"resettablePL": "account.resettablePL",
"NAV": "account.NAV",
"marginCloseoutMarginUsed": "account.marginCloseoutMarginUsed",
"marginCloseoutPositionValue": "account.marginCloseoutPositionValue",
"openTradeCount": "account.openTradeCount",
"id": "account.id",
"hedgingEnabled": "account.hedgingEnabled",
"marginCloseoutPercent": "account.marginCloseoutPercent",
"marginCallMarginUsed": "account.marginCallMarginUsed",
"openPositionCount": "account.openPositionCount",
"positionValue": "account.positionValue",
"pl": "account.pl",
"lastTransactionID": "account.lastTransactionID",
"marginAvailable": "account.marginAvailable",
"marginRate": "account.marginRate",
"marginCallPercent": "account.marginCallPercent",
"pendingOrderCount": "account.pendingOrderCount",
"withdrawalLimit": "account.withdrawalLimit",
"unrealizedPL": "account.unrealizedPL",
"alias": "account.alias",
"createdByUserID": "account.createdByUserID",
"marginCloseoutUnrealizedPL": "account.marginCloseoutUnrealizedPL",
"createdTime": "account.createdTime",
"balance": "account.balance",
}
class PositionDetailsNested(BaseModel): class PositionDetailsNested(BaseModel):
instrument: str instrument: str
long: PositionLong long: PositionLong

View File

@ -1,7 +1,6 @@
import re import re
import orjson import orjson
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from pydantic import ValidationError from pydantic import ValidationError
@ -56,9 +55,6 @@ class HookAPI(APIView):
base = hook_resp.market.item base = hook_resp.market.item
quote = hook_resp.market.currency quote = hook_resp.market.currency
symbol = f"{base.upper()}/{quote.upper()}" symbol = f"{base.upper()}/{quote.upper()}"
if symbol not in settings.ASSET_FILTER:
log.debug(f"Skipping {symbol} because it is not in the asset filter")
return HttpResponseBadRequest("Invalid symbol")
data = { data = {
"title": hook_resp.title, "title": hook_resp.title,

View File

@ -6,6 +6,7 @@ from django.shortcuts import render
from django.views import View from django.views import View
from rest_framework.parsers import FormParser from rest_framework.parsers import FormParser
from core.exchanges import GenericAPIError
from core.models import Account from core.models import Account
from core.util import logs from core.util import logs
@ -16,7 +17,10 @@ def get_positions(user, account_id=None):
items = [] items = []
accounts = Account.objects.filter(user=user) accounts = Account.objects.filter(user=user)
for account in accounts: for account in accounts:
try:
positions = account.client.get_all_positions() positions = account.client.get_all_positions()
except GenericAPIError:
continue
for item in positions: for item in positions:
items.append(item) items.append(item)