Compare commits
No commits in common. "9dda0e8b4a01703b6478b1f165a3514e33acf830" and "e0ea4c86fa1d59093e53eefc64c959448f84b129" have entirely different histories.
9dda0e8b4a
...
e0ea4c86fa
@ -3,7 +3,6 @@ from abc import ABC, abstractmethod
|
|||||||
from alpaca.common.exceptions import APIError
|
from alpaca.common.exceptions import APIError
|
||||||
from glom import glom
|
from glom import glom
|
||||||
from oandapyV20.exceptions import V20Error
|
from oandapyV20.exceptions import V20Error
|
||||||
from pydantic.error_wrappers import ValidationError
|
|
||||||
|
|
||||||
from core.lib import schemas
|
from core.lib import schemas
|
||||||
from core.util import logs
|
from core.util import logs
|
||||||
@ -132,12 +131,7 @@ class BaseExchange(ABC):
|
|||||||
def validate_response(self, response, method):
|
def validate_response(self, response, method):
|
||||||
schema = self.get_schema(method)
|
schema = self.get_schema(method)
|
||||||
# Return a dict of the validated response
|
# Return a dict of the validated response
|
||||||
try:
|
response_valid = schema(**response).dict()
|
||||||
response_valid = schema(**response).dict()
|
|
||||||
except ValidationError as e:
|
|
||||||
log.error(f"Error validating {method} response: {response}")
|
|
||||||
log.error(f"Errors: {e}")
|
|
||||||
raise GenericAPIError("Error validating response")
|
|
||||||
return response_valid
|
return response_valid
|
||||||
|
|
||||||
def call(self, method, *args, **kwargs):
|
def call(self, method, *args, **kwargs):
|
||||||
@ -204,10 +198,6 @@ class BaseExchange(ABC):
|
|||||||
def post_trade(self, trade):
|
def post_trade(self, trade):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def close_trade(self, trade_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_trade(self, trade_id):
|
def get_trade(self, trade_id):
|
||||||
pass
|
pass
|
||||||
|
@ -121,11 +121,6 @@ class AlpacaExchange(BaseExchange):
|
|||||||
trade.save()
|
trade.save()
|
||||||
return order
|
return order
|
||||||
|
|
||||||
def close_trade(self, trade_id): # TODO
|
|
||||||
"""
|
|
||||||
Close a trade
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_trade(self, trade_id):
|
def get_trade(self, trade_id):
|
||||||
pass # TODO
|
pass # TODO
|
||||||
|
|
||||||
|
@ -94,13 +94,6 @@ class OANDAExchange(BaseExchange):
|
|||||||
trade.save()
|
trade.save()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def close_trade(self, trade_id):
|
|
||||||
"""
|
|
||||||
Close a trade.
|
|
||||||
"""
|
|
||||||
r = trades.TradeClose(accountID=self.account_id, tradeID=trade_id)
|
|
||||||
return self.call(r)
|
|
||||||
|
|
||||||
def get_trade(self, trade_id):
|
def get_trade(self, trade_id):
|
||||||
# OANDA is off by one...
|
# OANDA is off by one...
|
||||||
r = trades.TradeDetails(accountID=self.account_id, tradeID=trade_id)
|
r = trades.TradeDetails(accountID=self.account_id, tradeID=trade_id)
|
||||||
|
@ -386,6 +386,41 @@ AccountInstrumentsSchema = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class OrderTransaction(BaseModel):
|
||||||
|
id: str
|
||||||
|
accountID: str
|
||||||
|
userID: int
|
||||||
|
batchID: str
|
||||||
|
requestID: str
|
||||||
|
time: str
|
||||||
|
type: str
|
||||||
|
instrument: str
|
||||||
|
units: str
|
||||||
|
timeInForce: str
|
||||||
|
positionFill: str
|
||||||
|
reason: str
|
||||||
|
|
||||||
|
|
||||||
|
class OrderCreate(BaseModel):
|
||||||
|
orderCreateTransaction: OrderTransaction
|
||||||
|
|
||||||
|
|
||||||
|
OrderCreateSchema = {
|
||||||
|
"id": "orderCreateTransaction.id",
|
||||||
|
"accountID": "orderCreateTransaction.accountID",
|
||||||
|
"userID": "orderCreateTransaction.userID",
|
||||||
|
"batchID": "orderCreateTransaction.batchID",
|
||||||
|
"requestID": "orderCreateTransaction.requestID",
|
||||||
|
"time": "orderCreateTransaction.time",
|
||||||
|
"type": "orderCreateTransaction.type",
|
||||||
|
"symbol": "orderCreateTransaction.instrument",
|
||||||
|
"units": "orderCreateTransaction.units",
|
||||||
|
"timeInForce": "orderCreateTransaction.timeInForce",
|
||||||
|
"positionFill": "orderCreateTransaction.positionFill",
|
||||||
|
"reason": "orderCreateTransaction.reason",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class PriceBid(BaseModel):
|
class PriceBid(BaseModel):
|
||||||
price: str
|
price: str
|
||||||
liquidity: int
|
liquidity: int
|
||||||
@ -441,6 +476,11 @@ PricingInfoSchema = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LongPositionCloseout(BaseModel):
|
||||||
|
instrument: str
|
||||||
|
units: str
|
||||||
|
|
||||||
|
|
||||||
class Trade(BaseModel):
|
class Trade(BaseModel):
|
||||||
tradeID: str
|
tradeID: str
|
||||||
clientTradeID: str
|
clientTradeID: str
|
||||||
@ -458,52 +498,30 @@ class Trade(BaseModel):
|
|||||||
trailingStopLossOrder: dict | None
|
trailingStopLossOrder: dict | None
|
||||||
|
|
||||||
|
|
||||||
class SideCarOrder(BaseModel):
|
{
|
||||||
id: str
|
"trades": [
|
||||||
createTime: str
|
{
|
||||||
state: str
|
"id": "14480",
|
||||||
price: str | None
|
"instrument": "EUR_USD",
|
||||||
timeInForce: str
|
"price": "1.06345",
|
||||||
gtdTime: str | None
|
"openTime": "2022-12-22T08:57:10.459593310Z",
|
||||||
clientExtensions: dict | None
|
"initialUnits": "100",
|
||||||
tradeID: str
|
"initialMarginRequired": "2.9226",
|
||||||
clientTradeID: str | None
|
"state": "OPEN",
|
||||||
type: str
|
"currentUnits": "100",
|
||||||
time: str | None
|
"realizedPL": "0.0000",
|
||||||
priceBound: str | None
|
"financing": "0.0000",
|
||||||
positionFill: str | None
|
"dividendAdjustment": "0.0000",
|
||||||
reason: str | None
|
"unrealizedPL": "-0.0158",
|
||||||
orderFillTransactionID: str | None
|
"marginUsed": "2.9228",
|
||||||
tradeOpenedID: str | None
|
}
|
||||||
tradeReducedID: str | None
|
],
|
||||||
tradeClosedIDs: list[str] | None
|
"lastTransactionID": "14480",
|
||||||
cancellingTransactionID: str | None
|
}
|
||||||
replacesOrderID: str | None
|
|
||||||
replacedByOrderID: str | None
|
|
||||||
|
|
||||||
|
|
||||||
class OpenTradesTrade(BaseModel):
|
|
||||||
id: str
|
|
||||||
instrument: str
|
|
||||||
price: str
|
|
||||||
openTime: str
|
|
||||||
initialUnits: str
|
|
||||||
initialMarginRequired: str
|
|
||||||
state: str
|
|
||||||
currentUnits: str
|
|
||||||
realizedPL: str
|
|
||||||
financing: str
|
|
||||||
dividendAdjustment: str
|
|
||||||
unrealizedPL: str
|
|
||||||
marginUsed: str
|
|
||||||
takeProfitOrder: SideCarOrder | None
|
|
||||||
stopLossOrder: SideCarOrder | None
|
|
||||||
trailingStopLossOrder: SideCarOrder | None
|
|
||||||
trailingStopValue: dict | None
|
|
||||||
|
|
||||||
|
|
||||||
class OpenTrades(BaseModel):
|
class OpenTrades(BaseModel):
|
||||||
trades: list[OpenTradesTrade]
|
trades: list[Trade]
|
||||||
lastTransactionID: str
|
lastTransactionID: str
|
||||||
|
|
||||||
|
|
||||||
@ -512,8 +530,8 @@ OpenTradesSchema = {
|
|||||||
"trades",
|
"trades",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "id",
|
"id": "tradeID",
|
||||||
"symbol": "instrument",
|
"instrument": "instrument",
|
||||||
"price": "price",
|
"price": "price",
|
||||||
"openTime": "openTime",
|
"openTime": "openTime",
|
||||||
"initialUnits": "initialUnits",
|
"initialUnits": "initialUnits",
|
||||||
@ -528,7 +546,6 @@ OpenTradesSchema = {
|
|||||||
"takeProfitOrder": "takeProfitOrder",
|
"takeProfitOrder": "takeProfitOrder",
|
||||||
"stopLossOrder": "stopLossOrder",
|
"stopLossOrder": "stopLossOrder",
|
||||||
"trailingStopLossOrder": "trailingStopLossOrder",
|
"trailingStopLossOrder": "trailingStopLossOrder",
|
||||||
"trailingStopValue": "trailingStopValue",
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -543,48 +560,6 @@ class HomeConversionFactors(BaseModel):
|
|||||||
lossBaseHome: str
|
lossBaseHome: str
|
||||||
|
|
||||||
|
|
||||||
class LongPositionCloseout(BaseModel):
|
|
||||||
instrument: str
|
|
||||||
units: str
|
|
||||||
|
|
||||||
|
|
||||||
class OrderTransaction(BaseModel):
|
|
||||||
id: str
|
|
||||||
accountID: str
|
|
||||||
userID: int
|
|
||||||
batchID: str
|
|
||||||
requestID: str
|
|
||||||
time: str
|
|
||||||
type: str
|
|
||||||
instrument: str | None
|
|
||||||
units: str | None
|
|
||||||
timeInForce: str | None
|
|
||||||
positionFill: str | None
|
|
||||||
reason: str
|
|
||||||
longPositionCloseout: LongPositionCloseout | None
|
|
||||||
longOrderFillTransaction: dict | None
|
|
||||||
|
|
||||||
|
|
||||||
class OrderCreate(BaseModel):
|
|
||||||
orderCreateTransaction: OrderTransaction
|
|
||||||
|
|
||||||
|
|
||||||
OrderCreateSchema = {
|
|
||||||
"id": "orderCreateTransaction.id",
|
|
||||||
"accountID": "orderCreateTransaction.accountID",
|
|
||||||
"userID": "orderCreateTransaction.userID",
|
|
||||||
"batchID": "orderCreateTransaction.batchID",
|
|
||||||
"requestID": "orderCreateTransaction.requestID",
|
|
||||||
"time": "orderCreateTransaction.time",
|
|
||||||
"type": "orderCreateTransaction.type",
|
|
||||||
"symbol": "orderCreateTransaction.instrument",
|
|
||||||
"units": "orderCreateTransaction.units",
|
|
||||||
"timeInForce": "orderCreateTransaction.timeInForce",
|
|
||||||
"positionFill": "orderCreateTransaction.positionFill",
|
|
||||||
"reason": "orderCreateTransaction.reason",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class LongOrderFillTransaction(BaseModel):
|
class LongOrderFillTransaction(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
accountID: str
|
accountID: str
|
||||||
@ -617,6 +592,23 @@ class LongOrderFillTransaction(BaseModel):
|
|||||||
longPositionCloseout: LongPositionCloseout
|
longPositionCloseout: LongPositionCloseout
|
||||||
|
|
||||||
|
|
||||||
|
class OrderTransaction(BaseModel):
|
||||||
|
id: str
|
||||||
|
accountID: str
|
||||||
|
userID: int
|
||||||
|
batchID: str
|
||||||
|
requestID: str
|
||||||
|
time: str
|
||||||
|
type: str
|
||||||
|
instrument: str | None
|
||||||
|
units: str | None
|
||||||
|
timeInForce: str | None
|
||||||
|
positionFill: str | None
|
||||||
|
reason: str
|
||||||
|
longPositionCloseout: LongPositionCloseout | None
|
||||||
|
longOrderFillTransaction: dict | None
|
||||||
|
|
||||||
|
|
||||||
class PositionClose(BaseModel):
|
class PositionClose(BaseModel):
|
||||||
longOrderCreateTransaction: OrderTransaction | None
|
longOrderCreateTransaction: OrderTransaction | None
|
||||||
longOrderFillTransaction: OrderTransaction | None
|
longOrderFillTransaction: OrderTransaction | None
|
||||||
@ -686,25 +678,3 @@ TradeDetailsSchema = {
|
|||||||
"clientExtensions": "trade.clientExtensions",
|
"clientExtensions": "trade.clientExtensions",
|
||||||
"lastTransactionID": "lastTransactionID",
|
"lastTransactionID": "lastTransactionID",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TradeClose(BaseModel):
|
|
||||||
orderCreateTransaction: OrderTransaction
|
|
||||||
|
|
||||||
|
|
||||||
TradeCloseSchema = {
|
|
||||||
"id": "orderCreateTransaction.id",
|
|
||||||
"accountID": "orderCreateTransaction.accountID",
|
|
||||||
"userID": "orderCreateTransaction.userID",
|
|
||||||
"batchID": "orderCreateTransaction.batchID",
|
|
||||||
"requestID": "orderCreateTransaction.requestID",
|
|
||||||
"time": "orderCreateTransaction.time",
|
|
||||||
"type": "orderCreateTransaction.type",
|
|
||||||
"symbol": "orderCreateTransaction.instrument",
|
|
||||||
"units": "orderCreateTransaction.units",
|
|
||||||
"timeInForce": "orderCreateTransaction.timeInForce",
|
|
||||||
"positionFill": "orderCreateTransaction.positionFill",
|
|
||||||
"reason": "orderCreateTransaction.reason",
|
|
||||||
"longPositionCloseout": "orderCreateTransaction.longPositionCloseout",
|
|
||||||
"longOrderFillTransaction": "orderCreateTransaction.longOrderFillTransaction",
|
|
||||||
}
|
|
||||||
|
@ -1,80 +1,12 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from core.models import Trade
|
|
||||||
from core.tests.helpers import ElasticMock, LiveBase
|
from core.tests.helpers import ElasticMock, LiveBase
|
||||||
|
|
||||||
|
|
||||||
class LiveTradingTestCase(ElasticMock, LiveBase, TestCase):
|
class LiveTradingTestCase(ElasticMock, LiveBase, TestCase):
|
||||||
def setUp(self):
|
|
||||||
super(LiveTradingTestCase, self).setUp()
|
|
||||||
self.trade = Trade.objects.create(
|
|
||||||
user=self.user,
|
|
||||||
account=self.account,
|
|
||||||
symbol="EUR_USD",
|
|
||||||
time_in_force="FOK",
|
|
||||||
type="market",
|
|
||||||
amount=10,
|
|
||||||
direction="buy",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_account_functional(self):
|
def test_account_functional(self):
|
||||||
"""
|
"""
|
||||||
Test that the account is functional.
|
Test that the account is functional.
|
||||||
"""
|
"""
|
||||||
balance = self.account.client.get_balance()
|
balance = self.account.client.get_balance()
|
||||||
# We need some money to place trades
|
self.assertTrue(balance > 0)
|
||||||
self.assertTrue(balance > 1000)
|
|
||||||
|
|
||||||
def open_trade(self):
|
|
||||||
posted = self.trade.post()
|
|
||||||
# Check the opened trade
|
|
||||||
self.assertEqual(posted["type"], "MARKET_ORDER")
|
|
||||||
self.assertEqual(posted["symbol"], "EUR_USD")
|
|
||||||
self.assertEqual(posted["units"], "10")
|
|
||||||
self.assertEqual(posted["timeInForce"], "FOK")
|
|
||||||
|
|
||||||
return posted
|
|
||||||
|
|
||||||
def close_trade(self):
|
|
||||||
# refresh the trade to get the trade id
|
|
||||||
self.trade.refresh_from_db()
|
|
||||||
closed = self.account.client.close_trade(self.trade.order_id)
|
|
||||||
|
|
||||||
# Check the feedback from closing the trade
|
|
||||||
self.assertEqual(closed["type"], "MARKET_ORDER")
|
|
||||||
self.assertEqual(closed["symbol"], "EUR_USD")
|
|
||||||
self.assertEqual(closed["units"], "-10")
|
|
||||||
self.assertEqual(closed["timeInForce"], "FOK")
|
|
||||||
self.assertEqual(closed["reason"], "TRADE_CLOSE")
|
|
||||||
|
|
||||||
return closed
|
|
||||||
|
|
||||||
def test_place_close_trade(self):
|
|
||||||
"""
|
|
||||||
Test placing a trade.
|
|
||||||
"""
|
|
||||||
self.open_trade()
|
|
||||||
self.close_trade()
|
|
||||||
|
|
||||||
def test_get_all_open_trades(self):
|
|
||||||
"""
|
|
||||||
Test getting all open trades.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.open_trade()
|
|
||||||
|
|
||||||
trades = self.account.client.get_all_open_trades()
|
|
||||||
self.trade.refresh_from_db()
|
|
||||||
found = False
|
|
||||||
for trade in trades["itemlist"]:
|
|
||||||
if trade["id"] == self.trade.order_id:
|
|
||||||
self.assertEqual(trade["symbol"], "EUR_USD")
|
|
||||||
self.assertEqual(trade["currentUnits"], "10")
|
|
||||||
self.assertEqual(trade["initialUnits"], "10")
|
|
||||||
self.assertEqual(trade["state"], "OPEN")
|
|
||||||
found = True
|
|
||||||
break
|
|
||||||
self.close_trade()
|
|
||||||
|
|
||||||
if not found:
|
|
||||||
self.fail("Could not find the trade in the list of open trades")
|
|
||||||
|
Loading…
Reference in New Issue
Block a user