Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Implemented ActiveParticipant for MtGox. #16

Merged
merged 2 commits into from over 1 year ago

4 participants

Steven Looman Goncalo Pinheira zuijlen Alexandre Gravier
Steven Looman

Implemented ActiveParticipant for MtGox.

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

Steven Looman

This is also related to #6.

Goncalo Pinheira
Owner

Thanks for the continuous hard work!

A couple of notes:

On market.py:

Given that an Order must have buy_or_sell set, why are there both Market.placeBidOrder and Market.placeAskOrder? Wouldn't a generic Market.placeOrder be enough and minimize the interface?

I think the code would look cleaner if the exception classes were the first items after the main class definition (before any class methods). Unless you have objections to that, I'll do it on the merge

On mtgox high_level:
Maybe MtGoxParticipant.private should be MtGoxParticipant.__private? Not that it provides any protection, but is it going to be used outside the class?

MtgoxTicker.repr: it seems wasteful to repeat everything on the subclass. I'll add a default argument to Ticker.repr with the class name, or maybe even use self.class.name, if that works

Owner

Oh, one more thing: I think we really ought to delete Order.is_sell_order and make the convention that a order is "SELL" if it is not BUY. Otherwise we'll be permanently writing this:

if order.is_sell_order():
pass
elif order.is_buy_order():
pass
else:
raise Exception()

That, or risk undefined behaviour...
Thoughts?

Steven Looman

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.

Steven Looman

Changed Order/is_buy_order/.is_sell_order.

Goncalo Pinheira
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

Steven Looman

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.

Goncalo Pinheira goncalopp merged commit 37e15c4 into from
Goncalo Pinheira
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

Steven Looman

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.

Steven Looman StevenLooman deleted the branch
Goncalo Pinheira
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.

Alexandre Gravier

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
This page is out of date. Refresh to see the latest.
7 mexbtcapi/api/mtgox/__init__.py
... ... @@ -1,4 +1,5 @@
1   -from mexbtcapi.api.mtgox.http_v1.high_level import Market
  1 +from mexbtcapi.api.mtgox.http_v1.high_level import MtGoxMarket, MtGoxParticipant
2 2
3   -name = Market.MARKET_NAME
4   -market = Market
  3 +name = MtGoxMarket.MARKET_NAME
  4 +market = MtGoxMarket
  5 +participant = MtGoxParticipant
109 mexbtcapi/api/mtgox/http_v1/high_level.py
... ... @@ -1,19 +1,43 @@
1 1 from datetime import datetime, timedelta
2 2 from decimal import Decimal
3 3 from functools import partial
  4 +import logging
4 5
5 6 from mexbtcapi import concepts
6 7 from mexbtcapi.concepts.currencies import BTC
7   -from mexbtcapi.concepts.currency import Amount, ExchangeRate
8   -from mexbtcapi.concepts.market import Market as BaseMarket, Order, Trade
  8 +from mexbtcapi.concepts.currency import Amount, Currency, ExchangeRate
  9 +from mexbtcapi.concepts.market import ActiveParticipant, Market as BaseMarket, Order, Trade
9 10 import mtgox as low_level
10 11
11 12
  13 +logger = logging.getLogger(__name__)
  14 +
  15 +
12 16 class MtgoxTicker(concepts.market.Ticker):
13 17 TIME_PERIOD = timedelta(days=1)
14 18
  19 + def __repr__(self):
  20 + return \
  21 + "<MtgoxTicker({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8})" \
  22 + .format(self.market, self.time, self.high, self.high, self.last,
  23 + self.volume, self.average, self.buy, self.sell)
  24 +
  25 +
  26 +class MtGoxOrder(Order):
  27 +
  28 + def __init__(self, market, oid, timestamp, buy_or_sell, from_amount,
  29 + exchange_rate, properties="", entity=None):
  30 + super(MtGoxOrder, self).__init__(market, timestamp, buy_or_sell, from_amount, exchange_rate, properties, entity)
  31 + self.oid = oid
15 32
16   -class Market(BaseMarket):
  33 + def __repr__(self):
  34 + return \
  35 + "<MtGoxOrder({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}>" \
  36 + .format(self.market, self.timestamp, self.oid, self.buy_or_sell,
  37 + self.from_amount, self.exchange_rate, self.properties, self.entity)
  38 +
  39 +
  40 +class MtGoxMarket(BaseMarket):
