From: Ismaël Bouya Date: Sun, 4 Feb 2018 18:12:50 +0000 (+0100) Subject: WIP: handle more balance information X-Git-Tag: v0.1~20 X-Git-Url: https://git.immae.eu/?p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FCryptoportfolio%2FTrader.git;a=commitdiff_plain;h=006a20846236ad365ec814f848f5fbf7e3dc7d3c WIP: handle more balance information --- diff --git a/market.py b/market.py index 1e1e083..979fbbf 100644 --- a/market.py +++ b/market.py @@ -26,7 +26,18 @@ def poloniex_fetch_balance(self, params={}): return self.parse_balance(result) ccxt.poloniex.fetch_balance = poloniex_fetch_balance -def poloniex_fetch_margin_balances(self): +def poloniex_fetch_margin_balance(self): + """ + portfolio.market.privatePostGetMarginPosition({"currencyPair": "BTC_DASH"}) + See DASH/BTC positions + {'amount': '-0.10000000', -> DASH empruntés + 'basePrice': '0.06818560', -> à ce prix là (0.06828800 demandé * (1-0.15%)) + 'lendingFees': '0.00000000', -> ce que je dois à mon créditeur + 'liquidationPrice': '0.15107132', -> prix auquel ça sera liquidé (dépend de ce que j’ai déjà sur mon compte margin) + 'pl': '-0.00000371', -> plus-value latente si je rachète tout de suite (négatif = perdu) + 'total': '0.00681856', -> valeur totale empruntée en BTC + 'type': 'short'} + """ positions = self.privatePostGetMarginPosition({"currencyPair": "all"}) parsed = {} for symbol, position in positions.items(): @@ -41,23 +52,10 @@ def poloniex_fetch_margin_balances(self): "liquidationPrice": decimal.Decimal(position["liquidationPrice"]), "type": position["type"], "total": decimal.Decimal(position["total"]), - "base_currency": base_currency, + "baseCurrency": base_currency, } return parsed -ccxt.poloniex.fetch_margin_balances = poloniex_fetch_margin_balances - -def poloniex_fetch_balance_with_margin(self, params={}): - exchange_balance = self.fetch_balance(params=params) - margin_balances = self.fetch_margin_balances() - - for currency, balance in margin_balances.items(): - assert exchange_balance[currency]["total"] == 0 - assert balance["type"] == "short" - exchange_balance[currency]["total"] = balance["amount"] - exchange_balance[currency]["marginPosition"] = balance - return exchange_balance -ccxt.poloniex.fetch_balance_with_margin = poloniex_fetch_balance_with_margin - +ccxt.poloniex.fetch_margin_balance = poloniex_fetch_margin_balance def poloniex_fetch_balance_per_type(self): balances = self.privatePostReturnAvailableAccountBalances() @@ -72,6 +70,49 @@ def poloniex_fetch_balance_per_type(self): return result ccxt.poloniex.fetch_balance_per_type = poloniex_fetch_balance_per_type +def poloniex_fetch_all_balances(self): + exchange_balances = self.fetch_balance() + margin_balances = self.fetch_margin_balance() + balances_per_type = self.fetch_balance_per_type() + + all_balances = {} + in_positions = {} + + for currency, exchange_balance in exchange_balances.items(): + if currency in ["info", "free", "used", "total"]: + continue + + margin_balance = margin_balances.get(currency, {}) + balance_per_type = balances_per_type.get(currency, {}) + + all_balances[currency] = { + "total": exchange_balance["total"] + margin_balance.get("amount", 0), + "exchange_used": exchange_balance["used"], + "exchange_total": exchange_balance["total"] - balance_per_type.get("margin", 0), + "exchange_free": exchange_balance["free"] - balance_per_type.get("margin", 0), + "margin_free": balance_per_type.get("margin", 0) + margin_balance.get("amount", 0), + "margin_borrowed": 0, + "margin_total": balance_per_type.get("margin", 0) + margin_balance.get("amount", 0), + "margin_lending_fees": margin_balance.get("lendingFees", 0), + "margin_pending_gain": margin_balance.get("pl", 0), + "margin_position_type": margin_balance.get("type", None), + "margin_liquidation_price": margin_balance.get("liquidationPrice", 0), + "margin_borrowed_base_price": margin_balance.get("total", 0), + "margin_borrowed_base_currency": margin_balance.get("baseCurrency", None), + } + if len(margin_balance) > 0: + if margin_balance["baseCurrency"] not in in_positions: + in_positions[margin_balance["baseCurrency"]] = 0 + in_positions[margin_balance["baseCurrency"]] += margin_balance["total"] + + for currency, in_position in in_positions.items(): + all_balances[currency]["total"] += in_position + all_balances[currency]["margin_total"] += in_position + all_balances[currency]["margin_borrowed"] += in_position + + return all_balances +ccxt.poloniex.fetch_all_balances = poloniex_fetch_all_balances + def poloniex_parse_ticker(self, ticker, market=None): timestamp = self.milliseconds() symbol = None @@ -153,48 +194,54 @@ def poloniex_transfer_balance(self, currency, amount, from_account, to_account): return result["success"] == 1 ccxt.poloniex.transfer_balance = poloniex_transfer_balance -# portfolio.market.create_order("DASH/BTC", "limit", "sell", 0.1, price=0.06828800, account="margin") - -# portfolio.market.privatePostReturnTradableBalances() -# Returns tradable balances in margin -# 'BTC_DASH': {'BTC': '0.01266999', 'DASH': '0.08574839'}, -# Je peux emprunter jusqu’à 0.08574839 DASH ou 0.01266999 BTC (une position est -# déjà ouverte) -# 'BTC_CLAM': {'BTC': '0.00585143', 'CLAM': '7.79300395'}, -# Je peux emprunter 7.7 CLAM pour les vendre contre des BTC, ou emprunter -# 0.00585143 BTC pour acheter des CLAM - -# portfolio.market.privatePostReturnMarginAccountSummary() -# Returns current informations for margin -# {'currentMargin': '1.49680968', -> marge (ne doit pas descendre sous 20% / 0.2) -# = netValue / totalBorrowedValue -# 'lendingFees': '0.00000000', -> fees totaux -# 'netValue': '0.01008254', -> balance + plus-value -# 'pl': '0.00008254', -> plus value latente (somme des positions) -# 'totalBorrowedValue': '0.00673602', -> valeur en BTC empruntée -# 'totalValue': '0.01000000'} -> valeur totale en compte - - -# portfolio.market.privatePostGetMarginPosition({"currencyPair": "BTC_DASH"}) -# See DASH/BTC positions -# {'amount': '-0.10000000', -> DASH empruntés -# 'basePrice': '0.06818560', -> à ce prix là (0.06828800 demandé * (1-0.15%)) -# 'lendingFees': '0.00000000', -> ce que je dois à mon créditeur -# 'liquidationPrice': '0.15107132', -> prix auquel ça sera liquidé (dépend de ce que j’ai déjà sur mon compte margin) -# 'pl': '-0.00000371', -> plus-value latente si je rachète tout de suite (négatif = perdu) -# 'total': '0.00681856', -> valeur totale empruntée en BTC -# 'type': 'short'} - - -# closeMarginPosition({"currencyPair": "BTC_DASH"}) : fermer la position au prix -# du marché -# Nécessaire à la fin -# portfolio.market.create_order("DASH/BTC", "limit", "buy", 0.1, price=0.06726487, account="margin") - -# portfolio.market.fetch_balance_per_type() -# Ne suffit pas pour calculer les positions: ne contient que les 0.01 envoyés -# TODO: vérifier si fetch_balance marque ces 0.01 comme disponibles -> oui +def poloniex_close_margin_position(self, currency, base_currency): + """ + closeMarginPosition({"currencyPair": "BTC_DASH"}) + fermer la position au prix du marché + """ + symbol = "{}_{}".format(base_currency, currency) + self.privatePostCloseMarginPosition({"currencyPair": symbol}) +ccxt.poloniex.close_margin_position = poloniex_close_margin_position + +def poloniex_tradable_balances(self): + """ + portfolio.market.privatePostReturnTradableBalances() + Returns tradable balances in margin + 'BTC_DASH': {'BTC': '0.01266999', 'DASH': '0.08574839'}, + Je peux emprunter jusqu’à 0.08574839 DASH ou 0.01266999 BTC (une position est déjà ouverte) + 'BTC_CLAM': {'BTC': '0.00585143', 'CLAM': '7.79300395'}, + Je peux emprunter 7.7 CLAM pour les vendre contre des BTC, ou emprunter 0.00585143 BTC pour acheter des CLAM + """ + + tradable_balances = self.privatePostReturnTradableBalances() + for symbol, balances in tradable_balances.items(): + for currency, balance in balances.items(): + balances[currency] = decimal.Decimal(balance) + return tradable_balances +ccxt.poloniex.fetch_tradable_balances = poloniex_tradable_balances + +def poloniex_margin_summary(self): + """ + portfolio.market.privatePostReturnMarginAccountSummary() + Returns current informations for margin + {'currentMargin': '1.49680968', -> marge (ne doit pas descendre sous 20% / 0.2) + = netValue / totalBorrowedValue + 'lendingFees': '0.00000000', -> fees totaux + 'netValue': '0.01008254', -> balance + plus-value + 'pl': '0.00008254', -> plus value latente (somme des positions) + 'totalBorrowedValue': '0.00673602', -> valeur en BTC empruntée + 'totalValue': '0.01000000'} -> valeur totale en compte + """ + summary = self.privatePostReturnMarginAccountSummary() + return { + "current_margin": decimal.Decimal(summary["currentMargin"]), + "lending_fees": decimal.Decimal(summary["lendingFees"]), + "gains": decimal.Decimal(summary["pl"]), + "total_borrowed": decimal.Decimal(summary["totalBorrowedValue"]), + "total": decimal.Decimal(summary["totalValue"]), + } +ccxt.poloniex.margin_summary = poloniex_margin_summary market = ccxt.poloniex({ "apiKey": "XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX", "secret": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", diff --git a/portfolio.py b/portfolio.py index d9d2d4d..0ab16fd 100644 --- a/portfolio.py +++ b/portfolio.py @@ -141,11 +141,22 @@ class Amount: def __truediv__(self, value): return self.__floordiv__(value) + def __le__(self, other): + return self == other or self < other + def __lt__(self, other): + if other == 0: + return self.value < 0 if self.currency != other.currency: raise Exception("Comparing amounts must be done with same currencies") return self.value < other.value + def __gt__(self, other): + return not self <= other + + def __ge__(self, other): + return not self < other + def __eq__(self, other): if other == 0: return self.value == 0 @@ -153,6 +164,12 @@ class Amount: raise Exception("Comparing amounts must be done with same currencies") return self.value == other.value + def __ne__(self, other): + return not self == other + + def __neg__(self): + return Amount(self.currency, - self.value) + def __str__(self): if self.linked_to is None: return "{:.8f} {}".format(self.value, self.currency) @@ -168,15 +185,24 @@ class Amount: class Balance: known_balances = {} - def __init__(self, currency, total_value, free_value, used_value): + def __init__(self, currency, hash_): self.currency = currency - self.total = Amount(currency, total_value) - self.free = Amount(currency, free_value) - self.used = Amount(currency, used_value) - - @classmethod - def from_hash(cls, currency, hash_): - return cls(currency, hash_["total"], hash_["free"], hash_["used"]) + for key in ["total", + "exchange_total", "exchange_used", "exchange_free", + "margin_total", "margin_borrowed", "margin_free"]: + setattr(self, key, Amount(currency, hash_.get(key, 0))) + + self.margin_position_type = hash_["margin_position_type"] + + if hash_["margin_borrowed_base_currency"] is not None: + base_currency = hash_["margin_borrowed_base_currency"] + for key in [ + "margin_liquidation_price", + "margin_pending_gain", + "margin_lending_fees", + "margin_borrowed_base_price" + ]: + setattr(self, key, Amount(base_currency, hash_[key])) @classmethod def in_currency(cls, other_currency, market, compute_value="average", type="total"): @@ -192,20 +218,14 @@ class Balance: def currencies(cls): return cls.known_balances.keys() - @classmethod - def _fill_balances(cls, hash_): - for key in hash_: - if key in ["info", "free", "used", "total"]: - continue - if hash_[key]["total"] != 0 or key in cls.known_balances: - cls.known_balances[key] = cls.from_hash(key, hash_[key]) - @classmethod def fetch_balances(cls, market): - cls._fill_balances(market.fetch_balance()) + all_balances = market.fetch_all_balances() + for currency, balance in all_balances.items(): + if balance["exchange_total"] != 0 or balance["margin_total"] != 0 or \ + currency in cls.known_balances: + cls.known_balances[currency] = cls(currency, balance) return cls.known_balances - # FIXME:Separate balances per trade type and in position - # Need to check how balances in position are represented @classmethod @@ -216,31 +236,21 @@ class Balance: amounts = {} for currency, (ptt, trade_type) in repartition.items(): amounts[currency] = ptt * amount / sum_ratio + if trade_type == "short": + amounts[currency] = - amounts[currency] if currency not in cls.known_balances: cls.known_balances[currency] = cls(currency, 0, 0, 0) return amounts - @classmethod - def dispatch_trade_types(cls, repartition=None): - if repartition is None: - repartition = Portfolio.repartition() - trade_types = {} - for currency, (ptt, trade_type) in repartition.items(): - trade_types[currency] = trade_type - return trade_types - # FIXME: once we know the repartition and sold everything, we can move - # the necessary part to the margin account - @classmethod def prepare_trades(cls, market, base_currency="BTC", compute_value="average"): cls.fetch_balances(market) values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) total_base_value = sum(values_in_base.values()) new_repartition = cls.dispatch_assets(total_base_value) - trade_types = cls.dispatch_trade_types() # Recompute it in case we have new currencies values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) - Trade.compute_trades(values_in_base, new_repartition, trade_types, market=market) + Trade.compute_trades(values_in_base, new_repartition, market=market) @classmethod def update_trades(cls, market, base_currency="BTC", compute_value="average", only=None): @@ -248,8 +258,7 @@ class Balance: values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) total_base_value = sum(values_in_base.values()) new_repartition = cls.dispatch_assets(total_base_value) - trade_types = cls.dispatch_trade_types() - Trade.compute_trades(values_in_base, new_repartition, trade_types, only=only, market=market) + Trade.compute_trades(values_in_base, new_repartition, only=only, market=market) @classmethod def prepare_trades_to_sell_all(cls, market, base_currency="BTC", compute_value="average"): @@ -257,11 +266,39 @@ class Balance: values_in_base = cls.in_currency(base_currency, market, compute_value=compute_value) total_base_value = sum(values_in_base.values()) new_repartition = cls.dispatch_assets(total_base_value, repartition={ base_currency: (1, "long") }) - trade_types = cls.dispatch_trade_types() - Trade.compute_trades(values_in_base, new_repartition, trade_types, market=market) + Trade.compute_trades(values_in_base, new_repartition, market=market) def __repr__(self): - return "Balance({} [{}/{}/{}])".format(self.currency, str(self.free), str(self.used), str(self.total)) + if self.exchange_total > 0: + if self.exchange_free > 0 and self.exchange_used > 0: + exchange = " Exch: [✔{} + ❌{} = {}]".format(str(self.exchange_free), str(self.exchange_used), str(self.exchange_total)) + elif self.exchange_free > 0: + exchange = " Exch: [✔{}]".format(str(self.exchange_free)) + else: + exchange = " Exch: [❌{}]".format(str(self.exchange_used)) + else: + exchange = "" + + if self.margin_total > 0: + if self.margin_free != 0 and self.margin_borrowed != 0: + margin = " Margin: [✔{} + borrowed {} = {}]".format(str(self.margin_free), str(self.margin_borrowed), str(self.margin_total)) + elif self.margin_free != 0: + margin = " Margin: [✔{}]".format(str(self.margin_free)) + else: + margin = " Margin: [borrowed {}]".format(str(self.margin_borrowed)) + elif self.margin_total < 0: + margin = " Margin: [{} @@ {}/{}]".format(str(self.margin_total), + str(self.margin_borrowed_base_price), + str(self.margin_lending_fees)) + else: + margin = "" + + if self.margin_total != 0 and self.exchange_total != 0: + total = " Total: [{}]".format(str(self.total)) + else: + total = "" + + return "Balance({}".format(self.currency) + "".join([exchange, margin, total]) + ")" class Computation: computations = { @@ -272,19 +309,21 @@ class Computation: } class Trade: - trades = {} + trades = [] - def __init__(self, value_from, value_to, currency, trade_type, market=None): + def __init__(self, value_from, value_to, currency, market=None): # We have value_from of currency, and want to finish with value_to of # that currency. value_* may not be in currency's terms self.currency = currency self.value_from = value_from self.value_to = value_to - self.trade_type = trade_type self.orders = [] self.market = market assert self.value_from.currency == self.value_to.currency - assert self.value_from.linked_to is not None and self.value_from.linked_to.currency == self.currency + if self.value_from != 0: + assert self.value_from.linked_to is not None and self.value_from.linked_to.currency == self.currency + elif self.value_from.linked_to is None: + self.value_from.linked_to = Amount(self.currency, 0) self.base_currency = self.value_from.currency fees_cache = {} @@ -331,28 +370,60 @@ class Trade: return cls.get_ticker(c1, c2, market) @classmethod - def compute_trades(cls, values_in_base, new_repartition, trade_types, only=None, market=None): + def compute_trades(cls, values_in_base, new_repartition, only=None, market=None): base_currency = sum(values_in_base.values()).currency for currency in Balance.currencies(): if currency == base_currency: continue - trade = cls( - values_in_base.get(currency, Amount(base_currency, 0)), - new_repartition.get(currency, Amount(base_currency, 0)), - currency, - trade_types.get(currency, "long"), - market=market - ) - if only is None or trade.action == only: - cls.trades[currency] = trade + value_from = values_in_base.get(currency, Amount(base_currency, 0)) + value_to = new_repartition.get(currency, Amount(base_currency, 0)) + if value_from.value * value_to.value < 0: + trade_1 = cls(value_from, Amount(base_currency, 0), currency, market=market) + if only is None or trade_1.action == only: + cls.trades.append(trade_1) + trade_2 = cls(Amount(base_currency, 0), value_to, currency, market=market) + if only is None or trade_2.action == only: + cls.trades.append(trade_2) + else: + trade = cls( + value_from, + value_to, + currency, + market=market + ) + if only is None or trade.action == only: + cls.trades.append(trade) return cls.trades @classmethod def prepare_orders(cls, only=None, compute_value="default"): - for currency, trade in cls.trades.items(): + for trade in cls.trades: if only is None or trade.action == only: trade.prepare_order(compute_value=compute_value) + @classmethod + def move_balances(cls, market, debug=False): + needed_in_margin = {} + for trade in cls.trades: + if trade.trade_type == "short": + if trade.value_to.currency not in needed_in_margin: + needed_in_margin[trade.value_to.currency] = 0 + needed_in_margin[trade.value_to.currency] += abs(trade.value_to) + for currency, needed in needed_in_margin.items(): + current_balance = Balance.known_balances[currency].margin_free + delta = (needed - current_balance).value + # FIXME: don't remove too much if there are open margin position + if delta > 0: + if debug: + print("market.transfer_balance({}, {}, 'exchange', 'margin')".format(currency, delta)) + else: + market.transfer_balance(currency, delta, "exchange", "margin") + elif delta < 0: + if debug: + print("market.transfer_balance({}, {}, 'margin', 'exchange')".format(currency, -delta)) + else: + market.transfer_balance(currency, -delta, "margin", "exchange") + @property def action(self): if self.value_from == self.value_to: @@ -361,17 +432,23 @@ class Trade: return None if self.value_from < self.value_to: - return "buy" + return "acquire" else: - return "sell" + return "dispose" def order_action(self, inverted): - # a xor b xor c - if (self.trade_type == "short") != ((self.value_from < self.value_to) != inverted): + if (self.value_from < self.value_to) != inverted: return "buy" else: return "sell" + @property + def trade_type(self): + if self.value_from + self.value_to < 0: + return "short" + else: + return "long" + def prepare_order(self, compute_value="default"): if self.action is None: return @@ -382,15 +459,13 @@ class Trade: rate = Trade.compute_value(ticker, self.order_action(inverted), compute_value=compute_value) # 0.1 - # FIXME: optimize if value_to == 0 or value_from == 0?) - delta_in_base = abs(self.value_from - self.value_to) # 9 BTC's worth of move (10 - 1 or 1 - 10 depending on case) if not inverted: currency = self.base_currency # BTC - if self.action == "sell": + if self.action == "dispose": # I have 10 BTC worth of FOO, and I want to sell 9 BTC worth of it # At rate 1 Foo = 0.1 BTC value_from = self.value_from.linked_to @@ -418,7 +493,11 @@ class Trade: # I want to buy 9 / 0.1 FOO # Action: "sell" "9 BTC" at rate "1/0.1" "FOO" on "market" - self.orders.append(Order(self.order_action(inverted), delta, rate, currency, self.trade_type, self.market)) + close_if_possible = (self.value_to == 0) + + self.orders.append(Order(self.order_action(inverted), + delta, rate, currency, self.trade_type, self.market, + close_if_possible=close_if_possible)) @classmethod def compute_value(cls, ticker, action, compute_value="default"): @@ -432,7 +511,7 @@ class Trade: @classmethod def all_orders(cls, state=None): - all_orders = sum(map(lambda v: v.orders, cls.trades.values()), []) + all_orders = sum(map(lambda v: v.orders, cls.trades), []) if state is None: return all_orders else: @@ -465,16 +544,15 @@ class Trade: order.get_status() def __repr__(self): - return "Trade({} -> {} in {}, {} {})".format( + return "Trade({} -> {} in {}, {})".format( self.value_from, self.value_to, self.currency, - self.action, - self.trade_type) + self.action) @classmethod def print_all_with_order(cls): - for trade in cls.trades.values(): + for trade in cls.trades: trade.print_with_order() def print_with_order(self): @@ -483,7 +561,8 @@ class Trade: print("\t", order, sep="") class Order: - def __init__(self, action, amount, rate, base_currency, trade_type, market): + def __init__(self, action, amount, rate, base_currency, trade_type, market, + close_if_possible=False): self.action = action self.amount = amount self.rate = rate @@ -492,15 +571,17 @@ class Order: self.trade_type = trade_type self.result = None self.status = "pending" + self.close_if_possible = close_if_possible def __repr__(self): - return "Order({} {} {} at {} {} [{}])".format( + return "Order({} {} {} at {} {} [{}]{})".format( self.action, self.trade_type, self.amount, self.rate, self.base_currency, - self.status + self.status, + " ✂" if self.close_if_possible else "", ) @property @@ -527,8 +608,6 @@ class Order: symbol, self.action, amount, self.rate, self.account)) else: try: - if self.action == "sell" and self.trade_type == "short": - assert self.market.transfer_balance(self.base_currency, amount * self.rate, "exchange", "margin") self.result = self.market.create_order(symbol, 'limit', self.action, amount, price=self.rate, account=self.account) self.status = "open" except Exception as e: @@ -542,9 +621,17 @@ class Order: # other states are "closed" and "canceled" if self.status == "open": result = self.market.fetch_order(self.result['id']) - self.status = result["status"] + if result["status"] != "open": + self.mark_finished_order(result["status"]) return self.status + def mark_finished_order(self, status): + if status == "closed": + if self.trade_type == "short" and self.action == "buy" and self.close_if_possible: + self.market.close_margin_position(self.amount.currency, self.base_currency) + + self.status = result["status"] + def cancel(self): self.market.cancel_order(self.result['id']) @@ -557,11 +644,23 @@ def print_orders(market, base_currency="BTC"): def make_orders(market, base_currency="BTC"): Balance.prepare_trades(market, base_currency=base_currency) - for currency, trade in Trade.trades.items(): + for trade in Trade.trades: print(trade) for order in trade.orders: print("\t", order, sep="") order.run() +def sell_all(market, base_currency="BTC"): + Balance.prepare_trades_to_sell_all(market) + Trade.prepare_orders(compute_value="average") + Trade.run_orders() + Trade.follow_orders() + + Balance.update_trades(market, only="acquire") + Trade.prepare_orders(only="acquire") + Trade.move_balances(market) + Trade.run_orders() + Trade.follow_orders() + if __name__ == '__main__': print_orders(market) diff --git a/test.py b/test.py index 8240eb4..b85e39f 100644 --- a/test.py +++ b/test.py @@ -844,6 +844,10 @@ class AcceptanceTest(unittest.TestCase): self.assertEqual("sell", all_orders[3].action) self.assertEqual("long", all_orders[3].trade_type) + # Action 6b + # TODO: + # Move balances to margin + # Action 7 # TODO # portfolio.Trade.run_orders()