Thank you for your interest in contributing to OpenPolyTrader! This document provides guidelines and standards for contributing to this Polymarket CLOB arbitrage automation system.
- Code of Conduct
- Development Environment
- Project Structure
- Coding Standards
- Testing Requirements
- Agent Architecture Guidelines
- Pull Request Process
- Commit Message Conventions
- Documentation Requirements
- Security Considerations
- Questions and Support
- Be respectful and constructive in all interactions
- Focus on technical merit and project goals
- Welcome newcomers and help them learn
- Respect the near-zero-risk philosophy of the project
- Node.js 20+ (required for native ESM support)
- npm (comes with Node.js)
- Git
- Docker (optional, for containerized development)
-
Fork and clone the repository:
git clone https://github.com/freshtechbro/openpolytrader.git cd openpolytrader -
Install dependencies:
npm install cd dashboard && npm install && cd ..
-
Set up environment:
cp .env.example .env # Set OPS_API_TOKEN in .env for npm run dev:ops and /ops/* auth # See docs/Development/setup.md for full setup details
-
View the root command/tool/flag index:
npm run help -
Verify setup:
npm run typecheck npm run lint npm run test
- Backend only:
npm run dev - Backend + Dashboard:
npm run dev:ops(requiresOPS_API_TOKEN) - Docker mode:
npm run dev:live
See docs/Development/setup.md for detailed setup instructions.
openpolytrader/
├── src/
│ ├── agents/ # Trading agents (9 specialized agents)
│ │ ├── dependency/ # Dependency extraction helpers
│ │ ├── execution/ # Order execution logic
│ │ ├── learning/ # RL model integration
│ │ ├── market-data/ # WebSocket data handling
│ │ ├── ops/ # Operations and health
│ │ ├── portfolio/ # Position management
│ │ ├── projection/ # Frank-Wolfe projection agent
│ │ ├── risk/ # Risk evaluation
│ │ ├── scanner/ # Opportunity detection
│ │ └── signal/ # Signal aggregation
│ ├── core/ # Supervisor, MessageBus, EventStore
│ ├── domain/ # Business logic, types, gates
│ ├── services/ # External API clients
│ ├── config/ # Environment and policy config
│ ├── venues/ # VenueAdapter abstraction
│ ├── telemetry/ # Metrics and monitoring
│ ├── security/ # Auth and secrets
│ ├── api/ # Fastify ops endpoints
│ ├── db/ # SQLite migrations
│ └── main.ts # Entry point
├── dashboard/ # React/Vite ops dashboard
├── tests/ # Vitest suites + fixtures
│ ├── unit/ # Unit tests
│ ├── integration/ # Reserved integration suites (currently empty)
│ └── fixtures/ # Test fixtures
├── docs/ # Documentation
│ ├── ARCHITECTURE.md
│ ├── Development/
│ ├── Operations/
│ └── Testing/
└── scripts/ # Utility scripts
- Target: ES2022 with NodeNext modules
- Strict mode: Enabled (no
any, no@ts-ignore) - Module system: ESM (
"type": "module"in package.json)
// ✅ Good: Explicit types, strict mode
interface TradeOpportunity {
marketId: string;
yesPrice: number;
noPrice: number;
edge: number;
}
function calculateProfit(op: TradeOpportunity): number {
return op.edge * 100; // percentage
}
// ❌ Bad: Implicit any, ts-ignore
// @ts-ignore
function badFunction(x) {
return x + 1;
}- Internal code:
camelCase - External API fields:
snake_case - Classes:
PascalCase - Interfaces:
PascalCase - Constants:
UPPER_SNAKE_CASE - Unused variables: Prefix with
_
// ✅ Good
interface OrderBookEntry {
price: number;
size: number;
}
class ExecutionAgent {
private _unusedVar: string;
processOrder(entry: OrderBookEntry): void {
// ...
}
}
// External API mapping
const apiPayload = {
market_id: marketId, // snake_case for API
order_type: 'fok'
};- Interfaces for data structures
- Classes for stateful services
- Functions for pure logic
- Zod for runtime validation
// Data interface
interface RiskConfig {
maxPositionPercent: number;
dailyDrawdownLimit: number;
}
// Runtime validation
const RiskConfigSchema = z.object({
maxPositionPercent: z.number().min(0).max(1),
dailyDrawdownLimit: z.number().min(0).max(1)
});
// Stateful service
export class RiskAgent {
constructor(private config: RiskConfig) {}
evaluate(opportunity: ArbitrageOpportunity): RiskDecision {
// Implementation
}
}Minimum 97% coverage is required for all contributions.
# Run tests with coverage
npm run test:coverage-
Unit Tests (
tests/unit/*.test.ts)- Test individual functions/classes in isolation
- Mock external dependencies with
vi.mock() - Fast, deterministic execution
-
Integration Suites (
tests/integration/*.test.ts, when present)- Reserved for targeted multi-component flows that need dedicated isolation
- Use real internal systems (don't mock unless justified)
- Isolate external integrations (Polymarket, RPC)
-
E2E Tests (
dashboard/tests/e2e/*.spec.ts)- Playwright-based UI testing
- Test critical user flows
// ✅ Good: Proper test structure
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { RiskAgent } from '../src/agents/risk/RiskAgent';
// Mock external dependency
vi.mock('../src/services/PolymarketClob', () => ({
PolymarketClob: vi.fn()
}));
describe('RiskAgent', () => {
let agent: RiskAgent;
beforeEach(() => {
agent = new RiskAgent({
maxPositionPercent: 0.8,
dailyDrawdownLimit: 0.1
});
});
afterEach(() => {
vi.clearAllMocks();
});
it('should approve valid opportunities', () => {
const opportunity = createMockOpportunity({ edge: 0.03 });
const decision = agent.evaluate(opportunity);
expect(decision.approved).toBe(true);
});
it('should reject opportunities exceeding drawdown limit', () => {
// Test implementation
});
});- Use synthetic orderbooks for CLOB gateway logic
- Use fixtures in
tests/fixtures/for reusable test data - Never use real API keys in tests
All agents follow a consistent pattern:
export class AgentName {
constructor(
private config: AgentConfig,
private dependencies: AgentDependencies
) {}
async start(): Promise<void> {
// Initialize connections, subscribe to events
}
async stop(): Promise<void> {
// Cleanup, close connections
}
private handleEvent(event: TypedEvent): void {
// Event handler
}
}Agents communicate via the typed MessageBus:
// Subscribe to events
MessageBus.on('opportunity:detected', (opportunity) => {
this.handleOpportunity(opportunity);
});
// Emit events
MessageBus.emit('risk:approved', {
opportunity,
positionSize: calculatedSize
});All state changes must be logged to EventStore:
await EventStore.append({
id: generateId(),
timestamp: Date.now(),
type: 'execution:order_placed',
data: orderDetails,
metadata: {
agent: 'ExecutionAgent',
correlationId: context.correlationId
}
});All operations must be idempotent:
// Use deterministic idempotency keys
const idempotencyKey = createHash('sha256')
.update(`${marketId}-${tokenId}-${price}-${timeBucket}`)
.digest('hex');-
Run all checks:
npm run lint npm run typecheck npm run test:coverage
-
Update documentation if needed
-
Add tests for new functionality
-
Verify no secrets in code
- Clear description of changes
- Link to related issue(s)
- Test coverage report (97%+ required)
- Documentation updates if applicable
- No breaking changes without discussion
- Automated checks must pass (CI)
- Code review by maintainer
- Address feedback promptly
- Squash commits if requested
We follow Conventional Commits:
<type>: <short summary>
- Detailed bullet points
- What was added/changed/fixed
- Why (if not obvious)
| Type | Description |
|---|---|
feat |
New feature |
fix |
Bug fix |
docs |
Documentation changes |
test |
Test additions or fixes |
refactor |
Code refactoring |
perf |
Performance improvements |
chore |
Build/config changes |
security |
Security fixes |
feat: add Kalshi venue adapter for Phase 2
- Implement VenueAdapter interface
- Add Kalshi-specific order normalization
- Include fee model in EV calculations
fix: correct orderbook depth calculation
- Use top 3 levels instead of top 1
- Add buffer multiplier for safety
test: add coverage for RiskAgent drawdown logic
- Test daily drawdown limit enforcement
- Test edge cases for partial fills
- JSDoc for all public methods
- Type annotations for all functions
- Inline comments for complex logic
/**
* Evaluates an arbitrage opportunity against risk parameters.
*
* @param opportunity - The detected arbitrage opportunity
* @returns Risk decision with approval status and sizing
* @throws Never throws; returns rejected decision on error
*/
async evaluateOpportunity(
opportunity: ArbitrageOpportunity
): Promise<RiskDecision> {
// Implementation
}Update relevant docs for architectural changes:
docs/ARCHITECTURE.md- System architecturedocs/Development/architecture-decisions.md- New ADRsAGENTS.md- Project knowledge base
Keep README.md current with:
- New features
- Changed commands
- New dependencies
.envfiles- API keys or secrets
- Private keys
- Passwords
// ✅ Good: Environment variables for secrets
const apiKey = process.env.POLYMARKET_API_KEY;
if (!apiKey) {
throw new Error('POLYMARKET_API_KEY not set');
}
// ❌ Bad: Hardcoded secrets
const apiKey = 'actual-secret-key';Never log sensitive data:
// ✅ Good: Redact sensitive fields
logger.info('Order placed', {
marketId: order.marketId,
size: order.size,
// apiKey is NOT logged
});
// ❌ Bad: Logging secrets
logger.info('API call', { apiKey, secret });See docs/Operations/security.md for full security procedures.
- Documentation: Check the docs/ folder first
- AGENTS.md: Review project knowledge base
- Issues: Search existing GitHub issues
- Discussions: Use GitHub Discussions for questions
Include:
- Steps to reproduce
- Expected vs actual behavior
- Environment details (Node version, OS)
- Relevant logs (redact secrets!)
- Describe the use case
- Explain the benefit to the project
- Consider implementation complexity
Contributors will be recognized in:
- Release notes
- CONTRIBUTORS file
- Project documentation
Thank you for helping make OpenPolyTrader better!