/// <summary>The position of this trade in the order book for the trade type</summary> public int OrderBookIndex(ETradeType tt, Unit <decimal> price_q2b, out bool beyond_order_book) { // Check units if (price_q2b < 0m._(RateUnits)) { throw new Exception("Invalid price"); } // If a trade cannot be filled by existing orders, it becomes an offer. // E.g. // - Want to trade B2Q == Sell our 'B' to get 'Q'. // - If there are no suitable B2Q.Orders (i.e. people wanting to buy 'B') then our trade becomes an offer in the Q2B order book. // i.e. we want to buy 'Q' so our trade is a Q2B offer. var idx = -1; switch (tt) { default: throw new Exception($"Unknown trade type:{tt}"); case ETradeType.B2Q: { idx = Q2B.Offers.BinarySearch(x => + x.PriceQ2B.CompareTo(price_q2b), find_insert_position: true); beyond_order_book = idx == Q2B.Offers.Count; break; } case ETradeType.Q2B: { idx = B2Q.Offers.BinarySearch(x => - x.PriceQ2B.CompareTo(price_q2b), find_insert_position: true); beyond_order_book = idx == B2Q.Offers.Count; break; } } return(idx); }
/// <summary>The total value of orders with a better price than 'price'</summary> public Unit <decimal> OrderBookDepth(ETradeType tt, Unit <decimal> price_q2b, out bool beyond_order_book) { var index = OrderBookIndex(tt, price_q2b, out beyond_order_book); var orders = tt == ETradeType.B2Q ? Q2B.Offers : B2Q.Offers; return(orders.Take(index).Sum(x => x.AmountBase)); }
/// <summary>Get/Set spot price (in Quote/Base)</summary> public Unit <decimal>?this[ETradeType tt] { get { return (tt == ETradeType.Q2B ? m_spot_q2b : tt == ETradeType.B2Q ? m_spot_b2q : throw new Exception("Unknown trade type")); } set { if (value < 0m._(RateUnits)) { throw new Exception("Invalid spot price"); } switch (tt) { default: throw new Exception("Unknown trade type"); case ETradeType.Q2B: m_spot_q2b = value; break; case ETradeType.B2Q: m_spot_b2q = value; break; } CoinData.NotifyLivePriceChanged(Base); CoinData.NotifyLivePriceChanged(Quote); } }
// Notes: // - A 'Trade' is a description of a trade that *could* be placed. It is different // to an 'Order' which is live on an exchange, waiting to be filled. // - AmountIn * Price does not have to equal AmountOut, because 'Trade' is used // with the order book to calculate the best price for trading a given amount. // The price will represent the best price that covers all of the amount, not // the spot price. // - Don't implicitly change amounts/prices based on order type for the same reason. // Instead, allow any values to be set and use validate to check they're correct. // The 'EditTradeUI' should be used to modify properties and ensure correct behaviour // w.r.t to order type. // Rounding issues: // - Quantisation doesn't help, that just makes the discrepancies larger. // - Instead, maintain separate values for amount in and amount out. These imply the // price and allow control over which side of the trade gets rounded. /// <summary>Create a trade on 'pair' to convert 'amount_in' of 'coin_in' to 'amount_out'</summary> public Trade(Fund fund, TradePair pair, EOrderType order_type, ETradeType trade_type, Unit <decimal> amount_in, Unit <decimal> amount_out, Unit <decimal>?price_q2b = null, string creator = null) { // Check trade amounts and units if (amount_in < 0m._(trade_type.CoinIn(pair))) { throw new Exception("Invalid trade 'in' amount"); } if (amount_out < 0m._(trade_type.CoinOut(pair))) { throw new Exception("Invalid trade 'out' amount"); } if (amount_out != 0 && amount_in != 0 && trade_type.PriceQ2B(amount_out / amount_in) < 0m._(pair.RateUnits)) { throw new Exception("Invalid trade price"); } CreatorName = creator ?? string.Empty; Fund = fund; Pair = pair; OrderType = order_type; TradeType = trade_type; AmountIn = amount_in; AmountOut = amount_out; PriceQ2B = price_q2b != null ? price_q2b.Value : amount_out != 0 && amount_in != 0 ? TradeType.PriceQ2B(amount_out / amount_in) : SpotPriceQ2B; }
/// <summary>Return the order type implied by the given trade type and price relative to the spot price</summary> public EOrderType?OrderType(ETradeType trade_type, Unit <decimal> price_q2b) { // If the spot price is unknown, then so is the impled order type var market_price_q2b = SpotPrice[trade_type]; if (market_price_q2b == null) { return(null); } if (trade_type == ETradeType.Q2B) { return (price_q2b > market_price_q2b.Value * (decimal)(1.0 + SettingsData.Settings.MarketOrderPriceToleranceFrac) ? EOrderType.Stop : price_q2b < market_price_q2b.Value * (decimal)(1.0 - SettingsData.Settings.MarketOrderPriceToleranceFrac) ? EOrderType.Limit : EOrderType.Market); } if (trade_type == ETradeType.B2Q) { return (price_q2b <market_price_q2b.Value *(decimal)(1.0 + SettingsData.Settings.MarketOrderPriceToleranceFrac) ? EOrderType.Stop : price_q2b> market_price_q2b.Value * (decimal)(1.0 - SettingsData.Settings.MarketOrderPriceToleranceFrac) ? EOrderType.Limit : EOrderType.Market); } throw new Exception("Unknown trade type"); }
// Notes: // - This is one side of the MarketDepth (either buy or sell). // - The available trades are ordered by price (Increasing for Q2B, Decreasing for B2Q). public OrderBook(Coin @base, Coin quote, ETradeType tt) { TradeType = tt; Offers = new List <Offer>(); Base = @base; Quote = quote; }
// Notes: // - A TradeCompleted is a completed single trade that is part of an OrderCompleted // - TradeCompleted doesn't really need 'pair' and 'tt' because they are duplicates // of fields in the containing OrderCompleted. However, they allow convenient conversion // to CoinIn/CoinOut etc. public TradeCompleted(long order_id, long trade_id, TradePair pair, ETradeType tt, Unit <decimal> amount_in, Unit <decimal> amount_out, Unit <decimal> commission, Coin commission_coin, DateTimeOffset created, DateTimeOffset updated) { // Check units if (amount_in <= 0m._(tt.CoinIn(pair))) { throw new Exception("Invalid 'in' amount"); } if (amount_out <= 0m._(tt.CoinOut(pair))) { throw new Exception("Invalid 'out' amount"); } if (commission < 0m._(commission_coin)) { throw new Exception("Negative commission"); } if (created < Misc.CryptoCurrencyEpoch) { throw new Exception("Invalid creation time"); } OrderId = order_id; TradeId = trade_id; Pair = pair; TradeType = tt; AmountIn = amount_in; AmountOut = amount_out; Commission = commission; CommissionCoin = commission_coin; Created = created; Updated = updated; }
/// <summary>Return the amount range to validate against for the output currency</summary> public RangeF <Unit <decimal> > AmountRangeOut(ETradeType tt) { return (tt == ETradeType.B2Q ? AmountRangeQuote : tt == ETradeType.Q2B ? AmountRangeBase : throw new Exception("Unknown trade type")); }
/// <summary>Return the unit type for a trade of this type on 'pair'</summary> public static string RateUnits(this ETradeType tt, TradePair pair) { return (tt == ETradeType.B2Q ? pair.RateUnits : tt == ETradeType.Q2B ? pair.RateUnitsInv : throw new Exception("Unknown trade type")); }
/// <summary>Return the opposite trade type</summary> public static ETradeType Opposite(this ETradeType tt) { return (tt == ETradeType.Q2B ? ETradeType.B2Q : tt == ETradeType.B2Q ? ETradeType.Q2B : throw new Exception($"Unknown trade type: {tt}")); }
public static string CoinOut(this ETradeType tt, CoinPair pair) { return (tt == ETradeType.B2Q ? pair.Quote : tt == ETradeType.Q2B ? pair.Base : throw new Exception("Unknown trade type")); }
/// <summary>Return the 'out' amount for a trade in this trade direction</summary> public static Unit <decimal> AmountOut(this ETradeType tt, Unit <decimal> amount_base, Unit <decimal> price_q2b) { return (tt == ETradeType.B2Q ? amount_base * price_q2b : tt == ETradeType.Q2B ? amount_base : throw new Exception("Unknown trade type")); }
/// <summary>Returns +1 for Q2B, -1 for B2Q</summary> public static int Sign(this ETradeType tt) { return (tt == ETradeType.Q2B ? +1 : tt == ETradeType.B2Q ? -1 : throw new Exception("Unknown trade type")); }
/// <summary>Convert an amount of currency using the available orders. e.g. Q2B => 'amount' in Quote, out in 'Base'</summary> public Trade MarketTrade(Fund fund, ETradeType tt, Unit <decimal> amount) { return (tt == ETradeType.Q2B ? QuoteToBase(fund, amount) : tt == ETradeType.B2Q ? BaseToQuote(fund, amount) : throw new Exception("Unknown trade type")); }
void SetSelectStore(ETradeType tradeType) { //买 if (tradeType == ETradeType.ETradeType_Buy) { m_dicMallItem = DataManager.Manager <HomeDataManager>().BuyItemDic; m_lstHomeTradeTab = DataManager.Manager <HomeDataManager>().GetTabList(m_tradeType); m_dicHomeTradeTab = DataManager.Manager <HomeDataManager>().TabNameDic; m_label_AddRemove_Text.text = "购买数量:"; m_label_Obtain_Text.text = "消 耗:"; m_label_Btn_name.text = "购买"; } //卖 if (tradeType == ETradeType.ETradeType_Sell) { m_dicMallItem = DataManager.Manager <HomeDataManager>().SellItemDic; m_lstHomeTradeTab = DataManager.Manager <HomeDataManager>().GetTabList(m_tradeType); m_dicHomeTradeTab = DataManager.Manager <HomeDataManager>().TabNameDic; m_label_AddRemove_Text.text = "出售数量:"; m_label_Obtain_Text.text = "获 得:"; m_label_Btn_name.text = "出售"; } if (m_GridCreator != null) { m_GridCreator.ClearAll(); //清除所有格子 } CreateHomeTradeUI(); }
/// <summary>Place an immediate market order</summary> public void NewMarketImmediateOrder(Instrument instr, ETradeType trade_type, double?volume = null, double?stop_loss = null, double?take_profit = null) { // Choose a volume for the position based on the current balance and the stop loss distance if (volume == null && stop_loss != null) { volume = CalculateVolume(instr, stop_loss.Value); } if (stop_loss == null && volume != null) { stop_loss = CalculateStopLoss(instr); } // Create the position we want to hold var position = new Position { SymbolCode = instr.SymbolCode, TradeType = trade_type, EntryTime = DateTimeOffset.UtcNow.Ticks, EntryPrice = trade_type == ETradeType.Long ? instr.PriceData.BidPrice : instr.PriceData.AskPrice, StopLossRel = stop_loss == null ? 0 : 0, //todo Volume = 0, Comment = "Auto Trade test", }; // Place the order Post(new OutMsg.PlaceMarketOrder(position, Settings.Trade.MarketRangePips)); }
/// <summary>Return 'price' in (Quote/Base) for this trade direction. Assumes price is in (CoinOut/CoinIn)</summary> public static Unit <decimal> PriceQ2B(this ETradeType tt, Unit <decimal> price) { return (price == 0m ? 0m / 1m._(price) : tt == ETradeType.B2Q ? price : tt == ETradeType.Q2B ? (1m / price) : throw new Exception("Unknown trade type")); }
// Notes: // - An OrderCompleted is a completed (or partially completed) Order consisting // of one or more 'TradeCompleted's that where made to complete the order. public OrderCompleted(long order_id, Fund fund, TradePair pair, ETradeType tt) { OrderId = order_id; Fund = fund; TradeType = tt; Pair = pair; Trades = new TradeCompletedCollection(this); }
public Trade(DateTime timeStamp, int quantity, ETradeType tradeType, decimal traderPrice, string tradedStock) { this.timeStamp = timeStamp; this.quantity = quantity; this.tradeType = tradeType; this.tradePrice = traderPrice; this.tradedStock = tradedStock; }
/// <summary>Return the 'base' amount for a trade in this trade direction</summary> public static Unit <decimal> AmountBase(this ETradeType tt, Unit <decimal> price_q2b, Unit <decimal>?amount_in = null, Unit <decimal>?amount_out = null) { var price = (Unit <decimal>?)price_q2b; return (tt == ETradeType.B2Q ? (amount_in ?? (amount_out / price) ?? throw new Exception("One of 'amount_in' or 'amount_out' must be given")) : tt == ETradeType.Q2B ? (amount_out ?? (amount_in / price) ?? throw new Exception("One of 'amount_in' or 'amount_out' must be given")) : throw new Exception("Unknown trade type")); }
/// <summary>Return the default amount to use for a trade on this pair (in Base currency)</summary> public Unit <decimal> DefaultTradeAmountBase(ETradeType tt, Unit <decimal> price_q2b) { var vol = DefaultTradeAmount(tt); return (tt == ETradeType.B2Q ? vol : // 'vol' is in base tt == ETradeType.Q2B ? vol / price_q2b : // 'vol' is in quote throw new Exception("Unknown trade type")); }
public static global::Bittrex.API.EOrderSide ToBittrexTT(this ETradeType trade_type) { switch (trade_type) { default: throw new Exception("Unknown trade type"); case ETradeType.Q2B: return(global::Bittrex.API.EOrderSide.Buy); case ETradeType.B2Q: return(global::Bittrex.API.EOrderSide.Sell); } }
public static global::Binance.API.EOrderSide ToBinanceTT(this ETradeType trade_type) { switch (trade_type) { default: throw new Exception("Unknown trade type"); case ETradeType.Q2B: return(global::Binance.API.EOrderSide.BUY); case ETradeType.B2Q: return(global::Binance.API.EOrderSide.SELL); } }
/// <summary>Convert a Tradee trade type to a CAlgo one</summary> public static TradeType ToCAlgoTradeType(this ETradeType tt) { switch (tt) { default: throw new Exception("Unknown trade type"); case ETradeType.None: throw new Exception("Unsupported trade type"); case ETradeType.Long: return(TradeType.Buy); case ETradeType.Short: return(TradeType.Sell); } }
// Notes: // - An Order is a request to buy/sell that has been sent to an exchange and // should exist somewhere in their order book. When an Order is filled it // becomes a 'OrderCompleted' public Order(long order_id, Fund fund, TradePair pair, EOrderType ot, ETradeType tt, Unit <decimal> amount_in, Unit <decimal> amount_out, Unit <decimal> remaining_in, DateTimeOffset created, DateTimeOffset updated) : base(fund, pair, ot, tt, amount_in, amount_out) { if (created < Misc.CryptoCurrencyEpoch) { throw new Exception("Invalid creation time"); } OrderId = order_id; UniqueKey = Guid.NewGuid(); RemainingIn = remaining_in; Created = created; Updated = updated; }
// Notes: // - Represents a single buy or sell order with the broker. // - A trade can have more than one of these. // - Orders are low level application objects, they don't know about charts, accounts, etc. // - Base currency is the currency of the first part of the instrument, e.g GBPNZD - base currency = GBP // - Account currency is the currency of the account = base currency * price_data.PipValue / price_data.PipSize public Order(int id, Instrument instr, ETradeType trade_type, Trade.EState state) { Id = id; Instrument = instr; TradeType = trade_type; EntryPrice = 0; ExitPrice = 0; EntryTimeUTC = DefaultEntryTime; ExitTimeUTC = DefaultExitTime; StopLossAbs = 0; TakeProfitAbs = 0; Volume = 0; GrossProfit = 0; NetProfit = 0; Commissions = 0; Swap = 0; Comment = string.Empty; State = state; }
/// <summary>Attempt to make a trade on 'pair' for the given 'price' and base 'amount'</summary> private void TryFillOrder(TradePair pair, Fund fund, long order_id, ETradeType tt, EOrderType ot, Unit <decimal> amount_in, Unit <decimal> amount_out, Unit <decimal> remaining_in, out Order ord, out OrderCompleted his) { // The order can be filled immediately, filled partially, or not filled and remain as an 'Order'. // Also, exchanges use the base currency as the amount to fill, so for Q2B trades it's possible // that 'amount_in' is less than the trade asked for. var market = m_depth[pair]; // Consume orders var price_q2b = tt.PriceQ2B(amount_out / amount_in); var amount_base = tt.AmountBase(price_q2b, amount_in: remaining_in); var filled = market.Consume(pair, tt, ot, price_q2b, amount_base, out var remaining_base); // The order is partially or completely filled... Debug.Assert(Misc.EqlAmount(amount_base, filled.Sum(x => x.AmountBase) + remaining_base)); ord = remaining_base != 0 ? new Order(order_id, fund, pair, ot, tt, amount_in, amount_out, tt.AmountIn(remaining_base, price_q2b), Model.UtcNow, Model.UtcNow) : null; his = remaining_base != amount_base ? new OrderCompleted(order_id, fund, pair, tt) : null; // Add 'TradeCompleted' entries for each order book offer that was filled foreach (var fill in filled) { his.Trades.AddOrUpdate(new TradeCompleted(his.OrderId, ++m_history_id, pair, tt, fill.AmountIn(tt), fill.AmountOut(tt), Exchange.Fee * fill.AmountOut(tt), tt.CoinOut(pair), Model.UtcNow, Model.UtcNow)); } }
/// <summary>The price that the trade was filled at</summary> public Unit <decimal> PriceQ2B(ETradeType tt) => tt.PriceQ2B(AmountOut / AmountIn);
public void UpdateUI(ResourceList wantedResources, ResourceList bankResources, ResourceList resources, XmlPortList ports, ETradeType tradeType) { _WantedResources = wantedResources; uiWanted.AvailableResources = bankResources; _Ports = ports; uiOffered.UpdateUI(resources, ports); if (tradeType == ETradeType.None) { pnlAutoTrade.Visibility = Visibility.Collapsed; btnCancel.Visibility = Visibility.Visible; uiWanted.ReadOnly = false; } else { pnlAutoTrade.Visibility = Visibility.Visible; btnCancel.Visibility = Visibility.Collapsed; lblItemType.Content = Enum.GetName(typeof(ETradeType), tradeType); uiWanted.Resources1 = _WantedResources; uiWanted.ReadOnly = true; switch (tradeType) { case ETradeType.City: imgItemType.Source = (ImageSource)Core.Instance.Icons["Sea3D"]; break; case ETradeType.Town: imgItemType.Source = (ImageSource)Core.Instance.Icons["Town3D"]; break; case ETradeType.Road: imgItemType.Source = (ImageSource)Core.Instance.Icons["Road48"]; break; case ETradeType.Ship: imgItemType.Source = (ImageSource)Core.Instance.Icons["Ship48"]; break; case ETradeType.Devcard: imgItemType.Source = (ImageSource)Core.Instance.Icons["IconBuyDevcard48"]; break; } } }
/// <summary> /// Constructor /// </summary> /// <param name="type">Type</param> protected Trade(ETradeType type) { Type = type; }
/// <summary>Return the default amount to use for a trade on 'pair' (in CoinIn currency)</summary> public Unit <decimal> DefaultTradeAmount(ETradeType tt) { var coin = tt.CoinIn(this); return(coin.DefaultTradeAmount); }
/// <summary>The total value of orders with a better price than 'price'</summary> public Unit <decimal> OrderBookDepth(ETradeType tt, Unit <decimal> price_q2b, out bool beyond_order_book) { return(MarketDepth.OrderBookDepth(tt, price_q2b, out beyond_order_book)); }