Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Implemented ActiveParticipant for MtGox. #16

Merged
merged 2 commits into from

4 participants

@StevenLooman

Implemented ActiveParticipant for MtGox.

I have touched some of the concept-classes. Please let me know if you have any comments and/or questions.

@StevenLooman

This is also related to #6.

@StevenLooman

Market has placeBidOrder/placeAskOrder to simplify the users' code. Now the user can simply do:

order =  market.placeBidOrder(5.0, 15.0)

Otherwise, the user has to create an Order first, with several tedious parameters such as the timestamp, or even parameters the user might now know the value of yet:

amount = Amount(5.0, BTC)
price = ExchangeRate(USD, BTC, 15.0)
timestamp = datetime.now()
order = Order(market, timestamp, Order.SELL, amount, price, entity=market)
updated_order = market.placeOrder(order)

This involves several more lines, and is also more error prone. Also, the user now has two orders: 1) the conceptual Order and 2) the MtGox specific order.

Feel free to move things around.

You are right about the Market.__private. It should be 'hidden.' I'll fix that soon.

repr is a repeating thing yes. Again, feel free to change things. You probably want to do something with dict, such as (not sure if this works):

return "<{0}({1})>".format(self.__class__.__name__, ", ".join(self.__dict__.values())

Or maybe add the keys of the dict as well.

Yes, good idea, we can Order.is_buy_order/is_sell_order. I'll commit this in a few minutes.

@StevenLooman

Changed Order/is_buy_order/.is_sell_order.

@goncalopp
Owner

I didn't notice you were passing integers to placeOrder. Suddenly this makes more and less sense :)
The problem you mention is very, the user cannot be expected to do all that just to make a simple order.
OTOH, having such a simplified interface is making the assumption that those two values are all the data a market wants.

Let's say a new market accepts Orders into the future (it places the order only after some user-specified time). If Order was used, the user could just set the timestamp and be done. Even if the market supports more advanced order placing features, it's free to expose the in its Order subclass, which the user is free to use (or not at all, if he has no need for them). With this kind of interface (integers), it's impossible to expose such functionality.

So, is it possible to solve both the lazy-user and the rigid-interface problems?
What immediately comes to mind is using placeOrder( Order ) at this level and defining some kind of SimpleMarket, and let the user choose between being lazy and having advanced features. Most of pythons stdlib feels this way to me (socket, logging, urllib...)

I'll have to think a bit more about this, but I'll merge the request meanwhile

@StevenLooman

Maybe we should add an additional parameter which accepts a dict, for market specific implementations. For example, using:

def placeBidOrder(self, amount, price, **args):
    ...

We can give additional parameters which, for example, the MtGox implementation can use. MtGox has an additional attribute called 'parameters' (from the top of my head) where you can specify an order is a limit or market order.

This solves the need for additional data, but is probably (also, compared to a placeOrder method) not very user friendly. Perhaps we can make it more user friendly by letting the user know - through API docs, or an additional constant at the MtGoxActiveParticipant, or both - what additional arguments can be given.

An additional contant at MtGoxActiveParticipant makes it possible for client-applications of the library/egg to read it and show additional options on the screen if needed.

@goncalopp goncalopp merged commit 37e15c4 into goncalopp:master
@goncalopp
Owner

The merge is done. Sorry about the delay

As you mentioned, that solution solves the order customization. About the user knowing which arguments are available, there's nothing preventing, for example, MtgoxActiveParticipant of overriding placeBidOrder with default arguments:

def placeBidOrder( self, amount, price, is_limit_order=False ):

This still doesn't minimize the interface, though.

And another smallish problem: if, for any reason, a new mandatory parameter must be included on the base placeBidOrder or placeAskOrder, the API must be changed; any existing code using the API is now broken, and users are left wondering on the

TypeError: placeBidOrder() takes exactly N arguments (N-1 given)

If the parameters go on the order, there's no need to change the API (just throw an exception if there's not enough data, explaining what needs be included)

For now, I'll change this to placeOrder at this level. I'll be implementing some SimpleMarket, and see where it leads... Maybe it turns out to be a bad idea

@StevenLooman

We can change MtGoxMarket.placeBidOrder to:

    def placeBidOrder( self, amount, price, **args):

Then, MtGoxMarket, for example, can check if args (dict) contains an entry with key 'order_type'. If not, it can raise an exception with a clear message that parameter 'order_type' is missing. ('order_type' is just an example.)

You can move it to Order, but that is just moving the same problem somewhere else. If you would place it on MtGoxOrder, the user still needs to understand that additional parameters are required.

Experimenting with it will give move insight. Be sure to do so.

What about the Bitstamp implementation? I can create an initial version for this using the updated concepts. I'm sure wel'll come across different different issues. Please let me know if you want me to do this.

@StevenLooman StevenLooman deleted the unknown repository branch
@goncalopp
Owner

I thought I had replied to this already, sorry. I did definitely read it...
main discussion on #13

You can move it to Order, but that is just moving the same problem somewhere else. If you would place it on MtGoxOrder, the user still needs to understand that additional parameters are required.

It's moving the problem somewhere else, yes, but it becomes a very different one. The market API needs be implemented by the exchanges; any change to it means changing ALL of them. Further, having that many options on placeOrder is a great way to turn ParticularExchange.placeOrder's implementation into a monolithic mess, since it can't easily pass the order to internal methods (there's so many arguments!).

If, OTOH, the optional arguments are passed on a single dict, it would be even less explicit than passing them as arguments on Order.

What about the Bitstamp implementation? I can create an initial version for this using the updated concepts. I'm sure wel'll come across different different issues. Please let me know if you want me to do this.

I think it's a bit early for that, since a lot of different ideas about the interface are still surfacing - but you're free to try, of course.

@zuijlen

Hi goncalopp,

...you're welcome to join the discussion.

Thank you.

It would be great to have a authoritative reference here...

