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 glom import glom
|
||||
from oandapyV20.exceptions import V20Error
|
||||
from pydantic.error_wrappers import ValidationError
|
||||
|
||||
from core.lib import schemas
|
||||
from core.util import logs
|
||||
@ -132,12 +131,7 @@ class BaseExchange(ABC):
|
||||
def validate_response(self, response, method):
|
||||
schema = self.get_schema(method)
|
||||
# Return a dict of the validated response
|
||||
try:
|
||||
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")
|
||||
response_valid = schema(**response).dict()
|
||||
return response_valid
|
||||
|
||||
def call(self, method, *args, **kwargs):
|
||||
@ -204,10 +198,6 @@ class BaseExchange(ABC):
|
||||
def post_trade(self, trade):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def close_trade(self, trade_id):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_trade(self, trade_id):
|
||||
pass
|
||||
|
@ -121,11 +121,6 @@ class AlpacaExchange(BaseExchange):
|
||||
trade.save()
|
||||
return order
|
||||
|
||||
def close_trade(self, trade_id): # TODO
|
||||
"""
|
||||
Close a trade
|
||||
"""
|
||||
|
||||
def get_trade(self, trade_id):
|
||||
pass # TODO
|
||||
|
||||
|
@ -94,13 +94,6 @@ class OANDAExchange(BaseExchange):
|
||||
trade.save()
|
||||
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):
|
||||
# OANDA is off by one...
|
||||
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):
|
||||
price: str
|
||||
liquidity: int
|
||||
@ -441,6 +476,11 @@ PricingInfoSchema = {
|
||||
}
|
||||
|
||||
|
||||
class LongPositionCloseout(BaseModel):
|
||||
instrument: str
|
||||
units: str
|
||||
|
||||
|
||||
class Trade(BaseModel):
|
||||
tradeID: str
|
||||
clientTradeID: str
|
||||
@ -458,52 +498,30 @@ class Trade(BaseModel):
|
||||
trailingStopLossOrder: dict | None
|
||||
|
||||
|
||||
class SideCarOrder(BaseModel):
|
||||
id: str
|
||||
createTime: str
|
||||
state: str
|
||||
price: str | None
|
||||
timeInForce: str
|
||||
gtdTime: str | None
|
||||
clientExtensions: dict | None
|
||||
tradeID: str
|
||||
clientTradeID: str | None
|
||||
type: str
|
||||
time: str | None
|
||||
priceBound: str | None
|
||||
positionFill: str | None
|
||||
reason: str | None
|
||||
orderFillTransactionID: str | None
|
||||
tradeOpenedID: str | None
|
||||
tradeReducedID: str | None
|
||||
tradeClosedIDs: list[str] | None
|
||||
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
|
||||
{
|
||||
"trades": [
|
||||
{
|
||||
"id": "14480",
|
||||
"instrument": "EUR_USD",
|
||||
"price": "1.06345",
|
||||
"openTime": "2022-12-22T08:57:10.459593310Z",
|
||||
"initialUnits": "100",
|
||||
"initialMarginRequired": "2.9226",
|
||||
"state": "OPEN",
|
||||
"currentUnits": "100",
|
||||
"realizedPL": "0.0000",
|
||||
"financing": "0.0000",
|
||||
"dividendAdjustment": "0.0000",
|
||||
"unrealizedPL": "-0.0158",
|
||||
"marginUsed": "2.9228",
|
||||
}
|
||||
],
|
||||
"lastTransactionID": "14480",
|
||||
}
|
||||
|
||||
|
||||
class OpenTrades(BaseModel):
|
||||
trades: list[OpenTradesTrade]
|
||||
trades: list[Trade]
|
||||
lastTransactionID: str
|
||||
|
||||
|
||||
@ -512,8 +530,8 @@ OpenTradesSchema = {
|
||||
"trades",
|
||||
[
|
||||
{
|
||||
"id": "id",
|
||||
"symbol": "instrument",
|
||||
"id": "tradeID",
|
||||
"instrument": "instrument",
|
||||
"price": "price",
|
||||
"openTime": "openTime",
|
||||
"initialUnits": "initialUnits",
|
||||
@ -528,7 +546,6 @@ OpenTradesSchema = {
|
||||
"takeProfitOrder": "takeProfitOrder",
|
||||
"stopLossOrder": "stopLossOrder",
|
||||
"trailingStopLossOrder": "trailingStopLossOrder",
|
||||
"trailingStopValue": "trailingStopValue",
|
||||
}
|
||||
],
|
||||
),
|
||||
@ -543,48 +560,6 @@ class HomeConversionFactors(BaseModel):
|
||||
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):
|
||||
id: str
|
||||
accountID: str
|
||||
@ -617,6 +592,23 @@ class LongOrderFillTransaction(BaseModel):
|
||||
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):
|
||||
longOrderCreateTransaction: OrderTransaction | None
|
||||
longOrderFillTransaction: OrderTransaction | None
|
||||
@ -686,25 +678,3 @@ TradeDetailsSchema = {
|
||||
"clientExtensions": "trade.clientExtensions",
|
||||
"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 core.models import Trade
|
||||
from core.tests.helpers import ElasticMock, LiveBase
|
||||
|
||||
|
||||
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):
|
||||
"""
|
||||
Test that the account is functional.
|
||||
"""
|
||||
balance = self.account.client.get_balance()
|
||||
# We need some money to place trades
|
||||
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")
|
||||
self.assertTrue(balance > 0)
|
||||
|
Loading…
Reference in New Issue
Block a user