1861 lines
79 KiB
Python
1861 lines
79 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
|
|
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
|
|
|
|
from ccxt.async_support.base.exchange import Exchange
|
|
from ccxt.abstract.alpaca import ImplicitAPI
|
|
from ccxt.base.types import Any, Balances, Currency, DepositAddress, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
|
|
from typing import List
|
|
from ccxt.base.errors import ExchangeError
|
|
from ccxt.base.errors import PermissionDenied
|
|
from ccxt.base.errors import ArgumentsRequired
|
|
from ccxt.base.errors import BadRequest
|
|
from ccxt.base.errors import BadSymbol
|
|
from ccxt.base.errors import InsufficientFunds
|
|
from ccxt.base.errors import InvalidOrder
|
|
from ccxt.base.errors import NotSupported
|
|
from ccxt.base.errors import RateLimitExceeded
|
|
from ccxt.base.decimal_to_precision import TICK_SIZE
|
|
from ccxt.base.precise import Precise
|
|
|
|
|
|
class alpaca(Exchange, ImplicitAPI):
|
|
|
|
def describe(self) -> Any:
|
|
return self.deep_extend(super(alpaca, self).describe(), {
|
|
'id': 'alpaca',
|
|
'name': 'Alpaca',
|
|
'countries': ['US'],
|
|
# 3 req/s for free
|
|
# 150 req/s for subscribers: https://alpaca.markets/data
|
|
# for brokers: https://alpaca.markets/docs/api-references/broker-api/#authentication-and-rate-limit
|
|
'rateLimit': 333,
|
|
'hostname': 'alpaca.markets',
|
|
'pro': True,
|
|
'urls': {
|
|
'logo': 'https://github.com/user-attachments/assets/e9476df8-a450-4c3e-ab9a-1a7794219e1b',
|
|
'www': 'https://alpaca.markets',
|
|
'api': {
|
|
'broker': 'https://broker-api.{hostname}',
|
|
'trader': 'https://api.{hostname}',
|
|
'market': 'https://data.{hostname}',
|
|
},
|
|
'test': {
|
|
'broker': 'https://broker-api.sandbox.{hostname}',
|
|
'trader': 'https://paper-api.{hostname}',
|
|
'market': 'https://data.{hostname}',
|
|
},
|
|
'doc': 'https://alpaca.markets/docs/',
|
|
'fees': 'https://docs.alpaca.markets/docs/crypto-fees',
|
|
},
|
|
'has': {
|
|
'CORS': False,
|
|
'spot': True,
|
|
'margin': False,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'addMargin': False,
|
|
'borrowCrossMargin': False,
|
|
'borrowIsolatedMargin': False,
|
|
'borrowMargin': False,
|
|
'cancelAllOrders': True,
|
|
'cancelOrder': True,
|
|
'closeAllPositions': False,
|
|
'closePosition': False,
|
|
'createMarketBuyOrder': True,
|
|
'createMarketBuyOrderWithCost': True,
|
|
'createMarketOrderWithCost': True,
|
|
'createOrder': True,
|
|
'createOrderWithTakeProfitAndStopLoss': False,
|
|
'createOrderWithTakeProfitAndStopLossWs': False,
|
|
'createReduceOnlyOrder': False,
|
|
'createStopOrder': True,
|
|
'createTriggerOrder': True,
|
|
'editOrder': True,
|
|
'fetchBalance': True,
|
|
'fetchBidsAsks': False,
|
|
'fetchBorrowInterest': False,
|
|
'fetchBorrowRate': False,
|
|
'fetchBorrowRateHistories': False,
|
|
'fetchBorrowRateHistory': False,
|
|
'fetchBorrowRates': False,
|
|
'fetchBorrowRatesPerSymbol': False,
|
|
'fetchClosedOrders': True,
|
|
'fetchCrossBorrowRate': False,
|
|
'fetchCrossBorrowRates': False,
|
|
'fetchCurrencies': False,
|
|
'fetchDepositAddress': True,
|
|
'fetchDepositAddressesByNetwork': False,
|
|
'fetchDeposits': True,
|
|
'fetchDepositsWithdrawals': True,
|
|
'fetchFundingHistory': False,
|
|
'fetchFundingInterval': False,
|
|
'fetchFundingIntervals': False,
|
|
'fetchFundingRate': False,
|
|
'fetchFundingRateHistory': False,
|
|
'fetchFundingRates': False,
|
|
'fetchGreeks': False,
|
|
'fetchIndexOHLCV': False,
|
|
'fetchIsolatedBorrowRate': False,
|
|
'fetchIsolatedBorrowRates': False,
|
|
'fetchIsolatedPositions': False,
|
|
'fetchL1OrderBook': True,
|
|
'fetchL2OrderBook': False,
|
|
'fetchLeverage': False,
|
|
'fetchLeverages': False,
|
|
'fetchLeverageTiers': False,
|
|
'fetchLiquidations': False,
|
|
'fetchLongShortRatio': False,
|
|
'fetchLongShortRatioHistory': False,
|
|
'fetchMarginAdjustmentHistory': False,
|
|
'fetchMarginMode': False,
|
|
'fetchMarginModes': False,
|
|
'fetchMarketLeverageTiers': False,
|
|
'fetchMarkets': True,
|
|
'fetchMarkOHLCV': False,
|
|
'fetchMarkPrices': False,
|
|
'fetchMyLiquidations': False,
|
|
'fetchMySettlementHistory': False,
|
|
'fetchMyTrades': True,
|
|
'fetchOHLCV': True,
|
|
'fetchOpenInterest': False,
|
|
'fetchOpenInterestHistory': False,
|
|
'fetchOpenInterests': False,
|
|
'fetchOpenOrder': False,
|
|
'fetchOpenOrders': True,
|
|
'fetchOption': False,
|
|
'fetchOptionChain': False,
|
|
'fetchOrder': True,
|
|
'fetchOrderBook': True,
|
|
'fetchOrders': True,
|
|
'fetchPosition': False,
|
|
'fetchPositionHistory': False,
|
|
'fetchPositionMode': False,
|
|
'fetchPositions': False,
|
|
'fetchPositionsForSymbol': False,
|
|
'fetchPositionsHistory': False,
|
|
'fetchPositionsRisk': False,
|
|
'fetchPremiumIndexOHLCV': False,
|
|
'fetchSettlementHistory': False,
|
|
'fetchStatus': False,
|
|
'fetchTicker': True,
|
|
'fetchTickers': True,
|
|
'fetchTime': True,
|
|
'fetchTrades': True,
|
|
'fetchTradingFee': False,
|
|
'fetchTradingFees': False,
|
|
'fetchTransactionFees': False,
|
|
'fetchTransactions': False,
|
|
'fetchTransfers': False,
|
|
'fetchVolatilityHistory': False,
|
|
'fetchWithdrawals': True,
|
|
'reduceMargin': False,
|
|
'repayCrossMargin': False,
|
|
'repayIsolatedMargin': False,
|
|
'sandbox': True,
|
|
'setLeverage': False,
|
|
'setMargin': False,
|
|
'setMarginMode': False,
|
|
'setPositionMode': False,
|
|
'transfer': False,
|
|
'withdraw': True,
|
|
},
|
|
'api': {
|
|
'broker': {
|
|
},
|
|
'trader': {
|
|
'private': {
|
|
'get': [
|
|
'v2/account',
|
|
'v2/orders',
|
|
'v2/orders/{order_id}',
|
|
'v2/positions',
|
|
'v2/positions/{symbol_or_asset_id}',
|
|
'v2/account/portfolio/history',
|
|
'v2/watchlists',
|
|
'v2/watchlists/{watchlist_id}',
|
|
'v2/watchlists:by_name',
|
|
'v2/account/configurations',
|
|
'v2/account/activities',
|
|
'v2/account/activities/{activity_type}',
|
|
'v2/calendar',
|
|
'v2/clock',
|
|
'v2/assets',
|
|
'v2/assets/{symbol_or_asset_id}',
|
|
'v2/corporate_actions/announcements/{id}',
|
|
'v2/corporate_actions/announcements',
|
|
'v2/wallets',
|
|
'v2/wallets/transfers',
|
|
],
|
|
'post': [
|
|
'v2/orders',
|
|
'v2/watchlists',
|
|
'v2/watchlists/{watchlist_id}',
|
|
'v2/watchlists:by_name',
|
|
'v2/wallets/transfers',
|
|
],
|
|
'put': [
|
|
'v2/orders/{order_id}',
|
|
'v2/watchlists/{watchlist_id}',
|
|
'v2/watchlists:by_name',
|
|
],
|
|
'patch': [
|
|
'v2/orders/{order_id}',
|
|
'v2/account/configurations',
|
|
],
|
|
'delete': [
|
|
'v2/orders',
|
|
'v2/orders/{order_id}',
|
|
'v2/positions',
|
|
'v2/positions/{symbol_or_asset_id}',
|
|
'v2/watchlists/{watchlist_id}',
|
|
'v2/watchlists:by_name',
|
|
'v2/watchlists/{watchlist_id}/{symbol}',
|
|
],
|
|
},
|
|
},
|
|
'market': {
|
|
'public': {
|
|
'get': [
|
|
'v1beta3/crypto/{loc}/bars',
|
|
'v1beta3/crypto/{loc}/latest/bars',
|
|
'v1beta3/crypto/{loc}/latest/orderbooks',
|
|
'v1beta3/crypto/{loc}/latest/quotes',
|
|
'v1beta3/crypto/{loc}/latest/trades',
|
|
'v1beta3/crypto/{loc}/quotes',
|
|
'v1beta3/crypto/{loc}/snapshots',
|
|
'v1beta3/crypto/{loc}/trades',
|
|
],
|
|
},
|
|
'private': {
|
|
'get': [
|
|
'v1beta1/corporate-actions',
|
|
'v1beta1/forex/latest/rates',
|
|
'v1beta1/forex/rates',
|
|
'v1beta1/logos/{symbol}',
|
|
'v1beta1/news',
|
|
'v1beta1/screener/stocks/most-actives',
|
|
'v1beta1/screener/{market_type}/movers',
|
|
'v2/stocks/auctions',
|
|
'v2/stocks/bars',
|
|
'v2/stocks/bars/latest',
|
|
'v2/stocks/meta/conditions/{ticktype}',
|
|
'v2/stocks/meta/exchanges',
|
|
'v2/stocks/quotes',
|
|
'v2/stocks/quotes/latest',
|
|
'v2/stocks/snapshots',
|
|
'v2/stocks/trades',
|
|
'v2/stocks/trades/latest',
|
|
'v2/stocks/{symbol}/auctions',
|
|
'v2/stocks/{symbol}/bars',
|
|
'v2/stocks/{symbol}/bars/latest',
|
|
'v2/stocks/{symbol}/quotes',
|
|
'v2/stocks/{symbol}/quotes/latest',
|
|
'v2/stocks/{symbol}/snapshot',
|
|
'v2/stocks/{symbol}/trades',
|
|
'v2/stocks/{symbol}/trades/latest',
|
|
],
|
|
},
|
|
},
|
|
},
|
|
'timeframes': {
|
|
'1m': '1min',
|
|
'3m': '3min',
|
|
'5m': '5min',
|
|
'15m': '15min',
|
|
'30m': '30min',
|
|
'1h': '1H',
|
|
'2h': '2H',
|
|
'4h': '4H',
|
|
'6h': '6H',
|
|
'8h': '8H',
|
|
'12h': '12H',
|
|
'1d': '1D',
|
|
'3d': '3D',
|
|
'1w': '1W',
|
|
'1M': '1M',
|
|
},
|
|
'precisionMode': TICK_SIZE,
|
|
'requiredCredentials': {
|
|
'apiKey': True,
|
|
'secret': True,
|
|
},
|
|
'fees': {
|
|
'trading': {
|
|
'tierBased': True,
|
|
'percentage': True,
|
|
'maker': self.parse_number('0.0015'),
|
|
'taker': self.parse_number('0.0025'),
|
|
'tiers': {
|
|
'taker': [
|
|
[self.parse_number('0'), self.parse_number('0.0025')],
|
|
[self.parse_number('100000'), self.parse_number('0.0022')],
|
|
[self.parse_number('500000'), self.parse_number('0.0020')],
|
|
[self.parse_number('1000000'), self.parse_number('0.0018')],
|
|
[self.parse_number('10000000'), self.parse_number('0.0015')],
|
|
[self.parse_number('25000000'), self.parse_number('0.0013')],
|
|
[self.parse_number('50000000'), self.parse_number('0.0012')],
|
|
[self.parse_number('100000000'), self.parse_number('0.001')],
|
|
],
|
|
'maker': [
|
|
[self.parse_number('0'), self.parse_number('0.0015')],
|
|
[self.parse_number('100000'), self.parse_number('0.0012')],
|
|
[self.parse_number('500000'), self.parse_number('0.001')],
|
|
[self.parse_number('1000000'), self.parse_number('0.0008')],
|
|
[self.parse_number('10000000'), self.parse_number('0.0005')],
|
|
[self.parse_number('25000000'), self.parse_number('0.0002')],
|
|
[self.parse_number('50000000'), self.parse_number('0.0002')],
|
|
[self.parse_number('100000000'), self.parse_number('0.00')],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
'headers': {
|
|
'APCA-PARTNER-ID': 'ccxt',
|
|
},
|
|
'options': {
|
|
'defaultExchange': 'CBSE',
|
|
'exchanges': [
|
|
'CBSE', # Coinbase
|
|
'FTX', # FTXUS
|
|
'GNSS', # Genesis
|
|
'ERSX', # ErisX
|
|
],
|
|
'defaultTimeInForce': 'gtc', # fok, gtc, ioc
|
|
'clientOrderId': 'ccxt_{id}',
|
|
},
|
|
'features': {
|
|
'spot': {
|
|
'sandbox': True,
|
|
'createOrder': {
|
|
'marginMode': False,
|
|
'triggerPrice': True,
|
|
'triggerPriceType': None,
|
|
'triggerDirection': False,
|
|
'stopLossPrice': False, # todo
|
|
'takeProfitPrice': False, # todo
|
|
'attachedStopLossTakeProfit': {
|
|
'triggerPriceType': {
|
|
'last': True,
|
|
'mark': True,
|
|
'index': True,
|
|
},
|
|
'price': True,
|
|
},
|
|
'timeInForce': {
|
|
'IOC': True,
|
|
'FOK': True,
|
|
'PO': True,
|
|
'GTD': False,
|
|
},
|
|
'hedged': False,
|
|
'trailing': True, # todo: implementation
|
|
'leverage': False,
|
|
'marketBuyRequiresPrice': False,
|
|
'marketBuyByCost': False,
|
|
'selfTradePrevention': False,
|
|
'iceberg': False,
|
|
},
|
|
'createOrders': None,
|
|
'fetchMyTrades': {
|
|
'marginMode': False,
|
|
'limit': 100,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrder': {
|
|
'marginMode': False,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOpenOrders': {
|
|
'marginMode': False,
|
|
'limit': 500,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOrders': {
|
|
'marginMode': False,
|
|
'limit': 500,
|
|
'daysBack': 100000,
|
|
'untilDays': 100000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchClosedOrders': {
|
|
'marginMode': False,
|
|
'limit': 500,
|
|
'daysBack': 100000,
|
|
'daysBackCanceled': None,
|
|
'untilDays': 100000,
|
|
'trigger': False,
|
|
'trailing': False,
|
|
'symbolRequired': False,
|
|
},
|
|
'fetchOHLCV': {
|
|
'limit': 1000,
|
|
},
|
|
},
|
|
'swap': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
'future': {
|
|
'linear': None,
|
|
'inverse': None,
|
|
},
|
|
},
|
|
'exceptions': {
|
|
'exact': {
|
|
'forbidden.': PermissionDenied, # {"message": "forbidden."}
|
|
'40410000': InvalidOrder, # {"code": 40410000, "message": "order is not found."}
|
|
'40010001': BadRequest, # {"code":40010001,"message":"invalid order type for crypto order"}
|
|
'40110000': PermissionDenied, # {"code": 40110000, "message": "request is not authorized"}
|
|
'40310000': InsufficientFunds, # {"available":"0","balance":"0","code":40310000,"message":"insufficient balance for USDT(requested: 221.63, available: 0)","symbol":"USDT"}
|
|
'42910000': RateLimitExceeded, # {"code":42910000,"message":"rate limit exceeded"}
|
|
},
|
|
'broad': {
|
|
'Invalid format for parameter': BadRequest, # {"message":"Invalid format for parameter start: error parsing '0' or 2006-01-02 time: parsing time \"0\" as \"2006-01-02\": cannot parse \"0\" as \"2006\""}
|
|
'Invalid symbol': BadSymbol, # {"message":"Invalid symbol(s): BTC/USDdsda does not match ^[A-Z]+/[A-Z]+$"}
|
|
},
|
|
},
|
|
})
|
|
|
|
async def fetch_time(self, params={}) -> Int:
|
|
"""
|
|
fetches the current integer timestamp in milliseconds from the exchange server
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns int: the current integer timestamp in milliseconds from the exchange server
|
|
"""
|
|
response = await self.traderPrivateGetV2Clock(params)
|
|
#
|
|
# {
|
|
# timestamp: '2023-11-22T08:07:57.654738097-05:00',
|
|
# is_open: False,
|
|
# next_open: '2023-11-22T09:30:00-05:00',
|
|
# next_close: '2023-11-22T16:00:00-05:00'
|
|
# }
|
|
#
|
|
timestamp = self.safe_string(response, 'timestamp')
|
|
localTime = timestamp[0:23]
|
|
jetlagStrStart = len(timestamp) - 6
|
|
jetlagStrEnd = len(timestamp) - 3
|
|
jetlag = timestamp[jetlagStrStart:jetlagStrEnd]
|
|
iso = self.parse8601(localTime) - self.parse_to_numeric(jetlag) * 3600 * 1000
|
|
return iso
|
|
|
|
async def fetch_markets(self, params={}) -> List[Market]:
|
|
"""
|
|
retrieves data on all markets for alpaca
|
|
|
|
https://docs.alpaca.markets/reference/get-v2-assets
|
|
|
|
:param dict [params]: extra parameters specific to the exchange api endpoint
|
|
:returns dict[]: an array of objects representing market data
|
|
"""
|
|
request: dict = {
|
|
'asset_class': 'crypto',
|
|
'status': 'active',
|
|
}
|
|
assets = await self.traderPrivateGetV2Assets(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id": "c150e086-1e75-44e6-9c2c-093bb1e93139",
|
|
# "class": "crypto",
|
|
# "exchange": "CRYPTO",
|
|
# "symbol": "BTC/USDT",
|
|
# "name": "Bitcoin / USD Tether",
|
|
# "status": "active",
|
|
# "tradable": True,
|
|
# "marginable": False,
|
|
# "maintenance_margin_requirement": 100,
|
|
# "shortable": False,
|
|
# "easy_to_borrow": False,
|
|
# "fractionable": True,
|
|
# "attributes": [],
|
|
# "min_order_size": "0.000026873",
|
|
# "min_trade_increment": "0.000000001",
|
|
# "price_increment": "1"
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_markets(assets)
|
|
|
|
def parse_market(self, asset) -> Market:
|
|
#
|
|
# {
|
|
# "id": "c150e086-1e75-44e6-9c2c-093bb1e93139",
|
|
# "class": "crypto",
|
|
# "exchange": "CRYPTO",
|
|
# "symbol": "BTC/USDT",
|
|
# "name": "Bitcoin / USD Tether",
|
|
# "status": "active",
|
|
# "tradable": True,
|
|
# "marginable": False,
|
|
# "maintenance_margin_requirement": 101,
|
|
# "shortable": False,
|
|
# "easy_to_borrow": False,
|
|
# "fractionable": True,
|
|
# "attributes": [],
|
|
# "min_order_size": "0.000026873",
|
|
# "min_trade_increment": "0.000000001",
|
|
# "price_increment": "1"
|
|
# }
|
|
#
|
|
marketId = self.safe_string(asset, 'symbol')
|
|
parts = marketId.split('/')
|
|
assetClass = self.safe_string(asset, 'class')
|
|
baseId = self.safe_string(parts, 0)
|
|
quoteId = self.safe_string(parts, 1)
|
|
base = self.safe_currency_code(baseId)
|
|
quote = self.safe_currency_code(quoteId)
|
|
# Us equity markets do not include quote in symbol.
|
|
# We can safely coerce us_equity quote to USD
|
|
if quote is None and assetClass == 'us_equity':
|
|
quote = 'USD'
|
|
symbol = base + '/' + quote
|
|
status = self.safe_string(asset, 'status')
|
|
active = (status == 'active')
|
|
minAmount = self.safe_number(asset, 'min_order_size')
|
|
amount = self.safe_number(asset, 'min_trade_increment')
|
|
price = self.safe_number(asset, 'price_increment')
|
|
return {
|
|
'id': marketId,
|
|
'symbol': symbol,
|
|
'base': base,
|
|
'quote': quote,
|
|
'settle': None,
|
|
'baseId': baseId,
|
|
'quoteId': quoteId,
|
|
'settleId': None,
|
|
'type': 'spot',
|
|
'spot': True,
|
|
'margin': None,
|
|
'swap': False,
|
|
'future': False,
|
|
'option': False,
|
|
'active': active,
|
|
'contract': False,
|
|
'linear': None,
|
|
'inverse': None,
|
|
'contractSize': None,
|
|
'expiry': None,
|
|
'expiryDatetime': None,
|
|
'strike': None,
|
|
'optionType': None,
|
|
'precision': {
|
|
'amount': amount,
|
|
'price': price,
|
|
},
|
|
'limits': {
|
|
'leverage': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'amount': {
|
|
'min': minAmount,
|
|
'max': None,
|
|
},
|
|
'price': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
'cost': {
|
|
'min': None,
|
|
'max': None,
|
|
},
|
|
},
|
|
'created': None,
|
|
'info': asset,
|
|
}
|
|
|
|
async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
|
|
"""
|
|
get the list of most recent trades for a particular symbol
|
|
|
|
https://docs.alpaca.markets/reference/cryptotrades
|
|
https://docs.alpaca.markets/reference/cryptolatesttrades
|
|
|
|
:param str symbol: unified symbol of the market to fetch trades for
|
|
:param int [since]: timestamp in ms of the earliest trade to fetch
|
|
:param int [limit]: the maximum amount of trades to fetch
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.loc]: crypto location, default: us
|
|
:param str [params.method]: method, default: marketPublicGetV1beta3CryptoLocTrades
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=public-trades>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
marketId = market['id']
|
|
loc = self.safe_string(params, 'loc', 'us')
|
|
method = self.safe_string(params, 'method', 'marketPublicGetV1beta3CryptoLocTrades')
|
|
request: dict = {
|
|
'symbols': marketId,
|
|
'loc': loc,
|
|
}
|
|
params = self.omit(params, ['loc', 'method'])
|
|
symbolTrades = None
|
|
if method == 'marketPublicGetV1beta3CryptoLocTrades':
|
|
if since is not None:
|
|
request['start'] = self.iso8601(since)
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.marketPublicGetV1beta3CryptoLocTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "next_page_token": null,
|
|
# "trades": {
|
|
# "BTC/USD": [
|
|
# {
|
|
# "i": 36440704,
|
|
# "p": 22625,
|
|
# "s": 0.0001,
|
|
# "t": "2022-07-21T11:47:31.073391Z",
|
|
# "tks": "B"
|
|
# }
|
|
# ]
|
|
# }
|
|
# }
|
|
#
|
|
trades = self.safe_dict(response, 'trades', {})
|
|
symbolTrades = self.safe_list(trades, marketId, [])
|
|
elif method == 'marketPublicGetV1beta3CryptoLocLatestTrades':
|
|
response = await self.marketPublicGetV1beta3CryptoLocLatestTrades(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "trades": {
|
|
# "BTC/USD": {
|
|
# "i": 36440704,
|
|
# "p": 22625,
|
|
# "s": 0.0001,
|
|
# "t": "2022-07-21T11:47:31.073391Z",
|
|
# "tks": "B"
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
trades = self.safe_dict(response, 'trades', {})
|
|
symbolTrades = self.safe_dict(trades, marketId, {})
|
|
symbolTrades = [symbolTrades]
|
|
else:
|
|
raise NotSupported(self.id + ' fetchTrades() does not support ' + method + ', marketPublicGetV1beta3CryptoLocTrades and marketPublicGetV1beta3CryptoLocLatestTrades are supported')
|
|
return self.parse_trades(symbolTrades, market, since, limit)
|
|
|
|
async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
|
|
"""
|
|
fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
|
|
|
|
https://docs.alpaca.markets/reference/cryptolatestorderbooks
|
|
|
|
:param str symbol: unified symbol of the market to fetch the order book for
|
|
:param int [limit]: the maximum amount of order book entries to return
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.loc]: crypto location, default: us
|
|
:returns dict: A dictionary of `order book structures <https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure>` indexed by market symbols
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
id = market['id']
|
|
loc = self.safe_string(params, 'loc', 'us')
|
|
request: dict = {
|
|
'symbols': id,
|
|
'loc': loc,
|
|
}
|
|
response = await self.marketPublicGetV1beta3CryptoLocLatestOrderbooks(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "orderbooks":{
|
|
# "BTC/USD":{
|
|
# "a":[
|
|
# {
|
|
# "p":22208,
|
|
# "s":0.0051
|
|
# },
|
|
# {
|
|
# "p":22209,
|
|
# "s":0.1123
|
|
# },
|
|
# {
|
|
# "p":22210,
|
|
# "s":0.2465
|
|
# }
|
|
# ],
|
|
# "b":[
|
|
# {
|
|
# "p":22203,
|
|
# "s":0.395
|
|
# },
|
|
# {
|
|
# "p":22202,
|
|
# "s":0.2465
|
|
# },
|
|
# {
|
|
# "p":22201,
|
|
# "s":0.6455
|
|
# }
|
|
# ],
|
|
# "t":"2022-07-19T13:41:55.13210112Z"
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
orderbooks = self.safe_dict(response, 'orderbooks', {})
|
|
rawOrderbook = self.safe_dict(orderbooks, id, {})
|
|
timestamp = self.parse8601(self.safe_string(rawOrderbook, 't'))
|
|
return self.parse_order_book(rawOrderbook, market['symbol'], timestamp, 'b', 'a', 'p', 's')
|
|
|
|
async def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
|
|
"""
|
|
fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
|
|
|
|
https://docs.alpaca.markets/reference/cryptobars
|
|
https://docs.alpaca.markets/reference/cryptolatestbars
|
|
|
|
:param str symbol: unified symbol of the market to fetch OHLCV data for
|
|
:param str timeframe: the length of time each candle represents
|
|
:param int [since]: timestamp in ms of the earliest candle to fetch
|
|
:param int [limit]: the maximum amount of candles to fetch
|
|
:param dict [params]: extra parameters specific to the alpha api endpoint
|
|
:param str [params.loc]: crypto location, default: us
|
|
:param str [params.method]: method, default: marketPublicGetV1beta3CryptoLocBars
|
|
:returns int[][]: A list of candles ordered, open, high, low, close, volume
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
marketId = market['id']
|
|
loc = self.safe_string(params, 'loc', 'us')
|
|
method = self.safe_string(params, 'method', 'marketPublicGetV1beta3CryptoLocBars')
|
|
request: dict = {
|
|
'symbols': marketId,
|
|
'loc': loc,
|
|
}
|
|
params = self.omit(params, ['loc', 'method'])
|
|
ohlcvs = None
|
|
if method == 'marketPublicGetV1beta3CryptoLocBars':
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
if since is not None:
|
|
request['start'] = self.yyyymmdd(since)
|
|
request['timeframe'] = self.safe_string(self.timeframes, timeframe, timeframe)
|
|
response = await self.marketPublicGetV1beta3CryptoLocBars(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "bars": {
|
|
# "BTC/USD": [
|
|
# {
|
|
# "c": 22887,
|
|
# "h": 22888,
|
|
# "l": 22873,
|
|
# "n": 11,
|
|
# "o": 22883,
|
|
# "t": "2022-07-21T05:00:00Z",
|
|
# "v": 1.1138,
|
|
# "vw": 22883.0155324116
|
|
# },
|
|
# {
|
|
# "c": 22895,
|
|
# "h": 22895,
|
|
# "l": 22884,
|
|
# "n": 6,
|
|
# "o": 22884,
|
|
# "t": "2022-07-21T05:01:00Z",
|
|
# "v": 0.001,
|
|
# "vw": 22889.5
|
|
# }
|
|
# ]
|
|
# },
|
|
# "next_page_token": "QlRDL1VTRHxNfDIwMjItMDctMjFUMDU6MDE6MDAuMDAwMDAwMDAwWg=="
|
|
# }
|
|
#
|
|
bars = self.safe_dict(response, 'bars', {})
|
|
ohlcvs = self.safe_list(bars, marketId, [])
|
|
elif method == 'marketPublicGetV1beta3CryptoLocLatestBars':
|
|
response = await self.marketPublicGetV1beta3CryptoLocLatestBars(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "bars": {
|
|
# "BTC/USD": {
|
|
# "c": 22887,
|
|
# "h": 22888,
|
|
# "l": 22873,
|
|
# "n": 11,
|
|
# "o": 22883,
|
|
# "t": "2022-07-21T05:00:00Z",
|
|
# "v": 1.1138,
|
|
# "vw": 22883.0155324116
|
|
# }
|
|
# }
|
|
# }
|
|
#
|
|
bars = self.safe_dict(response, 'bars', {})
|
|
ohlcvs = self.safe_dict(bars, marketId, {})
|
|
ohlcvs = [ohlcvs]
|
|
else:
|
|
raise NotSupported(self.id + ' fetchOHLCV() does not support ' + method + ', marketPublicGetV1beta3CryptoLocBars and marketPublicGetV1beta3CryptoLocLatestBars are supported')
|
|
return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)
|
|
|
|
def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
|
|
#
|
|
# {
|
|
# "c":22895,
|
|
# "h":22895,
|
|
# "l":22884,
|
|
# "n":6,
|
|
# "o":22884,
|
|
# "t":"2022-07-21T05:01:00Z",
|
|
# "v":0.001,
|
|
# "vw":22889.5
|
|
# }
|
|
#
|
|
datetime = self.safe_string(ohlcv, 't')
|
|
timestamp = self.parse8601(datetime)
|
|
return [
|
|
timestamp, # timestamp
|
|
self.safe_number(ohlcv, 'o'), # open
|
|
self.safe_number(ohlcv, 'h'), # high
|
|
self.safe_number(ohlcv, 'l'), # low
|
|
self.safe_number(ohlcv, 'c'), # close
|
|
self.safe_number(ohlcv, 'v'), # volume
|
|
]
|
|
|
|
async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
|
|
"""
|
|
fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
|
|
|
|
https://docs.alpaca.markets/reference/cryptosnapshots-1
|
|
|
|
:param str symbol: unified symbol of the market to fetch the ticker for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.loc]: crypto location, default: us
|
|
:returns dict: a `ticker structure <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
symbol = self.symbol(symbol)
|
|
tickers = await self.fetch_tickers([symbol], params)
|
|
return self.safe_dict(tickers, symbol)
|
|
|
|
async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
|
|
"""
|
|
fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
|
|
|
|
https://docs.alpaca.markets/reference/cryptosnapshots-1
|
|
|
|
:param str[] symbols: unified symbols of the markets to fetch tickers for
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.loc]: crypto location, default: us
|
|
:returns dict: a dictionary of `ticker structures <https://docs.ccxt.com/#/?id=ticker-structure>`
|
|
"""
|
|
if symbols is None:
|
|
raise ArgumentsRequired(self.id + ' fetchTickers() requires a symbols argument')
|
|
await self.load_markets()
|
|
symbols = self.market_symbols(symbols)
|
|
loc = self.safe_string(params, 'loc', 'us')
|
|
ids = self.market_ids(symbols)
|
|
request = {
|
|
'symbols': ','.join(ids),
|
|
'loc': loc,
|
|
}
|
|
params = self.omit(params, 'loc')
|
|
response = await self.marketPublicGetV1beta3CryptoLocSnapshots(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "snapshots": {
|
|
# "BTC/USD": {
|
|
# "dailyBar": {
|
|
# "c": 69403.554,
|
|
# "h": 69609.6515,
|
|
# "l": 69013.26,
|
|
# "n": 9,
|
|
# "o": 69536.7,
|
|
# "t": "2024-11-01T05:00:00Z",
|
|
# "v": 0.210809181,
|
|
# "vw": 69327.655393908
|
|
# },
|
|
# "latestQuote": {
|
|
# "ap": 69424.19,
|
|
# "as": 0.68149,
|
|
# "bp": 69366.086,
|
|
# "bs": 0.68312,
|
|
# "t": "2024-11-01T08:31:41.880246926Z"
|
|
# },
|
|
# "latestTrade": {
|
|
# "i": 5272941104897543146,
|
|
# "p": 69416.9,
|
|
# "s": 0.014017324,
|
|
# "t": "2024-11-01T08:14:28.245088803Z",
|
|
# "tks": "B"
|
|
# },
|
|
# "minuteBar": {
|
|
# "c": 69403.554,
|
|
# "h": 69403.554,
|
|
# "l": 69399.125,
|
|
# "n": 0,
|
|
# "o": 69399.125,
|
|
# "t": "2024-11-01T08:30:00Z",
|
|
# "v": 0,
|
|
# "vw": 0
|
|
# },
|
|
# "prevDailyBar": {
|
|
# "c": 69515.1415,
|
|
# "h": 72668.837,
|
|
# "l": 68796.85,
|
|
# "n": 129,
|
|
# "o": 72258.9,
|
|
# "t": "2024-10-31T05:00:00Z",
|
|
# "v": 2.217683307,
|
|
# "vw": 70782.6811608144
|
|
# }
|
|
# },
|
|
# }
|
|
# }
|
|
#
|
|
results = []
|
|
snapshots = self.safe_dict(response, 'snapshots', {})
|
|
marketIds = list(snapshots.keys())
|
|
for i in range(0, len(marketIds)):
|
|
marketId = marketIds[i]
|
|
market = self.safe_market(marketId)
|
|
entry = self.safe_dict(snapshots, marketId)
|
|
dailyBar = self.safe_dict(entry, 'dailyBar', {})
|
|
prevDailyBar = self.safe_dict(entry, 'prevDailyBar', {})
|
|
latestQuote = self.safe_dict(entry, 'latestQuote', {})
|
|
latestTrade = self.safe_dict(entry, 'latestTrade', {})
|
|
datetime = self.safe_string(latestQuote, 't')
|
|
ticker = self.safe_ticker({
|
|
'info': entry,
|
|
'symbol': market['symbol'],
|
|
'timestamp': self.parse8601(datetime),
|
|
'datetime': datetime,
|
|
'high': self.safe_string(dailyBar, 'h'),
|
|
'low': self.safe_string(dailyBar, 'l'),
|
|
'bid': self.safe_string(latestQuote, 'bp'),
|
|
'bidVolume': self.safe_string(latestQuote, 'bs'),
|
|
'ask': self.safe_string(latestQuote, 'ap'),
|
|
'askVolume': self.safe_string(latestQuote, 'as'),
|
|
'vwap': self.safe_string(dailyBar, 'vw'),
|
|
'open': self.safe_string(dailyBar, 'o'),
|
|
'close': self.safe_string(dailyBar, 'c'),
|
|
'last': self.safe_string(latestTrade, 'p'),
|
|
'previousClose': self.safe_string(prevDailyBar, 'c'),
|
|
'change': None,
|
|
'percentage': None,
|
|
'average': None,
|
|
'baseVolume': self.safe_string(dailyBar, 'v'),
|
|
'quoteVolume': self.safe_string(dailyBar, 'n'),
|
|
}, market)
|
|
results.append(ticker)
|
|
return self.filter_by_array(results, 'symbol', symbols)
|
|
|
|
def generate_client_order_id(self, params):
|
|
clientOrderIdprefix = self.safe_string(self.options, 'clientOrderId')
|
|
uuid = self.uuid()
|
|
parts = uuid.split('-')
|
|
random_id = ''.join(parts)
|
|
defaultClientId = self.implode_params(clientOrderIdprefix, {'id': random_id})
|
|
clientOrderId = self.safe_string(params, 'clientOrderId', defaultClientId)
|
|
return clientOrderId
|
|
|
|
async def create_market_order_with_cost(self, symbol: str, side: OrderSide, cost: float, params={}):
|
|
"""
|
|
create a market order by providing the symbol, side and cost
|
|
|
|
https://docs.alpaca.markets/reference/postorder
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str side: 'buy' or 'sell'
|
|
:param float cost: how much you want to trade in units of the quote currency
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
req = {
|
|
'cost': cost,
|
|
}
|
|
return await self.create_order(symbol, 'market', side, 0, None, self.extend(req, params))
|
|
|
|
async def create_market_buy_order_with_cost(self, symbol: str, cost: float, params={}):
|
|
"""
|
|
create a market buy order by providing the symbol and cost
|
|
|
|
https://docs.alpaca.markets/reference/postorder
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param float cost: how much you want to trade in units of the quote currency
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
req = {
|
|
'cost': cost,
|
|
}
|
|
return await self.create_order(symbol, 'market', 'buy', 0, None, self.extend(req, params))
|
|
|
|
async def create_market_sell_order_with_cost(self, symbol: str, cost: float, params={}):
|
|
"""
|
|
create a market sell order by providing the symbol and cost
|
|
|
|
https://docs.alpaca.markets/reference/postorder
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param float cost: how much you want to trade in units of the quote currency
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
req = {
|
|
'cost': cost,
|
|
}
|
|
return await self.create_order(symbol, 'market', 'sell', cost, None, self.extend(req, params))
|
|
|
|
async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
|
|
"""
|
|
create a trade order
|
|
|
|
https://docs.alpaca.markets/reference/postorder
|
|
|
|
:param str symbol: unified symbol of the market to create an order in
|
|
:param str type: 'market', 'limit' or 'stop_limit'
|
|
:param str side: 'buy' or 'sell'
|
|
:param float amount: how much of currency you want to trade in units of base currency
|
|
:param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param float [params.triggerPrice]: The price at which a trigger order is triggered at
|
|
:param float [params.cost]: *market orders only* the cost of the order in units of the quote currency
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = self.market(symbol)
|
|
id = market['id']
|
|
request: dict = {
|
|
'symbol': id,
|
|
'side': side,
|
|
'type': type, # market, limit, stop_limit
|
|
}
|
|
triggerPrice = self.safe_string_n(params, ['triggerPrice', 'stop_price'])
|
|
if triggerPrice is not None:
|
|
newType: str
|
|
if type.find('limit') >= 0:
|
|
newType = 'stop_limit'
|
|
else:
|
|
raise NotSupported(self.id + ' createOrder() does not support stop orders for ' + type + ' orders, only stop_limit orders are supported')
|
|
request['stop_price'] = self.price_to_precision(symbol, triggerPrice)
|
|
request['type'] = newType
|
|
if type.find('limit') >= 0:
|
|
request['limit_price'] = self.price_to_precision(symbol, price)
|
|
cost = self.safe_string(params, 'cost')
|
|
if cost is not None:
|
|
params = self.omit(params, 'cost')
|
|
request['notional'] = self.cost_to_precision(symbol, cost)
|
|
else:
|
|
request['qty'] = self.amount_to_precision(symbol, amount)
|
|
defaultTIF = self.safe_string(self.options, 'defaultTimeInForce')
|
|
request['time_in_force'] = self.safe_string(params, 'timeInForce', defaultTIF)
|
|
params = self.omit(params, ['timeInForce', 'triggerPrice'])
|
|
request['client_order_id'] = self.generate_client_order_id(params)
|
|
params = self.omit(params, ['clientOrderId'])
|
|
order = await self.traderPrivatePostV2Orders(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": "61e69015-8549-4bfd-b9c3-01e75843f47d",
|
|
# "client_order_id": "eb9e2aaa-f71a-4f51-b5b4-52a6c565dad4",
|
|
# "created_at": "2021-03-16T18:38:01.942282Z",
|
|
# "updated_at": "2021-03-16T18:38:01.942282Z",
|
|
# "submitted_at": "2021-03-16T18:38:01.937734Z",
|
|
# "filled_at": null,
|
|
# "expired_at": null,
|
|
# "canceled_at": null,
|
|
# "failed_at": null,
|
|
# "replaced_at": null,
|
|
# "replaced_by": null,
|
|
# "replaces": null,
|
|
# "asset_id": "b0b6dd9d-8b9b-48a9-ba46-b9d54906e415",
|
|
# "symbol": "AAPL",
|
|
# "asset_class": "us_equity",
|
|
# "notional": "500",
|
|
# "qty": null,
|
|
# "filled_qty": "0",
|
|
# "filled_avg_price": null,
|
|
# "order_class": "",
|
|
# "order_type": "market",
|
|
# "type": "market",
|
|
# "side": "buy",
|
|
# "time_in_force": "day",
|
|
# "limit_price": null,
|
|
# "stop_price": null,
|
|
# "status": "accepted",
|
|
# "extended_hours": False,
|
|
# "legs": null,
|
|
# "trail_percent": null,
|
|
# "trail_price": null,
|
|
# "hwm": null
|
|
# }
|
|
#
|
|
return self.parse_order(order, market)
|
|
|
|
async def cancel_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
cancels an open order
|
|
|
|
https://docs.alpaca.markets/reference/deleteorderbyorderid
|
|
|
|
:param str id: order id
|
|
:param str symbol: unified symbol of the market the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {
|
|
'order_id': id,
|
|
}
|
|
response = await self.traderPrivateDeleteV2OrdersOrderId(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "code": 40410000,
|
|
# "message": "order is not found."
|
|
# }
|
|
#
|
|
return self.parse_order(response)
|
|
|
|
async def cancel_all_orders(self, symbol: Str = None, params={}):
|
|
"""
|
|
cancel all open orders in a market
|
|
|
|
https://docs.alpaca.markets/reference/deleteallorders
|
|
|
|
:param str symbol: alpaca cancelAllOrders cannot setting symbol, it will cancel all open orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.traderPrivateDeleteV2Orders(params)
|
|
if isinstance(response, list):
|
|
return self.parse_orders(response, None)
|
|
else:
|
|
return [
|
|
self.safe_order({
|
|
'info': response,
|
|
}),
|
|
]
|
|
|
|
async def fetch_order(self, id: str, symbol: Str = None, params={}):
|
|
"""
|
|
fetches information on an order made by the user
|
|
|
|
https://docs.alpaca.markets/reference/getorderbyorderid
|
|
|
|
:param str id: the order id
|
|
:param str symbol: unified symbol of the market the order was made in
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: An `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'order_id': id,
|
|
}
|
|
order = await self.traderPrivateGetV2OrdersOrderId(self.extend(request, params))
|
|
marketId = self.safe_string(order, 'symbol')
|
|
market = self.safe_market(marketId)
|
|
return self.parse_order(order, market)
|
|
|
|
async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple orders made by the user
|
|
|
|
https://docs.alpaca.markets/reference/getallorders
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch orders for
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'status': 'all',
|
|
}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
request['symbols'] = market['id']
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
params = self.omit(params, 'until')
|
|
request['endTime'] = self.iso8601(until)
|
|
if since is not None:
|
|
request['after'] = self.iso8601(since)
|
|
if limit is not None:
|
|
request['limit'] = limit
|
|
response = await self.traderPrivateGetV2Orders(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id": "cbaf12d7-69b8-49c0-a31b-b46af35c755c",
|
|
# "client_order_id": "ccxt_b36156ae6fd44d098ac9c179bab33efd",
|
|
# "created_at": "2023-11-17T04:21:42.234579Z",
|
|
# "updated_at": "2023-11-17T04:22:34.442765Z",
|
|
# "submitted_at": "2023-11-17T04:21:42.233357Z",
|
|
# "filled_at": null,
|
|
# "expired_at": null,
|
|
# "canceled_at": "2023-11-17T04:22:34.399019Z",
|
|
# "failed_at": null,
|
|
# "replaced_at": null,
|
|
# "replaced_by": null,
|
|
# "replaces": null,
|
|
# "asset_id": "77c6f47f-0939-4b23-b41e-47b4469c4bc8",
|
|
# "symbol": "LTC/USDT",
|
|
# "asset_class": "crypto",
|
|
# "notional": null,
|
|
# "qty": "0.001",
|
|
# "filled_qty": "0",
|
|
# "filled_avg_price": null,
|
|
# "order_class": "",
|
|
# "order_type": "limit",
|
|
# "type": "limit",
|
|
# "side": "sell",
|
|
# "time_in_force": "gtc",
|
|
# "limit_price": "1000",
|
|
# "stop_price": null,
|
|
# "status": "canceled",
|
|
# "extended_hours": False,
|
|
# "legs": null,
|
|
# "trail_percent": null,
|
|
# "trail_price": null,
|
|
# "hwm": null,
|
|
# "subtag": null,
|
|
# "source": "access_key"
|
|
# }
|
|
# ]
|
|
#
|
|
return self.parse_orders(response, market, since, limit)
|
|
|
|
async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetch all unfilled currently open orders
|
|
|
|
https://docs.alpaca.markets/reference/getallorders
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch orders for
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {
|
|
'status': 'open',
|
|
}
|
|
return await self.fetch_orders(symbol, since, limit, self.extend(request, params))
|
|
|
|
async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
|
|
"""
|
|
fetches information on multiple closed orders made by the user
|
|
|
|
https://docs.alpaca.markets/reference/getallorders
|
|
|
|
:param str symbol: unified market symbol of the market orders were made in
|
|
:param int [since]: the earliest time in ms to fetch orders for
|
|
:param int [limit]: the maximum number of order structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch orders for
|
|
:returns Order[]: a list of `order structures <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
request: dict = {
|
|
'status': 'closed',
|
|
}
|
|
return await self.fetch_orders(symbol, since, limit, self.extend(request, params))
|
|
|
|
async def edit_order(self, id: str, symbol: str, type: OrderType, side: OrderSide, amount: Num = None, price: Num = None, params={}):
|
|
"""
|
|
edit a trade order
|
|
|
|
https://docs.alpaca.markets/reference/patchorderbyorderid-1
|
|
|
|
:param str id: order id
|
|
:param str [symbol]: unified symbol of the market to create an order in
|
|
:param str [type]: 'market', 'limit' or 'stop_limit'
|
|
:param str [side]: 'buy' or 'sell'
|
|
:param float [amount]: how much of the currency you want to trade in units of the base currency
|
|
:param float [price]: the price for the order, in units of the quote currency, ignored in market orders
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param str [params.triggerPrice]: the price to trigger a stop order
|
|
:param str [params.timeInForce]: for crypto trading either 'gtc' or 'ioc' can be used
|
|
:param str [params.clientOrderId]: a unique identifier for the order, automatically generated if not sent
|
|
:returns dict: an `order structure <https://docs.ccxt.com/#/?id=order-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
request: dict = {
|
|
'order_id': id,
|
|
}
|
|
market = None
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
if amount is not None:
|
|
request['qty'] = self.amount_to_precision(symbol, amount)
|
|
triggerPrice = self.safe_string_n(params, ['triggerPrice', 'stop_price'])
|
|
if triggerPrice is not None:
|
|
request['stop_price'] = self.price_to_precision(symbol, triggerPrice)
|
|
params = self.omit(params, 'triggerPrice')
|
|
if price is not None:
|
|
request['limit_price'] = self.price_to_precision(symbol, price)
|
|
timeInForce = None
|
|
timeInForce, params = self.handle_option_and_params_2(params, 'editOrder', 'timeInForce', 'defaultTimeInForce')
|
|
if timeInForce is not None:
|
|
request['time_in_force'] = timeInForce
|
|
request['client_order_id'] = self.generate_client_order_id(params)
|
|
params = self.omit(params, ['clientOrderId'])
|
|
response = await self.traderPrivatePatchV2OrdersOrderId(self.extend(request, params))
|
|
return self.parse_order(response, market)
|
|
|
|
def parse_order(self, order: dict, market: Market = None) -> Order:
|
|
#
|
|
# {
|
|
# "id":"6ecfcc34-4bed-4b53-83ba-c564aa832a81",
|
|
# "client_order_id":"ccxt_1c6ceab0b5e84727b2f1c0394ba17560",
|
|
# "created_at":"2022-06-14T13:59:30.224037068Z",
|
|
# "updated_at":"2022-06-14T13:59:30.224037068Z",
|
|
# "submitted_at":"2022-06-14T13:59:30.221856828Z",
|
|
# "filled_at":null,
|
|
# "expired_at":null,
|
|
# "canceled_at":null,
|
|
# "failed_at":null,
|
|
# "replaced_at":null,
|
|
# "replaced_by":null,
|
|
# "replaces":null,
|
|
# "asset_id":"64bbff51-59d6-4b3c-9351-13ad85e3c752",
|
|
# "symbol":"BTCUSD",
|
|
# "asset_class":"crypto",
|
|
# "notional":null,
|
|
# "qty":"0.01",
|
|
# "filled_qty":"0",
|
|
# "filled_avg_price":null,
|
|
# "order_class":"",
|
|
# "order_type":"limit",
|
|
# "type":"limit",
|
|
# "side":"buy",
|
|
# "time_in_force":"day",
|
|
# "limit_price":"14000",
|
|
# "stop_price":null,
|
|
# "status":"accepted",
|
|
# "extended_hours":false,
|
|
# "legs":null,
|
|
# "trail_percent":null,
|
|
# "trail_price":null,
|
|
# "hwm":null,
|
|
# "commission":"0.42",
|
|
# "source":null
|
|
# }
|
|
#
|
|
marketId = self.safe_string(order, 'symbol')
|
|
market = self.safe_market(marketId, market)
|
|
symbol = market['symbol']
|
|
alpacaStatus = self.safe_string(order, 'status')
|
|
status = self.parse_order_status(alpacaStatus)
|
|
feeValue = self.safe_string(order, 'commission')
|
|
fee = None
|
|
if feeValue is not None:
|
|
fee = {
|
|
'cost': feeValue,
|
|
'currency': 'USD',
|
|
}
|
|
orderType = self.safe_string(order, 'order_type')
|
|
if orderType is not None:
|
|
if orderType.find('limit') >= 0:
|
|
# might be limit or stop-limit
|
|
orderType = 'limit'
|
|
datetime = self.safe_string(order, 'submitted_at')
|
|
timestamp = self.parse8601(datetime)
|
|
return self.safe_order({
|
|
'id': self.safe_string(order, 'id'),
|
|
'clientOrderId': self.safe_string(order, 'client_order_id'),
|
|
'timestamp': timestamp,
|
|
'datetime': datetime,
|
|
'lastTradeTimeStamp': None,
|
|
'status': status,
|
|
'symbol': symbol,
|
|
'type': orderType,
|
|
'timeInForce': self.parse_time_in_force(self.safe_string(order, 'time_in_force')),
|
|
'postOnly': None,
|
|
'side': self.safe_string(order, 'side'),
|
|
'price': self.safe_number(order, 'limit_price'),
|
|
'triggerPrice': self.safe_number(order, 'stop_price'),
|
|
'cost': None,
|
|
'average': self.safe_number(order, 'filled_avg_price'),
|
|
'amount': self.safe_number(order, 'qty'),
|
|
'filled': self.safe_number(order, 'filled_qty'),
|
|
'remaining': None,
|
|
'trades': None,
|
|
'fee': fee,
|
|
'info': order,
|
|
}, market)
|
|
|
|
def parse_order_status(self, status: Str):
|
|
statuses: dict = {
|
|
'pending_new': 'open',
|
|
'accepted': 'open',
|
|
'new': 'open',
|
|
'partially_filled': 'open',
|
|
'activated': 'open',
|
|
'filled': 'closed',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_time_in_force(self, timeInForce: Str):
|
|
timeInForces: dict = {
|
|
'day': 'Day',
|
|
}
|
|
return self.safe_string(timeInForces, timeInForce, timeInForce)
|
|
|
|
async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
|
|
"""
|
|
fetch all trades made by the user
|
|
|
|
https://docs.alpaca.markets/reference/getaccountactivitiesbyactivitytype-1
|
|
|
|
:param str [symbol]: unified market symbol
|
|
:param int [since]: the earliest time in ms to fetch trades for
|
|
:param int [limit]: the maximum number of trade structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:param int [params.until]: the latest time in ms to fetch trades for
|
|
:param str [params.page_token]: page_token - used for paging
|
|
:returns Trade[]: a list of `trade structures <https://docs.ccxt.com/#/?id=trade-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
market = None
|
|
request: dict = {
|
|
'activity_type': 'FILL',
|
|
}
|
|
if symbol is not None:
|
|
market = self.market(symbol)
|
|
until = self.safe_integer(params, 'until')
|
|
if until is not None:
|
|
params = self.omit(params, 'until')
|
|
request['until'] = self.iso8601(until)
|
|
if since is not None:
|
|
request['after'] = self.iso8601(since)
|
|
if limit is not None:
|
|
request['page_size'] = limit
|
|
request, params = self.handle_until_option('until', request, params)
|
|
response = await self.traderPrivateGetV2AccountActivitiesActivityType(self.extend(request, params))
|
|
#
|
|
# [
|
|
# {
|
|
# "id": "20221228071929579::ca2aafd0-1270-4b56-b0a9-85423b4a07c8",
|
|
# "activity_type": "FILL",
|
|
# "transaction_time": "2022-12-28T12:19:29.579352Z",
|
|
# "type": "fill",
|
|
# "price": "67.31",
|
|
# "qty": "0.07",
|
|
# "side": "sell",
|
|
# "symbol": "LTC/USD",
|
|
# "leaves_qty": "0",
|
|
# "order_id": "82eebcf7-6e66-4b7e-93f8-be0df0e4f12e",
|
|
# "cum_qty": "0.07",
|
|
# "order_status": "filled",
|
|
# "swap_rate": "1"
|
|
# },
|
|
# ]
|
|
#
|
|
return self.parse_trades(response, market, since, limit)
|
|
|
|
def parse_trade(self, trade: dict, market: Market = None) -> Trade:
|
|
#
|
|
# fetchTrades
|
|
#
|
|
# {
|
|
# "t":"2022-06-14T05:00:00.027869Z",
|
|
# "x":"CBSE",
|
|
# "p":"21942.15",
|
|
# "s":"0.0001",
|
|
# "tks":"S",
|
|
# "i":"355681339"
|
|
# }
|
|
#
|
|
# fetchMyTrades
|
|
#
|
|
# {
|
|
# "id": "20221228071929579::ca2aafd0-1270-4b56-b0a9-85423b4a07c8",
|
|
# "activity_type": "FILL",
|
|
# "transaction_time": "2022-12-28T12:19:29.579352Z",
|
|
# "type": "fill",
|
|
# "price": "67.31",
|
|
# "qty": "0.07",
|
|
# "side": "sell",
|
|
# "symbol": "LTC/USD",
|
|
# "leaves_qty": "0",
|
|
# "order_id": "82eebcf7-6e66-4b7e-93f8-be0df0e4f12e",
|
|
# "cum_qty": "0.07",
|
|
# "order_status": "filled",
|
|
# "swap_rate": "1"
|
|
# },
|
|
#
|
|
marketId = self.safe_string_2(trade, 'S', 'symbol')
|
|
symbol = self.safe_symbol(marketId, market)
|
|
datetime = self.safe_string_2(trade, 't', 'transaction_time')
|
|
timestamp = self.parse8601(datetime)
|
|
alpacaSide = self.safe_string(trade, 'tks')
|
|
side = self.safe_string(trade, 'side')
|
|
if alpacaSide == 'B':
|
|
side = 'buy'
|
|
elif alpacaSide == 'S':
|
|
side = 'sell'
|
|
priceString = self.safe_string_2(trade, 'p', 'price')
|
|
amountString = self.safe_string_2(trade, 's', 'qty')
|
|
return self.safe_trade({
|
|
'info': trade,
|
|
'id': self.safe_string_2(trade, 'i', 'id'),
|
|
'timestamp': timestamp,
|
|
'datetime': self.iso8601(timestamp),
|
|
'symbol': symbol,
|
|
'order': self.safe_string(trade, 'order_id'),
|
|
'type': None,
|
|
'side': side,
|
|
'takerOrMaker': 'taker',
|
|
'price': priceString,
|
|
'amount': amountString,
|
|
'cost': None,
|
|
'fee': None,
|
|
}, market)
|
|
|
|
async def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
|
|
"""
|
|
fetch the deposit address for a currency associated with self account
|
|
|
|
https://docs.alpaca.markets/reference/listcryptofundingwallets
|
|
|
|
:param str code: unified currency code
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: an `address structure <https://docs.ccxt.com/#/?id=address-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
}
|
|
response = await self.traderPrivateGetV2Wallets(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "asset_id": "4fa30c85-77b7-4cbc-92dd-7b7513640aad",
|
|
# "address": "bc1q2fpskfnwem3uq9z8660e4z6pfv7aqfamysk75r",
|
|
# "created_at": "2024-11-03T07:30:05.609976344Z"
|
|
# }
|
|
#
|
|
return self.parse_deposit_address(response, currency)
|
|
|
|
def parse_deposit_address(self, depositAddress, currency: Currency = None) -> DepositAddress:
|
|
#
|
|
# {
|
|
# "asset_id": "4fa30c85-77b7-4cbc-92dd-7b7513640aad",
|
|
# "address": "bc1q2fpskfnwem3uq9z8660e4z6pfv7aqfamysk75r",
|
|
# "created_at": "2024-11-03T07:30:05.609976344Z"
|
|
# }
|
|
#
|
|
parsedCurrency = None
|
|
if currency is not None:
|
|
parsedCurrency = currency['id']
|
|
return {
|
|
'info': depositAddress,
|
|
'currency': parsedCurrency,
|
|
'network': None,
|
|
'address': self.safe_string(depositAddress, 'address'),
|
|
'tag': None,
|
|
}
|
|
|
|
async def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
|
|
"""
|
|
make a withdrawal
|
|
|
|
https://docs.alpaca.markets/reference/createcryptotransferforaccount
|
|
|
|
:param str code: unified currency code
|
|
:param float amount: the amount to withdraw
|
|
:param str address: the address to withdraw to
|
|
:param str tag: a memo for the transaction
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
tag, params = self.handle_withdraw_tag_and_params(tag, params)
|
|
self.check_address(address)
|
|
await self.load_markets()
|
|
currency = self.currency(code)
|
|
if tag:
|
|
address = address + ':' + tag
|
|
request: dict = {
|
|
'asset': currency['id'],
|
|
'address': address,
|
|
'amount': self.number_to_string(amount),
|
|
}
|
|
response = await self.traderPrivatePostV2WalletsTransfers(self.extend(request, params))
|
|
#
|
|
# {
|
|
# "id": "e27b70a6-5610-40d7-8468-a516a284b776",
|
|
# "tx_hash": null,
|
|
# "direction": "OUTGOING",
|
|
# "amount": "20",
|
|
# "usd_value": "19.99856",
|
|
# "chain": "ETH",
|
|
# "asset": "USDT",
|
|
# "from_address": "0x123930E4dCA196E070d39B60c644C8Aae02f23",
|
|
# "to_address": "0x1232c0925196e4dcf05945f67f690153190fbaab",
|
|
# "status": "PROCESSING",
|
|
# "created_at": "2024-11-07T02:39:01.775495Z",
|
|
# "network_fee": "4",
|
|
# "fees": "0.1"
|
|
# }
|
|
#
|
|
return self.parse_transaction(response, currency)
|
|
|
|
async def fetch_transactions_helper(self, type, code, since, limit, params):
|
|
await self.load_markets()
|
|
currency = None
|
|
if code is not None:
|
|
currency = self.currency(code)
|
|
response = await self.traderPrivateGetV2WalletsTransfers(params)
|
|
#
|
|
# {
|
|
# "id": "e27b70a6-5610-40d7-8468-a516a284b776",
|
|
# "tx_hash": null,
|
|
# "direction": "OUTGOING",
|
|
# "amount": "20",
|
|
# "usd_value": "19.99856",
|
|
# "chain": "ETH",
|
|
# "asset": "USDT",
|
|
# "from_address": "0x123930E4dCA196E070d39B60c644C8Aae02f23",
|
|
# "to_address": "0x1232c0925196e4dcf05945f67f690153190fbaab",
|
|
# "status": "PROCESSING",
|
|
# "created_at": "2024-11-07T02:39:01.775495Z",
|
|
# "network_fee": "4",
|
|
# "fees": "0.1"
|
|
# }
|
|
#
|
|
results = []
|
|
for i in range(0, len(response)):
|
|
entry = response[i]
|
|
direction = self.safe_string(entry, 'direction')
|
|
if direction == type:
|
|
results.append(entry)
|
|
elif type == 'BOTH':
|
|
results.append(entry)
|
|
return self.parse_transactions(results, currency, since, limit, params)
|
|
|
|
async def fetch_deposits_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch history of deposits and withdrawals
|
|
|
|
https://docs.alpaca.markets/reference/listcryptofundingtransfers
|
|
|
|
:param str [code]: unified currency code for the currency of the deposit/withdrawals, default is None
|
|
:param int [since]: timestamp in ms of the earliest deposit/withdrawal, default is None
|
|
:param int [limit]: max number of deposit/withdrawals to return, default is None
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a list of `transaction structure <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
return await self.fetch_transactions_helper('BOTH', code, since, limit, params)
|
|
|
|
async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all deposits made to an account
|
|
|
|
https://docs.alpaca.markets/reference/listcryptofundingtransfers
|
|
|
|
:param str [code]: unified currency code
|
|
:param int [since]: the earliest time in ms to fetch deposits for
|
|
:param int [limit]: the maximum number of deposit structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
return await self.fetch_transactions_helper('INCOMING', code, since, limit, params)
|
|
|
|
async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
|
|
"""
|
|
fetch all withdrawals made from an account
|
|
|
|
https://docs.alpaca.markets/reference/listcryptofundingtransfers
|
|
|
|
:param str [code]: unified currency code
|
|
:param int [since]: the earliest time in ms to fetch withdrawals for
|
|
:param int [limit]: the maximum number of withdrawal structures to retrieve
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict[]: a list of `transaction structures <https://docs.ccxt.com/#/?id=transaction-structure>`
|
|
"""
|
|
return await self.fetch_transactions_helper('OUTGOING', code, since, limit, params)
|
|
|
|
def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
|
|
#
|
|
# {
|
|
# "id": "e27b70a6-5610-40d7-8468-a516a284b776",
|
|
# "tx_hash": null,
|
|
# "direction": "OUTGOING",
|
|
# "amount": "20",
|
|
# "usd_value": "19.99856",
|
|
# "chain": "ETH",
|
|
# "asset": "USDT",
|
|
# "from_address": "0x123930E4dCA196E070d39B60c644C8Aae02f23",
|
|
# "to_address": "0x1232c0925196e4dcf05945f67f690153190fbaab",
|
|
# "status": "PROCESSING",
|
|
# "created_at": "2024-11-07T02:39:01.775495Z",
|
|
# "network_fee": "4",
|
|
# "fees": "0.1"
|
|
# }
|
|
#
|
|
datetime = self.safe_string(transaction, 'created_at')
|
|
currencyId = self.safe_string(transaction, 'asset')
|
|
code = self.safe_currency_code(currencyId, currency)
|
|
fees = self.safe_string(transaction, 'fees')
|
|
networkFee = self.safe_string(transaction, 'network_fee')
|
|
totalFee = Precise.string_add(fees, networkFee)
|
|
fee = {
|
|
'cost': self.parse_number(totalFee),
|
|
'currency': code,
|
|
}
|
|
return {
|
|
'info': transaction,
|
|
'id': self.safe_string(transaction, 'id'),
|
|
'txid': self.safe_string(transaction, 'tx_hash'),
|
|
'timestamp': self.parse8601(datetime),
|
|
'datetime': datetime,
|
|
'network': self.safe_string(transaction, 'chain'),
|
|
'address': self.safe_string(transaction, 'to_address'),
|
|
'addressTo': self.safe_string(transaction, 'to_address'),
|
|
'addressFrom': self.safe_string(transaction, 'from_address'),
|
|
'tag': None,
|
|
'tagTo': None,
|
|
'tagFrom': None,
|
|
'type': self.parse_transaction_type(self.safe_string(transaction, 'direction')),
|
|
'amount': self.safe_number(transaction, 'amount'),
|
|
'currency': code,
|
|
'status': self.parse_transaction_status(self.safe_string(transaction, 'status')),
|
|
'updated': None,
|
|
'fee': fee,
|
|
'comment': None,
|
|
'internal': None,
|
|
}
|
|
|
|
def parse_transaction_status(self, status: Str):
|
|
statuses: dict = {
|
|
'PROCESSING': 'pending',
|
|
'FAILED': 'failed',
|
|
'COMPLETE': 'ok',
|
|
}
|
|
return self.safe_string(statuses, status, status)
|
|
|
|
def parse_transaction_type(self, type):
|
|
types: dict = {
|
|
'INCOMING': 'deposit',
|
|
'OUTGOING': 'withdrawal',
|
|
}
|
|
return self.safe_string(types, type, type)
|
|
|
|
async def fetch_balance(self, params={}) -> Balances:
|
|
"""
|
|
query for balance and get the amount of funds available for trading or funds locked in orders
|
|
|
|
https://docs.alpaca.markets/reference/getaccount-1
|
|
|
|
:param dict [params]: extra parameters specific to the exchange API endpoint
|
|
:returns dict: a `balance structure <https://docs.ccxt.com/#/?id=balance-structure>`
|
|
"""
|
|
await self.load_markets()
|
|
response = await self.traderPrivateGetV2Account(params)
|
|
#
|
|
# {
|
|
# "id": "43a01bde-4eb1-64fssc26adb5",
|
|
# "admin_configurations": {
|
|
# "allow_instant_ach": True,
|
|
# "max_margin_multiplier": "4"
|
|
# },
|
|
# "user_configurations": {
|
|
# "fractional_trading": True,
|
|
# "max_margin_multiplier": "4"
|
|
# },
|
|
# "account_number": "744873727",
|
|
# "status": "ACTIVE",
|
|
# "crypto_status": "ACTIVE",
|
|
# "currency": "USD",
|
|
# "buying_power": "5.92",
|
|
# "regt_buying_power": "5.92",
|
|
# "daytrading_buying_power": "0",
|
|
# "effective_buying_power": "5.92",
|
|
# "non_marginable_buying_power": "5.92",
|
|
# "bod_dtbp": "0",
|
|
# "cash": "5.92",
|
|
# "accrued_fees": "0",
|
|
# "portfolio_value": "48.6",
|
|
# "pattern_day_trader": False,
|
|
# "trading_blocked": False,
|
|
# "transfers_blocked": False,
|
|
# "account_blocked": False,
|
|
# "created_at": "2022-06-13T14:59:18.318096Z",
|
|
# "trade_suspended_by_user": False,
|
|
# "multiplier": "1",
|
|
# "shorting_enabled": False,
|
|
# "equity": "48.6",
|
|
# "last_equity": "48.8014266",
|
|
# "long_market_value": "42.68",
|
|
# "short_market_value": "0",
|
|
# "position_market_value": "42.68",
|
|
# "initial_margin": "0",
|
|
# "maintenance_margin": "0",
|
|
# "last_maintenance_margin": "0",
|
|
# "sma": "5.92",
|
|
# "daytrade_count": 0,
|
|
# "balance_asof": "2024-12-10",
|
|
# "crypto_tier": 1,
|
|
# "intraday_adjustments": "0",
|
|
# "pending_reg_taf_fees": "0"
|
|
# }
|
|
#
|
|
return self.parse_balance(response)
|
|
|
|
def parse_balance(self, response) -> Balances:
|
|
result: dict = {'info': response}
|
|
account = self.account()
|
|
currencyId = self.safe_string(response, 'currency')
|
|
code = self.safe_currency_code(currencyId)
|
|
account['free'] = self.safe_string(response, 'cash')
|
|
account['total'] = self.safe_string(response, 'equity')
|
|
result[code] = account
|
|
return self.safe_balance(result)
|
|
|
|
def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
|
|
endpoint = '/' + self.implode_params(path, params)
|
|
url = self.implode_hostname(self.urls['api'][api[0]])
|
|
headers = headers if (headers is not None) else {}
|
|
if api[1] == 'private':
|
|
self.check_required_credentials()
|
|
headers['APCA-API-KEY-ID'] = self.apiKey
|
|
headers['APCA-API-SECRET-KEY'] = self.secret
|
|
query = self.omit(params, self.extract_params(path))
|
|
if query:
|
|
if (method == 'GET') or (method == 'DELETE'):
|
|
endpoint += '?' + self.urlencode(query)
|
|
else:
|
|
body = self.json(query)
|
|
headers['Content-Type'] = 'application/json'
|
|
url = url + endpoint
|
|
return {'url': url, 'method': method, 'body': body, 'headers': headers}
|
|
|
|
def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
|
|
if response is None:
|
|
return None # default error handler
|
|
# {
|
|
# "code": 40110000,
|
|
# "message": "request is not authorized"
|
|
# }
|
|
feedback = self.id + ' ' + body
|
|
errorCode = self.safe_string(response, 'code')
|
|
if code is not None:
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
|
|
message = self.safe_value(response, 'message', None)
|
|
if message is not None:
|
|
self.throw_exactly_matched_exception(self.exceptions['exact'], message, feedback)
|
|
self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
|
|
raise ExchangeError(feedback)
|
|
return None
|