Remove asset filter and begin implementing posting trades
This commit is contained in:
parent
47384aed5f
commit
40f6330a13
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue