Skip to content

Instantly share code, notes, and snippets.

@Kailang
Last active October 28, 2017 20:04
Show Gist options
  • Save Kailang/fccce38b7270b8fdc23d425520fff993 to your computer and use it in GitHub Desktop.
Save Kailang/fccce38b7270b8fdc23d425520fff993 to your computer and use it in GitHub Desktop.
var debug = require('debug')('gskse:corpController');
var Friend = getModel('friend');
var Corp = getModel('corp');
var News = getModel('news');
var Order = getModel('order');
var Stock = getModel('stock');
var Report = getModel('report');
var sharp = require('sharp');
var Rusha = require('rusha');
var rusha = new Rusha();
exports.register = function(friend, name, desc, symbol, locale, avatarData) {
var self = this;
return Promise.resolve(rusha.digestFromBuffer(avataData)).then(sha1 => {
self.avatar = sha1 + '.jpeg';
return sharp(avataData).resize(gskse.avatarWidth, gskse.avatarHeight).jpeg().toFile(getUploadPath(self.avatar));
}).then(info => {
return Corp.create({
avatar: self.avatar,
name: req.body.name,
desc: req.body.desc,
symbol: req.body.symbol,
locale: req.body.locale,
cash: 0,
revenue: 0,
stock: 0,
offer: 0,
price: 1, // before ipo
life: gskse.emptyObjectId,
ceo: self.friend.id,
founder: self.friend.id,
founded: Date.now(),
ipo: new Date(0),
is_public: false,
is_offering: false,
});
}).then(corp => {
self.corp = corp;
return friendController.register(symbol + locale.toUpperCase().replace(/[^A-Z]/, ''), '', self.avataData);
}).then(friend => {
self.corp.life = friend._id;
return self.corp.save();
});
};
exports.findCorp = function(symbol, locale) {
return Corp.findOne({ symbol: symbol, locale: locale });
};
exports.findStock = function(friend, corp) {
return Stock.findOne({ friend: friend._id, corp: corp._id, quantity: { $gt: 0 } });
};
exports.findReports = function(corp) {
return Report.find({ corp: corp._id }).sort('-date').limit(5);
};
exports.findNewses = function(corp) {
return News.find({ corp: corp._id }).sort('-click -posted');
};
exports.findHolderStocks = function(corp) {
return Stock.find({ corp: corp._id, quantity: { $gt: 0 } }).populate('friend');
};
var findStockOrCreateOne = function(friend, corp) {
findStockOrCreateOneById(friend._id, corp._id);
};
var findStockOrCreateOneById = function(friendId, corpId) {
return Stock.findOne({ friend: friendId, corp: corpId }).then(stock => {
if (stock) return stock;
return Stock.create({
friend: friendId,
corp: corpId,
quantity: 0,
spent: 0,
lock_up: new Date(0),
});
});
};
var createTick = function(corp, buyer, seller, quantity, price) {
return createTickById(corp._id, buyer._id, seller._id, quantity, price);
};
var createTickById = function(corpId, buyerId, sellerId, quantity, price) {
return Tick.create({
corp: corpId,
buyer: buyerId,
seller: sellerId,
quantity: quantity,
price: price,
date: Date.now(),
});
};
exports.invest = function(friend, corp, quantity) {
return Order.create({
friend: friend._id,
corp: corp._id,
quantity: quantity,
unfilled: 0,
action: 'buy',
type: 'private',
duration: 'gtc',
price: 1,
placed: Date.now(),
filled: Date.now(),
expired: gskse.getOrderExpiration('gtc'),
is_aborted: false,
}).then(order => {
debug('invest [%s] <- [%d] by [$s]', corp.name, quantity, friend.name);
return findStockOrCreateOne(friend, corp);
}).then(stock => {
corp.stock += quantity;
corp.save();
stock.quantity += quantity;
stock.spent += quantity;
stock.save();
debug('stock change [$s] now holding [%s] [%d]', friend.name, corp.name, stock.quantity);
return createTickById(corp._id, friend._id, corp.life, quantity, 1);
});
};
exports.offer = function(friend, corp, quantity, price) {
if (friend._id != corp.ceo) return Promise.reject(gskse.status.unauthoried);
if (!corp.is_public) { // ipo
corp.is_public = true;
corp.ipo = Date.now();
}
corp.price = price;
corp.offer = quantity;
corp.is_offering = true;
corp.save();
return findStockOrCreateOne(friend, corp).then(stock => {
stock.lock_up = gskse.getOfferLockUp();
return stock.save();
});
};
exports.trade = function(friend, corp, quantity, price, action, type, duration) {
var self = this;
return Order.create({
friend: friend._id,
corp: corp._id,
quantity: quantity,
unfilled: quantity,
action: action,
type: type,
duration: (type == 'market') ? 'ioc' : duration,
price: (type == 'market') ? 0 : price,
placed: Date.now(),
filled: gskse.epoch,
expired: gskse.getOrderExpiration(duration),
is_aborted: false,
}).then(order => {
self.order = order;
return matchOrder(corp);
}).catch(err => {
debug('match order abort: [%s]', err);
if (self.order.duration == 'ioc' && self.order.unfilled != 0) { // ioc order not filled, abort
self.order.is_aborted = true;
self.order.save();
}
});
};
var matchOrder = function(corp) {
var self = this;
self.corp = corp;
debug('corp [%s] [%s]', self.corp.symbol, self.corp.name);
return Promise.all([
Order.findOne({
corp: self.corp._id,
action: 'buy',
type: { $in: [ 'limit', 'market' ] },
unfilled: { $gt: 0 },
expired: { $gt: Date.now() },
is_aborted: false,
}).sort('-type -price placed'), // -type so that (market > limit) can merge to the top
Order.findOne({
corp: self.corp._id,
action: 'sell',
type: { $in: [ 'limit', 'market' ] },
unfilled: { $gt: 0 },
expired: { $gt: Date.now() },
is_aborted: false,
}).sort('-type price placed'),
Tick.findOne({
corp: self.corp._id,
}).sort('date'),
]).then(results => {
self.bid = results[0];
self.ask = results[1];
self.tick = results[2];
if (!self.bid || !self.ask) {
debug('no order');
throw 'No Order';
}
debug('bid [%d] x [%d]', self.bid.price, self.bid.quantity);
debug('ask [%d] x [%d]', self.ask.price, self.ask.quantity);
debug('last tick [%d] x [%d]', self.tick.price, self.tick.quantity);
if (self.bid.price < self.ask.price) {
debug('no trade');
throw 'No Trade';
}
return Promise.all([
Friend.findById(self.bid.friend),
Friend.findById(self.ask.friend),
findStockOrCreateOneById(self.bid.friend, self.corp._id),
findStockOrCreateOneById(self.ask.friend, self.corp._id),
]);
}).then(results => {
self.buyer = results[0];
self.seller = results[1];
self.buyerStock = results[2];
self.sellerStock = results[3];
debug('buyer [%s] with [%d]', self.buyer.name, self.buyer.cash);
debug('seller [%s] with [%d]', self.seller.name, self.seller.cash);
debug('buyer stock [%d]', self.buyerStock.quantity);
debug('seller stock [%d]', self.sellerStock.quantity);
self.quantity = Math.min(self.bid.unfilled, self.ask.unfilled);
// determine the transaction price:
if (self.bid.type == 'market' && self.ask.type == 'market') self.price = self.tick.price; // all market order, trade at previous price
else if (self.bid.type == 'market') self.price = self.ask.price; // bid is market, trade at ask
else if (self.ask.type == 'market') self.price = self.bid.price; // ask is market, trade at bid
else self.price = (self.bid.price + self.ask.price) * 0.5; // all limit order, trade at middle
self.amount = self.quantity * self.price;
debug('trade [%d] x [%d]', self.quantity, self.price);
checkViability(self.bid, self.ask, self.buyer, self.sellerStock, self.amount, self.quantity);
self.bid.unfilled -= self.quantity;
self.ask.unfilled -= self.quantity;
self.buyer.cash -= self.amount;
self.seller.cash += self.amount;
self.buyerStock.quantity += self.quantity;
self.sellerStock.quantity -= self.quantity;
self.bid.save();
self.ask.save();
self.buyer.save();
self.seller.save();
self.buyerStock.save();
self.sellerStock.save();
return createTick(self.corp, self.buyer, self.seller, self.quantity, self.price);
}).then(ignored => {
return matchOrder(corp);
});
};
var checkViability = function(bid, ask, buyer, sellerStock, buyAmount, sellQuantity) {
debug('check buy [%d] has [%d], sell [%d] has [%d]', buyAmount, buyer.cash, sellQuantity, sellerStock.quantity);
if (buyer.cash < buyAmount) { // buyer cannot paid for the stock
bid.is_aborted = true;
bid.save();
debug('buyer too poor');
throw 'Buyer Too Poor';
}
if (sellerStock.quantity < sellQuantity) { // seller cannot sell the stock
ask.is_aborted = true;
ask.save();
debug('seller too poor');
throw 'Seller Too Poor';
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment