/// <summary> /// Importa un archivo DDP en formato XML a la base de datos /// </summary> /// <param name="stream">Stream de memoria del archivo</param> public DateTime Import(XmlDocument ddpxml, DDPVersion ddpVersion) { DateTime pubDate = DateTime.UtcNow; DBDataContext context = null; try { context = new DBDataContext(Config.ConnectionString); //--------------------------------------------------- XmlNamespaceManager nsmanager = new XmlNamespaceManager(ddpxml.NameTable); nsmanager.AddNamespace("lr", "http://gisis.imo.org/XML/LRIT/ddp/2008"); //--------------------------------------------------- //Hack-o-mati: Asociation of contracting goverment Dictionary <ContractingGoverment, ContractingConfig> goverments = new Dictionary <ContractingGoverment, ContractingConfig>(); //Just for return, do not use here. pubDate = DateTime.Parse(ddpxml.SelectSingleNode("/lr:DataDistributionPlan", nsmanager).Attributes["regularVersionImplementationAt"].Value); readCGS("/lr:DataDistributionPlan/lr:ContractingGovernment", ref goverments, ref ddpxml, ref nsmanager); readCGS("/lr:DataDistributionPlan/lr:Territory", ref goverments, ref ddpxml, ref nsmanager); var cgda = new ContractingGovermentDataAccess(context); var pda = new PlaceDataAccess(context); var soda = new StandingOrderDataAccess(context); var ssda = new SARServiceDataAccess(context); //var exda = new ExclusionDataAccess(context); //cgda.DropAll(); foreach (KeyValuePair <ContractingGoverment, ContractingConfig> kv in goverments) { kv.Key.DDPVersionId = ddpVersion.Id; int gId = cgda.Create(kv.Key); foreach (Place p in kv.Value.places) { p.ContractingGovermentId = gId; } foreach (SARService ss in kv.Value.sarservice) { ss.ContractingGovermentId = gId; } //foreach (Exclusion excl in kv.Value.exclusions) // excl.ContractingGoverment = gId; log.Debug(string.Format("{2}: Key:{0}, Value{1}", kv.Key.Name, kv.Value.places.Count, kv.Key.Id)); pda.Create(kv.Value.places.ToArray()); //exda.Create(kv.Value.exclusions.ToArray()); ssda.Create(kv.Value.sarservice.ToArray()); //Places with ids List <Place> places = pda.GetAll(ddpVersion.regularVer + ":" + ddpVersion.inmediateVer); //Standing orders string path = string.Format("/lr:DataDistributionPlan/lr:CoastalStateStandingOrders/lr:StandingOrder[@contractingGovernmentID='{0}']", kv.Key.LRITId); XmlNode standingOrder = ddpxml.SelectSingleNode(path, nsmanager); if (standingOrder != null && !String.IsNullOrEmpty(standingOrder.InnerText)) { foreach (string area in standingOrder.InnerText.Split(' ')) { int id = getPlaceId(places, area); if (id == -1) { continue; } StandingOrder so = new StandingOrder(); so.PlaceId = id; kv.Value.standingOrders.Add(so); } } //ES ACA soda.Create(kv.Value.standingOrders.ToArray()); } } catch (Exception ex) { if (context != null) { context.Dispose(); } throw new Exception("Unable to Import DDP File", ex); } finally { if (context != null) { context.Dispose(); } } return(pubDate); }
public async Task <ActionResult <MarketOrderDTO> > PostMarketOrder(long quantity, string type) { // buy/sell quantity of bitcoin at lowest/highest price available User user = Utils.GetUserFromToken(_context, Request.Headers); if (user == null) { // would rather do this inside the GetUserFromToken function, but i don't know how to do that with this framework return(Unauthorized()); } bool isBuy; IQueryable <StandingOrder> query; switch (type) { case "BUY": isBuy = true; query = from o in _context.Orders where (o.isBuy == false) && (o.status == "LIVE") && (o.user_id != user.id) orderby o.dollarRate ascending select o; break; case "SELL": isBuy = false; query = from o in _context.Orders where (o.isBuy == true) && (o.status == "LIVE") && (o.user_id != user.id) orderby o.dollarRate descending select o; break; default: return(BadRequest("type must be `BUY` or `SELL`")); } lock (OrdersController.tradeLock) { long remaining_quantity = quantity, money = 0; while (remaining_quantity > 0) { StandingOrder standingOrder = query.FirstOrDefault(); if (standingOrder == null) { break; } User anotherUser = _context.Users.Find(standingOrder.user_id); long canAfford; if (isBuy) { canAfford = user.dollars / standingOrder.dollarRate; // integer division (rounding down) } else { canAfford = user.bitcoins; } if (canAfford == 0) { break; } long amount_trade = Math.Min(remaining_quantity, Math.Min(standingOrder.remainingBitcoinAmount, canAfford)); remaining_quantity -= amount_trade; long money_trade = amount_trade * standingOrder.dollarRate; money += money_trade; standingOrder.remainingBitcoinAmount -= amount_trade; if (standingOrder.remainingBitcoinAmount == 0) { standingOrder.status = "FULFILLED"; } if (isBuy) { user.dollars -= money_trade; user.bitcoins += amount_trade; anotherUser.dollars += money_trade; } else { user.bitcoins -= amount_trade; user.dollars += money_trade; anotherUser.bitcoins += amount_trade; } _context.SaveChanges(); } long total_amount_traded = quantity - remaining_quantity; decimal average_rate = total_amount_traded == 0 ? 0 : Decimal.Divide(money, total_amount_traded); return(new MarketOrderDTO(total_amount_traded, average_rate)); } }
public async System.Threading.Tasks.Task ComplexTest() { //taken from project definition (UsersController usersController, OrdersController ordersController, HttpContext httpContext) = setupControllers(); //4 users set up balance UserDTO a = (await usersController.PostUser("A")).Value; UserDTO b = (await usersController.PostUser("B")).Value; UserDTO c = (await usersController.PostUser("C")).Value; UserDTO d = (await usersController.PostUser("D")).Value; httpContext.Request.Headers["token"] = a.token; await usersController.PutBalance("BTC", 1); httpContext.Request.Headers["token"] = b.token; await usersController.PutBalance("BTC", 10); httpContext.Request.Headers["token"] = c.token; await usersController.PutBalance("USD", 250000); httpContext.Request.Headers["token"] = d.token; await usersController.PutBalance("USD", 300000); // user a tries to post order, doesn't have enough tokens, gets more tokens and posts order again, this time succesfully httpContext.Request.Headers["token"] = a.token; var ao1 = (await ordersController.PostOrder(10, "SELL", 10000)).Result as BadRequestObjectResult; Assert.AreEqual(400, ao1.StatusCode); Assert.AreEqual("Not enough assets on account for this order", ao1.Value); await usersController.PutBalance("BTC", 10); StandingOrder ao2 = (await ordersController.PostOrder(10, "SELL", 10000)).Value; // B makes a standing order too httpContext.Request.Headers["token"] = b.token; var bo1 = (await ordersController.PostOrder(10, "SELL", 20000)).Value; // C makes a market order - check average price, balance httpContext.Request.Headers["token"] = c.token; MarketOrderDTO cm1 = (await ordersController.PostMarketOrder(15, "BUY")).Value; Assert.AreEqual(13333.33, (double)cm1.avg_price, 0.004); // there is no AreEqual for decimal UserBalanceDTO cb1 = (await usersController.GetBalance()).Value; Assert.AreEqual(15, cb1.bitcoins); Assert.AreEqual(50000, cb1.dollars); //check existing standing orders httpContext.Request.Headers["token"] = a.token; ao2 = (await ordersController.GetOrder(ao2.id)).Value; Assert.AreEqual("FULFILLED", ao2.status); Assert.AreEqual(0, ao2.remainingBitcoinAmount); httpContext.Request.Headers["token"] = b.token; bo1 = (await ordersController.GetOrder(bo1.id)).Value; Assert.AreEqual("LIVE", bo1.status); Assert.AreEqual(5, bo1.remainingBitcoinAmount); // D makes a standing buy order thats cheaper than what B is selling for, and then makes another one but doesn't have enough money remaining httpContext.Request.Headers["token"] = d.token; StandingOrder do1 = (await ordersController.PostOrder(20, "BUY", 10000)).Value; var do2 = (await ordersController.PostOrder(10, "BUY", 25000)).Result as BadRequestObjectResult; Assert.AreEqual(400, do2.StatusCode); Assert.AreEqual("Not enough assets on account for this order", do2.Value); // D then deletes the first order and remakes the second order, which is partially fulfilled do1 = (await ordersController.DeleteOrder(do1.id)).Value; Assert.AreEqual("CANCELLED", do1.status); StandingOrder do3 = (await ordersController.PostOrder(10, "BUY", 25000)).Value; Assert.AreEqual("LIVE", do3.status); Assert.AreEqual(5, do3.remainingBitcoinAmount); // since D bought 5 bitcoin at 20k (older standing order), they should have 100k less than at beginning, and onother 125k reserved their standing order UserBalanceDTO db = (await usersController.GetBalance()).Value; Assert.AreEqual(300000 - 100000 - 125000, db.dollars); Assert.AreEqual(5, db.bitcoins); // now checking balance and standing order status of B httpContext.Request.Headers["token"] = b.token; bo1 = (await ordersController.GetOrder(bo1.id)).Value; Assert.AreEqual("FULFILLED", bo1.status); UserBalanceDTO bb = (await usersController.GetBalance()).Value; Assert.AreEqual(0, bb.bitcoins); Assert.AreEqual(200000, bb.dollars); }
public async Task <ActionResult <StandingOrder> > PostOrder(long quantity, string type, long limit_price) { // in this endpoint, we simultaneously create a standing order and try to fulfill it similarly to market order User user = Utils.GetUserFromToken(_context, Request.Headers); if (user == null) { // would rather do this inside the GetUserFromToken function, but i don't know how to do that with this framework return(Unauthorized()); } bool isBuy; IQueryable <StandingOrder> query; lock (OrdersController.tradeLock) { switch (type) { case "BUY": isBuy = true; query = from o in _context.Orders where (o.isBuy == false) && (o.status == "LIVE") && (o.dollarRate <= limit_price) && (o.user_id != user.id) orderby o.dollarRate ascending select o; break; case "SELL": isBuy = false; query = from o in _context.Orders where (o.isBuy == true) && (o.status == "LIVE") && (o.dollarRate >= limit_price) && (o.user_id != user.id) orderby o.dollarRate descending select o; break; default: return(BadRequest("type must be `BUY` or `SELL`")); } var debug = query.ToQueryString(); if ((isBuy && quantity * limit_price > user.dollars) || (!isBuy && quantity > user.bitcoins)) { return(BadRequest("Not enough assets on account for this order")); } long remaining_quantity = quantity; while (remaining_quantity > 0) { StandingOrder anotherOrder = query.FirstOrDefault(); if (anotherOrder == null) { break; } User anotherUser = _context.Users.Find(anotherOrder.user_id); long amount_trade = Math.Min(anotherOrder.remainingBitcoinAmount, remaining_quantity); remaining_quantity -= amount_trade; long money_trade = amount_trade * anotherOrder.dollarRate; // we trade at the limit of the older standing order (to mimic first doing market order up to limit and then posting a standing order) if (isBuy) { user.bitcoins += amount_trade; user.dollars -= money_trade; anotherUser.dollars += money_trade; } else { user.bitcoins -= amount_trade; user.dollars += money_trade; anotherUser.bitcoins += amount_trade; } anotherOrder.remainingBitcoinAmount -= amount_trade; if (anotherOrder.remainingBitcoinAmount == 0) { anotherOrder.status = "FULFILLED"; } _context.SaveChanges(); } StandingOrder order = order = new StandingOrder(user.id, isBuy, remaining_quantity, quantity, limit_price, remaining_quantity == 0 ? "FULFILLED" : "LIVE", DateTime.UtcNow); // reserve assets if (isBuy) { user.dollars -= remaining_quantity * limit_price; } else { user.bitcoins -= remaining_quantity; } _context.Orders.Add(order); _context.SaveChanges(); return(order); } }