Skip to content

Commit

Permalink
initial commit - javascript robot market agents
Browse files Browse the repository at this point in the history
existing tests pass - need more tests
  • Loading branch information
DrPaulBrewer committed May 10, 2016
0 parents commit 6e1e63a
Show file tree
Hide file tree
Showing 5 changed files with 387 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
coverage
*~
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: node_js

node_js:
- stable

install:
- npm install

script:
- npm run cover

after_script: "cat coverage/lcov.info | node_modules/coveralls/bin/coveralls.js"

190 changes: 190 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
const util = require('util');
const EventEmitter = require('events').EventEmitter;
const RandomJS = require('random-js');
const ProbJS = require('prob.js');


var _nextId = 1;
function nextId(){ return _nextId++; }

function neverWake(){ return 0; }

function poissonWake(){
var delta = ProbJS.exponential(this.rate)();
return this.wakeTime+delta;
}

var Agent = function(options){
EventEmitter.call(this);
var defaults = {
id: nextId(),
description: 'default do nothing agent',
inventory: {},
endowment: {},
wakeTime: 0,
rate: 1,
period: 0,
nextWake: poissonWake
};
Object.assign(this, defaults, options);
this.init();
};

util.inherits(Agent, EventEmitter);

Agent.prototype.getPeriodNumber = function(){
if (typeof(this.period)==='number') return this.period;
if (typeof(this.period)==='object') return this.period.number;
};

Agent.prototype.resetInventory = function(newInventory){
var amounts = newInventory || this.endowment;
var goods;
var i,l,g;
if (Array.isArray(amounts))
amounts = amounts[this.getPeriodNumber()];
if (typeof(amounts)==='function')
amounts = amounts.call(this);
if (typeof(amounts)==='object'){
goods = Object.keys(amounts);
for(i=0,l=goods.length;i<l;++i){
g = goods[i];
this.inventory[g] = amounts[g];
}
}
};

Agent.prototype.init = function(){
this.resetInventory();
this.wakeTime = this.nextWake();
};

Agent.prototype.initPeriod = function(period, info){
this.period = period;
this.wakeTime = (typeof(period)==='object')? (period.startTime): 0;
this.init();
this.emit('initPeriod', info);
};

Agent.prototype.endPeriod = function(info){
this.emit('endPeriod', info);
}

Agent.prototype.wake = function(info){
this.emit('wake', info);
this.wakeTime = this.nextWake();
};

Agent.prototype.transfer = function(myTransfers){
var goods, i, l;
if (myTransfers){
goods = Object.keys(myTransfers);
for(i=0,l=goods.length; i<l; ++i){
if (this.inventory[goods[i]])
this.inventory[goods[i]] += myTransfers[goods[i]]
else
this.inventory[goods[i]] = myTransfers[goods[i]];
}
}
};

ziAgent = function(options){
// from an idea developed by Gode and Sunder in a series of economics papers
var defaults = {
description: 'Gode and Sunder style ZI Agent',
markets: {},
values: {},
costs: {},
minPrice: 0,
maxPrice: 1000
};
Agent.call(this, Object.assign({}, defaults, options));
ziAgent.on('wake', function(){
var names = Object.keys(this.markets);
var i,l;
var vals, costs;
var unitValue, unitCost;
var good;
var myPrice;
for(i=0,l=names.length;i<l;++i){
good = names[i];
vals = this.values[good];
costs = this.costs[good];
if (vals && (vals.length>0) && (this.inventory[good]>=0)){
unitValue = vals[this.inventory[good]];
if (unitValue>0){
myPrice = ProbJS.uniform(this.minPrice, unitValue);
if (this.integer) myPrice = Math.floor(myPrice);
this.bid(good, myPrice);
}
}
else if (costs && (costs.length>0) && (this.inventory[good]<=0)){
unitCost = costs[-this.inventory[good]];
if (unitCost>0){
myPrice = ProbJS.uniform(unitCost, this.maxPrice);
this.ask(good, myPrice);
}
}
}
});
};