17 41 MARKET_NAME = "MtGox"
18 42
19 43 def __init__(self, currency):
@@ -28,6 +52,8 @@ def _multiplier(self, currency):
28 52 return self.multiplier[currency.name]
29 53
30 54 def getTicker(self):
  55 + logger.debug("getting ticker")
  56 +
31 57 time = datetime.now()
32 58 data = low_level.ticker(self.currency1.name)
33 59
@@ -43,6 +69,8 @@ def getTicker(self):
43 69 return ticker
44 70
45 71 def getDepth(self):
  72 + logger.debug("getting depth")
  73 +
46 74 low_level_depth = low_level.depth()
47 75
48 76 return {
@@ -66,6 +94,8 @@ def _depthToOrders(self, depth, order_type):
66 94 return orders
67 95
68 96 def getTrades(self):
  97 + logger.debug("getting trades")
  98 +
69 99 low_level_trades = low_level.trades()
70 100
71 101 # convert tradres to array of Trades
@@ -86,3 +116,76 @@ def getTrades(self):
86 116 trades.append(t)
87 117
88 118 return trades
  119 +
  120 +
  121 +class MtGoxParticipant(MtGoxMarket, ActiveParticipant):
  122 +
  123 + def __init__(self, currency, key, secret):
  124 + MtGoxMarket.__init__(self, currency)
  125 +
  126 + self.private = low_level.Private(key, secret)
  127 +
  128 + def placeBidOrder(self, amount, price):
  129 + """places an Order in the market for price/amount"""
  130 +
  131 + logger.debug("placing bid order")
  132 +
  133 + oid = self.private.bid(amount.value, price.exchange_rate)
  134 +
  135 + now = datetime.now()
  136 + return MtGoxOrder(self, oid, now, Order.BID, amount, price, entity=self)
  137 +
  138 + def placeAskOrder(self, amount, price):
  139 + """places an Order in the market for price/amount"""
  140 +
  141 + logger.debug("placing ask order")
  142 +
  143 + oid = self.private.ask(amount, price)
  144 +
  145 + now = datetime.now()
  146 + return MtGoxOrder(self, oid, now, Order.ASK, amount, price, entity=self)
  147 +
  148 + def cancelOrder(self, order):
  149 + """Cancel an existing order"""
  150 + assert(isinstance(order, MtGoxOrder))
  151 +
  152 + logger.debug("cancelling order {0}".format(order.oid))
  153 +
  154 + oid = order.oid
  155 + if order.is_buy_order():
  156 + result = self.private.cancel_bid(oid)
  157 + else:
  158 + result = self.private.cancel_ask(oid)
  159 +
  160 + if not result:
  161 + raise ActiveParticipant.ActiveParticipantError()
  162 +
  163 + def getOpenOrders(self):
  164 + """Gets all the open orders"""
  165 +
  166 + logger.debug("getting open orders")
  167 +
  168 + low_level_orders = self.private.orders()
  169 + orders = []
  170 +
  171 + for o in low_level_orders:
  172 + currency = Currency(o['currency'])
  173 + oid = o['oid']
  174 + timestamp = datetime.fromtimestamp(o['date'])
  175 + order_type = Order.BID if o['type'] else Order.ASK
  176 + amount = Amount(Decimal(o['amount']['value_int']) / self._multiplier(BTC), BTC)
  177 + price = ExchangeRate(currency, BTC, Decimal(o['price']['value_int']) / self._multiplier(currency))
  178 + order = MtGoxOrder(self, oid, timestamp, order_type, amount, price, entity=self)
  179 +
  180 + # add additional status from MtGox
  181 + order.status = o['status']
  182 +
  183 + orders.append(order)
  184 +
  185 + return orders
  186 +
  187 + def __str__(self):
  188 + return self.__repr__()
  189 +
  190 + def __repr__(self):
  191 + return "<MtGoxParticipant({0})>".format(self.currency1)
8 mexbtcapi/api/mtgox/http_v1/mtgox.py
@@ -212,11 +212,11 @@ def info(self):
212 212 def orders(self):
213 213 """Return standing orders"""
214 214 return self._generic('orders')
215   -
  215 +
216 216 def wallet_history(self,type='',date_start='',date_end='',trade_id='',page='',currency=CURRENCY) :
217 217 url = "https://mtgox.com/api/1/generic/wallet/history"
218 218 data = {
219   - 'currency' : currency,
  219 + 'currency' : currency,
220 220 'type' : type,
221 221 'date_start' : date_start,
222 222 'date_end' : date_end,
@@ -252,7 +252,7 @@ def ask(self, amount, price, currency=CURRENCY):
252 252 an expanded format. For example, int(12300000)BTC
253 253 is interpreted as 1.23BTC.
254 254 """
255   - self._order_add('ask', amount, price, currency)
  255 + return self._order_add('ask', amount, price, currency)
256 256
257 257 def bid(self, amount, price, currency=CURRENCY):
258 258 """Buy bitcoins
@@ -261,7 +261,7 @@ def bid(self, amount, price, currency=CURRENCY):
261 261 an expanded format. For example, int(12300000)BTC
262 262 is interpreted as 1.23BTC.
263 263 """
264   - self._order_add('bid', amount, price, currency)
  264 + return self._order_add('bid', amount, price, currency)
265 265
266 266 def _order_add(self, order_type, amount, price, currency):
267 267 if type(amount) in (Decimal, float):
44 mexbtcapi/concepts/market.py
@@ -43,16 +43,14 @@ class Order(object):
43 43 ASK = 'ASK'
44 44
45 45 def __init__(self, market, timestamp, buy_or_sell, from_amount,
46   - exchange_rate, properties="",
47   - from_entity=None, to_entity=None):
  46 + exchange_rate, properties="", entity=None):
48 47 assert isinstance(market, Market) # must not be null
49 48 assert isinstance(timestamp, datetime) # must not be null
50 49 assert buy_or_sell in [self.BID, self.ASK]
51 50 assert isinstance(from_amount, Amount)
52 51 assert isinstance(exchange_rate, ExchangeRate)
53 52 assert isinstance(properties, str)
54   - assert all([x is None or isinstance(x, Participant) for x
55   - in (from_entity, to_entity)])
  53 + assert entity is None or isinstance(entity, Participant)
56 54
57 55 self.market = market
58 56 self.timestamp = timestamp
@@ -60,14 +58,13 @@ def __init__(self, market, timestamp, buy_or_sell, from_amount,
60 58 self.from_amount = from_amount
61 59 self.exchange_rate = exchange_rate
62 60 self.properties = properties
63   - self.from_entity = from_entity
64   - self.to_entity = to_entity
  61 + self.entity = entity
65 62
66 63 def is_buy_order(self):
67   - return self.buy_or_sell == self.BUY
  64 + return self.buy_or_sell == self.BID
68 65
69 66 def is_sell_order(self):
70   - return self.buy_or_sell == self.SELL
  67 + return self.buy_or_sell != self.BID
71 68
72 69 def __str__(self):
73 70 return "{0} -> {1}".format(self.from_amount, self.exchange_rate)
@@ -127,18 +124,31 @@ class ActiveParticipant(Participant):
127 124 """A participant under user control (may be the user itself)
128 125 """
129 126
130   - def placeTrade(trade):
131   - """places a trade in the market"""
  127 + def placeBidOrder(self, price, amount):
  128 + """places an Order in the market for price/amount"""
132 129 raise NotImplementedError()
133 130
134   - def cancelTrade(trade):
  131 + def placeAskOrder(self, price, amount):
  132 + """places an Order in the market for price/amount"""
135 133 raise NotImplementedError()
136 134
137   - class TradeAlreadyClosedError(Exception):
138   - """Occurs when trying to cancel a already-closed trade"""
  135 + def cancelOrder(self, order):
  136 + """Cancel an existing order"""
  137 + raise NotImplementedError()
  138 +
  139 + def getOpenOrders(self):
  140 + """Gets all the open orders"""
  141 + raise NotImplementedError()
  142 +
  143 + class ActiveParitipantError(Exception):
  144 + """Base ActiveParticipant error"""
139 145 pass
140 146
141   - class NotAuthorizedError(Exception):
  147 + class OrderAlreadyClosedError(ActiveParitipantError):
  148 + """Occurs when trying to cancel a already-closed Order"""
  149 + pass
  150 +
  151 + class NotAuthorizedError(ActiveParitipantError):
142 152 """Occurs when the user is not authorized to do the requested operation
143 153 """
144 154 pass
@@ -170,3 +180,9 @@ def __init__(self, market, time, high=None, low=None, average=None,
170 180 self.market, self.time, self.volume = market, time, volume
171 181 self.high, self.low, self.average, self.last, self.sell, self.buy = \
172 182 high, low, average, last, sell, buy
  183 +
  184 + def __repr__(self):
  185 + return \
  186 + "<Ticker({0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}, {8})" \
  187 + .format(self.market, self.time, self.high, self.high, self.last,
  188 + self.volume, self.average, self.buy, self.sell)

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.