The wiki link stated in my previous post. I bet each exchange has a different interface. Maybe the big FOREX interfaces/libraries can be used as reference ( http://en.wikipedia.org/wiki/Foreign_exchange_market ).
After some googling Oanda has some programs to interact with their api: http://fxtrade.oanda.com/trade-forex/api/
Dukascopy also has some api's:
Market order: http://www.dukascopy.com/wiki/#Set_Market_Order
Set Stop Loss Price: http://www.dukascopy.com/wiki/#Set_Stop_Loss_price
I have only limited knowledge of trading api's, and programming language, but I hope this (and probably some more references) should gives some more context.

This is a lot of new info; we'll have to consider everything you said carefully while designing the interface.

Thank you, this is my intention. To become a very good and widely used (multi currency) interface the library/interface should be able to adopt/extended with mentioned order variables in the future.
In the future.

I noticed some questions/discussion about the level of knowledge someone must have to be able to use the interface. If this concerns a 'must have' variable I would like to ask you to add the variable and set a default. For example: order type deafault = limit order (due to all interfaces require to add quantity and exchange price). And for the 'Time in force' default GTC.
This way a unfamiliar user gets the most safe, or most common, settings (they probably even expect it) and a professional user/exchange can select(/offer) a specific setting and do not turn down the interface because of some limitations.

I already post this in the other discussion (#13) and also here because #13 is already closed.

@agravier

I think you meant ... self.high, self.low, self.last, ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 28, 2013
  1. @StevenLooman
Commits on Jan 29, 2013
  1. @StevenLooman
This page is out of date. Refresh to see the latest.
View
7 mexbtcapi/api/mtgox/__init__.py
@@ -1,4 +1,5 @@
-from mexbtcapi.api.mtgox.http_v1.high_level import Market
+from mexbtcapi.api.mtgox.http_v1.high_level import MtGoxMarket, MtGoxParticipant
-name = Market.MARKET_NAME
-market = Market
+name = MtGoxMarket.MARKET_NAME
+market = MtGoxMarket
+participant = MtGoxParticipant
View
109 mexbtcapi/api/mtgox/http_v1/high_level.py
@@ -1,19 +1,43 @@
from datetime import datetime, timedelta
from decimal import Decimal
from functools import partial
+import logging
from mexbtcapi import concepts
from mexbtcapi.concepts.currencies import BTC
-from mexbtcapi.concepts.currency import Amount, ExchangeRate
-from mexbtcapi.concepts.market import Market as BaseMarket, Order, Trade
+from mexbtcapi.concepts.currency import Amount, Currency, ExchangeRate
+from mexbtcapi.concepts.market import ActiveParticipant, Market as BaseMarket, Order, Trade
import mtgox as low_level
+logger = logging.getLogger(__name__)
+
+
class MtgoxTicker(concepts.market.Ticker):
TIME_PERIOD = timedelta(days=1)
+ def __repr__(self):
+ return \
+ "<MtgoxTicker({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8})" \
+ .format(self.market, self.time, self.high, self.high, self.last,
+ self.volume, self.average, self.buy, self.sell)
+
+
+class MtGoxOrder(Order):
+
+ def __init__(self, market, oid, timestamp, buy_or_sell, from_amount,
+ exchange_rate, properties="", entity=None):
+ super(MtGoxOrder, self).__init__(market, timestamp, buy_or_sell, from_amount, exchange_rate, properties, entity)
+ self.oid = oid
-class Market(BaseMarket):
+ def __repr__(self):
+ return \
+ "<MtGoxOrder({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}>" \
+ .format(self.market, self.timestamp, self.oid, self.buy_or_sell,
+ self.from_amount, self.exchange_rate, self.properties, self.entity)
+
+
+class MtGoxMarket(BaseMarket):
MARKET_NAME = "MtGox"
def __init__(self, currency):
@@ -28,6 +52,8 @@ def _multiplier(self, currency):
return self.multiplier[currency.name]
def getTicker(self):
+ logger.debug("getting ticker")
+
time = datetime.now()
data = low_level.ticker(self.currency1.name)
@@ -43,6 +69,8 @@ def getTicker(self):
return ticker
def getDepth(self):
+ logger.debug("getting depth")
+
low_level_depth = low_level.depth()
return {
@@ -66,6 +94,8 @@ def _depthToOrders(self, depth, order_type):
return orders
def getTrades(self):
+ logger.debug("getting trades")
+
low_level_trades = low_level.trades()
# convert tradres to array of Trades
@@ -86,3 +116,76 @@ def getTrades(self):
trades.append(t)
return trades
+
+
+class MtGoxParticipant(MtGoxMarket, ActiveParticipant):
+
+ def __init__(self, currency, key, secret):
+ MtGoxMarket.__init__(self, currency)
+
+ self.private = low_level.Private(key, secret)
+
+ def placeBidOrder(self, amount, price):
+ """places an Order in the market for price/amount"""
+
+ logger.debug("placing bid order")
+
+ oid = self.private.bid(amount.value, price.exchange_rate)
+
+ now = datetime.now()
+ return MtGoxOrder(self, oid, now, Order.BID, amount, price, entity=self)
+
+ def placeAskOrder(self, amount, price):
+ """places an Order in the market for price/amount"""
+
+ logger.debug("placing ask order")
+
+ oid = self.private.ask(amount, price)
+
+ now = datetime.now()
+ return MtGoxOrder(self, oid, now, Order.ASK, amount, price, entity=self)
+
+ def cancelOrder(self, order):
+ """Cancel an existing order"""
+ assert(isinstance(order, MtGoxOrder))
+
+ logger.debug("cancelling order {0}".format(order.oid))
+
+ oid = order.oid
+ if order.is_buy_order():
+ result = self.private.cancel_bid(oid)
+ else:
+ result = self.private.cancel_ask(oid)
+
+ if not result:
+ raise ActiveParticipant.ActiveParticipantError()
+
+ def getOpenOrders(self):
+ """Gets all the open orders"""
+
+ logger.debug("getting open orders")
+
+ low_level_orders = self.private.orders()
+ orders = []
+
+ for o in low_level_orders:
+ currency = Currency(o['currency'])
+ oid = o['oid']
+ timestamp = datetime.fromtimestamp(o['date'])
+ order_type = Order.BID if o['type'] else Order.ASK
+ amount = Amount(Decimal(o['amount']['value_int']) / self._multiplier(BTC), BTC)
+ price = ExchangeRate(currency, BTC, Decimal(o['price']['value_int']) / self._multiplier(currency))
+ order = MtGoxOrder(self, oid, timestamp, order_type, amount, price, entity=self)
+
+ # add additional status from MtGox
+ order.status = o['status']
+
+ orders.append(order)
+
+ return orders
+
+ def __str__(self):
+ return self.__repr__()
+
+ def __repr__(self):
+ return "<MtGoxParticipant({0})>".format(self.currency1)
View
8 mexbtcapi/api/mtgox/http_v1/mtgox.py
@@ -212,11 +212,11 @@ def info(self):
def orders(self):
"""Return standing orders"""
return self._generic('orders')
-
+
def wallet_history(self,type='',date_start='',date_end='',trade_id='',page='',currency=CURRENCY) :
url = "https://mtgox.com/api/1/generic/wallet/history"
data = {
- 'currency' : currency,
+ 'currency' : currency,
'type' : type,
'date_start' : date_start,
'date_end' : date_end,
@@ -252,7 +252,7 @@ def ask(self, amount, price, currency=CURRENCY):
an expanded format. For example, int(12300000)BTC
is interpreted as 1.23BTC.
"""
- self._order_add('ask', amount, price, currency)
+ return self._order_add('ask', amount, price, currency)
def bid(self, amount, price, currency=CURRENCY):
"""Buy bitcoins
@@ -261,7 +261,7 @@ def bid(self, amount, price, currency=CURRENCY):
an expanded format. For example, int(12300000)BTC
is interpreted as 1.23BTC.
"""
- self._order_add('bid', amount, price, currency)
+ return self._order_add('bid', amount, price, currency)
def _order_add(self, order_type, amount, price, currency):
if type(amount) in (Decimal, float):
View
44 mexbtcapi/concepts/market.py
@@ -43,16 +43,14 @@ class Order(object):
ASK = 'ASK'
def __init__(self, market, timestamp, buy_or_sell, from_amount,
- exchange_rate, properties="",
- from_entity=None, to_entity=None):
+ exchange_rate, properties="", entity=None):
assert isinstance(market, Market) # must not be null
assert isinstance(timestamp, datetime) # must not be null
assert buy_or_sell in [self.BID, self.ASK]
assert isinstance(from_amount, Amount)
assert isinstance(exchange_rate, ExchangeRate)
assert isinstance(properties, str)
- assert all([x is None or isinstance(x, Participant) for x
- in (from_entity, to_entity)])
+ assert entity is None or isinstance(entity, Participant)
self.market = market
self.timestamp = timestamp
@@ -60,14 +58,13 @@ def __init__(self, market, timestamp, buy_or_sell, from_amount,
self.from_amount = from_amount
self.exchange_rate = exchange_rate
self.properties = properties
- self.from_entity = from_entity
- self.to_entity = to_entity
+ self.entity = entity
def is_buy_order(self):
- return self.buy_or_sell == self.BUY
+ return self.buy_or_sell == self.BID
def is_sell_order(self):
- return self.buy_or_sell == self.SELL
+ return self.buy_or_sell != self.BID
def __str__(self):
return "{0} -> {1}".format(self.from_amount, self.exchange_rate)
@@ -127,18 +124,31 @@ class ActiveParticipant(Participant):
"""A participant under user control (may be the user itself)
"""
- def placeTrade(trade):
- """places a trade in the market"""
+ def placeBidOrder(self, price, amount):
+ """places an Order in the market for price/amount"""
raise NotImplementedError()
- def cancelTrade(trade):
+ def placeAskOrder(self, price, amount):
+ """places an Order in the market for price/amount"""
raise NotImplementedError()
- class TradeAlreadyClosedError(Exception):
- """Occurs when trying to cancel a already-closed trade"""
+ def cancelOrder(self, order):
+ """Cancel an existing order"""
+ raise NotImplementedError()
+
+ def getOpenOrders(self):
+ """Gets all the open orders"""
+ raise NotImplementedError()
+
+ class ActiveParitipantError(Exception):
+ """Base ActiveParticipant error"""
pass
- class NotAuthorizedError(Exception):
+ class OrderAlreadyClosedError(ActiveParitipantError):
+ """Occurs when trying to cancel a already-closed Order"""
+ pass
+
+ class NotAuthorizedError(ActiveParitipantError):
"""Occurs when the user is not authorized to do the requested operation
"""
pass
@@ -170,3 +180,9 @@ def __init__(self, market, time, high=None, low=None, average=None,
self.market, self.time, self.volume = market, time, volume
self.high, self.low, self.average, self.last, self.sell, self.buy = \
high, low, average, last, sell, buy
+
+ def __repr__(self):
+ return \
+ "<Ticker({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8})" \
+ .format(self.market, self.time, self.high, self.high, self.last,
+ self.volume, self.average, self.buy, self.sell)
Something went wrong with that request. Please try again.