util.inherits(ziAgent, Agent);

Pool = function(){
this.agents = [];
};

Pool.prototype.push = function(agent){
this.agents.push(agent);
};

Pool.prototype.next = function(){
var tMin=1e20, i=0, l=this.agents.length, A=this.agents, t=0, result=0;
for(; i<l; i++){
t = A[i].wakeTime;
if ( (t>0) && (t<tMin) ){
result = A[i];
tMin = t;
}
}
return result;
}

Pool.prototype.period = function(untilTime, cb){
var that = this;
if (typeof(cb)!=='function')
throw new Error("Pool.period: Callback function undefined")
var loop = function(){
var nextAgent = that.next();
if (!nextAgent) cb('Error: Pool.next() undefined');
var tNow = nextAgent.wakeTime;
if (tNow > untilTime){
cb(false);
} else {
nextAgent.wake();
setTimeout(loop,0);
}
};
setTimeout(loop, 0);
};

Pool.prototype.syncPeriod = function(untilTime){
var nextAgent = this.next();
while (nextAgent.wakeTime < untilTime){
nextAgent.wake();
nextAgent = this.next();
}
};

Pool.prototype.settleTrades = function(tradeSpec){
};

module.exports = {
Agent: Agent,
ziAgent: ziAgent,
Pool: Pool
};



36 changes: 36 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "market-agents",
"version": "0.0.0",
"description": "pool of heterogeneous robot trading agents for economic or financial simulations",
"main": "index.js",
"scripts": {
"test": "node_modules/.bin/mocha --reporter spec --slow 50 --timeout 2000",
"cover": "node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha -- -R spec test/*"
},
"repository": {
"type": "git",
"url": "git+https://github.com/drpaulbrewer/market-agents.git"
},
"keywords": [
"robot-trading",
"hft",
"market-simulation",
"financial-simulation"
],
"author": "drpaulbrewer@eaftc.com",
"license": "MIT",
"bugs": {
"url": "https://github.com/drpaulbrewer/market-agents/issues"
},
"homepage": "https://github.com/drpaulbrewer/market-agents#readme",
"devDependencies": {
"coveralls": "^2.11.9",
"istanbul": "^0.4.3",
"mocha": "^2.4.5",
"should": "^8.3.1"
},
"dependencies": {
"prob.js": "^1.0.6",
"random-js": "^1.0.8"
}
}
145 changes: 145 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
var assert = require('assert');
var should = require('should');
const MarketAgents = require("../index.js");
const Agent = MarketAgents.Agent;
const ziAgent = MarketAgents.ziAgent;
const Pool = MarketAgents.Pool;

describe('MarketAgents', function(){

it('should be an object', function(){
MarketAgents.should.be.type('object');
});

it('should have properties Agent, ziAgent, Pool', function(){
MarketAgents.should.have.properties('Agent','ziAgent','Pool');
});

});

describe('new Agent', function(){
it('should have properties id, description, inventory, endowment, wakeTime, rate, nextWake, period with proper types',
function(){
var myAgent = new Agent();
myAgent.should.be.type('object');
myAgent.should.have.properties('id','description','inventory','endowment','wakeTime','rate','nextWake','period');
myAgent.id.should.be.type('number');
myAgent.description.should.be.type('string');
myAgent.inventory.should.be.type('object');
myAgent.endowment.should.be.type('object');
myAgent.wakeTime.should.be.type('number');
myAgent.rate.should.be.type('number');
myAgent.nextWake.should.be.type('function');
myAgent.period.should.be.type('number');
});

it('should have ascending default id number', function(){
var agent1 = new Agent();
var agent2 = new Agent();
assert.ok(agent1.id>0);
assert.ok(agent2.id>0);
assert.ok(agent2.id>agent1.id);
});

it('test 1000 wakes, should have ascending wake times', function(){
var agent = new Agent();
var i,l;
var t0,t1;
var wakes = 0;
agent.on('wake',function(){ wakes++; });
for(i=0,l=1000;i<l;i++){
t0 = agent.wakeTime;
agent.wake();
t1 = agent.wakeTime;
assert.ok(t1>t0);
}
assert.ok(wakes===1000);
});

it('test 1000 wakes, agent with rate 2 should use between 1/3 and 2/3 the time of agent with rate 1', function(){
var agent1 = new Agent();
var agent2 = new Agent({rate: 2});
var i,l;
var wakes1=0, wakes2=0;
agent1.on('wake', function(){ wakes1++; });
agent2.on('wake', function(){ wakes2++; });
for(i=0,l=1000;i<l;++i){
agent1.wake();
agent2.wake();
}
assert.ok(wakes1===1000);
assert.ok(wakes2===1000);
assert.ok(agent2.wakeTime>(0.33*agent1.wakeTime));
assert.ok(agent2.wakeTime<(0.67*agent1.wakeTime));
});

it('agent.getPeriodNumber() should initially return 0', function(){
var agent = new Agent();
agent.getPeriodNumber().should.equal(0);
});

describe('agent-period cycle interactions with numeric period', function(){
function setup(){
var someMoneyNoX = {money: 1000, X:0};
var agent0 = new Agent({endowment: someMoneyNoX});
var agent1 = new Agent({endowment: someMoneyNoX});
return [agent0, agent1];
}
it('should initially be at period 0', function(){
var agents = setup();
agents[0].getPeriodNumber().should.equal(0);
agents[1].getPeriodNumber().should.equal(0);
});
it('agents 1,2 should show initial inventory 0 X 1000 Money', function(){
var agents = setup();
assert.ok(agents[0].inventory.X===0);
assert.ok(agents[0].inventory.money===1000);
assert.ok(agents[1].inventory.X===0);
assert.ok(agents[1].inventory.money===1000);
});
it('after Transfer of +1X, -500 Money, agent 1 should show 1 X, 500 Money; agent 1 endowment, agent 2 unaffected', function(){
var agents = setup();
var buyOneXFor500 = {money: -500, X:1 };
agents[0].transfer(buyOneXFor500);
assert.ok(agents[0].inventory.X===1);
assert.ok(agents[0].inventory.money===500);
assert.ok(agents[0].endowment.X===0);
assert.ok(agents[0].endowment.money===1000);
assert.ok(agents[1].inventory.X===0);
assert.ok(agents[1].inventory.money===1000);
});
it('agents should indicate period 1 when set', function(){
var agents = setup();
var buyOneXFor500 = {money: -500, X:1 };
agents[0].transfer(buyOneXFor500);
agents.forEach(function(a){ a.initPeriod(1) });
assert.ok(agents[0].getPeriodNumber()===1);
assert.ok(agents[1].getPeriodNumber()===1);
});
it('agents 1,2, should show initial inventory 0 X, 1000 Money for Period 1', function(){
var agents = setup();
var buyOneXFor500 = {money: -500, X:1 };
agents[0].transfer(buyOneXFor500);
agents.forEach(function(a){ a.initPeriod(1) });
assert.ok(agents[0].inventory.X===0);
assert.ok(agents[0].inventory.money===1000);
assert.ok(agents[1].inventory.X===0);
assert.ok(agents[1].inventory.money===1000);
});
it('agent 1 given 2Y should still have 2Y after period reset as Y amount unspecified in endowment', function(){
var agents = setup();
var give1Y = {Y:1};
agents[0].transfer(give1Y);
assert.ok(agents[0].inventory.Y===1);
agents.forEach(function(a){a.initPeriod(1) });
assert.ok(agents[0].inventory.X===0);
assert.ok(agents[0].inventory.money===1000);
assert.ok(agents[0].inventory.Y===1);
assert.ok(agents[1].inventory.X===0);
assert.ok(agents[1].inventory.money===1000);
});

});


});

0 comments on commit 6e1e63a

Please sign in to comment.