Installation
composer require blazar/trading-engine
Add the service provider to config/app.php:
'providers' => [
// ...
Blazar\TradingEngine\TradingEngineServiceProvider::class,
],
Publish Config & Migrations
php artisan vendor:publish --provider="Blazar\TradingEngine\TradingEngineServiceProvider"
php artisan migrate
Review config/trading-engine.php for core settings (e.g., default_engine, strategy_paths).
First Use Case: Basic Strategy Execution
use Blazar\TradingEngine\Facades\TradingEngine;
// Register a strategy (e.g., in a service provider)
TradingEngine::registerStrategy('my_strategy', \App\Strategies\MyStrategy::class);
// Execute a strategy
$result = TradingEngine::run('my_strategy', ['symbol' => 'AAPL', 'amount' => 100]);
config/trading-engine.php: Core configuration (strategy paths, engines).app/Strategies/: Default directory for strategy classes (configurable).database/migrations/: Schema for strategies, orders, and executions tables.Workflow:
Define a Strategy Class
Extend Blazar\TradingEngine\Contracts\Strategy and implement execute():
namespace App\Strategies;
use Blazar\TradingEngine\Contracts\Strategy;
class MovingAverageCrossover implements Strategy {
public function execute(array $params) {
// Fetch data, compute signals, return orders
return [
'action' => 'buy',
'symbol' => $params['symbol'],
'quantity' => 5,
];
}
}
Register the Strategy
// In a service provider or via config
TradingEngine::registerStrategy('macrossover', \App\Strategies\MovingAverageCrossover::class);
Parameterize Strategies
Pass dynamic inputs via run():
$params = [
'symbol' => 'BTC-USD',
'fast_window' => 10,
'slow_window' => 20,
];
TradingEngine::run('macrossover', $params);
Integration with Laravel Queues
Use the Order model to queue orders for execution:
use Blazar\TradingEngine\Models\Order;
Order::create([
'strategy_id' => 'macrossover',
'symbol' => 'ETH-USD',
'quantity' => 2,
'params' => ['fast_window' => 12],
'status' => 'pending',
]);
// Process orders via a queue worker
php artisan queue:work
Webhook Listeners
Extend Blazar\TradingEngine\Events\OrderExecuted to react to fills:
use Blazar\TradingEngine\Events\OrderExecuted;
Event::listen(OrderExecuted::class, function ($event) {
// Log, notify, or trigger follow-up actions
});
Override Default Engine
Configure a custom engine in config/trading-engine.php:
'engines' => [
'simulated' => \Blazar\TradingEngine\Engines\SimulatedEngine::class,
'live' => \App\Engines\LiveBrokerEngine::class, // Your implementation
],
Then use it:
TradingEngine::setEngine('live')->run('my_strategy', $params);
Create a Custom Engine
Implement Blazar\TradingEngine\Contracts\Engine:
namespace App\Engines;
use Blazar\TradingEngine\Contracts\Engine;
class LiveBrokerEngine implements Engine {
public function executeOrder(array $order) {
// Integrate with a real broker API
return $broker->placeOrder($order);
}
}
Mock the Engine Use Laravel’s mocking to test strategies in isolation:
$engine = Mockery::mock(\Blazar\TradingEngine\Contracts\Engine::class);
$engine->shouldReceive('executeOrder')
->once()
->with(['symbol' => 'AAPL', 'action' => 'buy']);
TradingEngine::setEngine($engine);
$strategy = new \App\Strategies\MyStrategy();
$strategy->execute(['symbol' => 'AAPL']);
Unit Test Example
public function test_strategy_execution() {
$this->partialMock(TradingEngine::class, function ($mock) {
$mock->shouldReceive('run')
->with('macrossover', ['symbol' => 'BTC-USD'])
->andReturn(['status' => 'filled']);
});
$result = TradingEngine::run('macrossover', ['symbol' => 'BTC-USD']);
$this->assertEquals('filled', $result['status']);
}
Strategy Registration Timing
boot() of a provider:
public function boot() {
TradingEngine::registerStrategy('my_strategy', MyStrategy::class);
}
Missing Database Tables
Order::create() or Execution logging will fail.php artisan migrate after publishing config.Circular Dependencies in Strategies
app()->make()). Use dependency injection via constructor.public function __construct(private OrderRepository $orders) {}
Engine-Specific Quirks
SimulatedEngine uses in-memory storage. For persistence, switch to a custom engine.SimulatedEngine to log to the database:
class DatabaseSimulatedEngine extends \Blazar\TradingEngine\Engines\SimulatedEngine {
public function executeOrder(array $order) {
$result = parent::executeOrder($order);
\Blazar\TradingEngine\Models\Execution::create([
'order_id' => $order['id'],
'result' => $result,
]);
return $result;
}
}
Log Strategy Inputs/Outputs Add logging to strategies:
\Log::debug('Strategy params', ['params' => $params, 'result' => $result]);
Inspect Queue Jobs
Check failed jobs in .env:
QUEUE_CONNECTION=database
Then inspect failed_jobs table.
Enable Engine Debugging Temporarily override the engine to log all calls:
TradingEngine::setEngine(new class implements \Blazar\TradingEngine\Contracts\Engine {
public function executeOrder(array $order) {
\Log::info('Order executed', $order);
return ['status' => 'debugged'];
}
});
Custom Strategy Metadata Add metadata to strategies via an interface:
interface MetadataAwareStrategy {
public function metadata(): array;
}
Then extend the registerStrategy method to store metadata.
Pre/Post-Execution Hooks Use Laravel events to intercept strategy execution:
// In a service provider
Event::listen(\Blazar\TradingEngine\Events\StrategyExecuting::class, function ($event) {
// Validate params, log, etc.
});
Strategy Performance Metrics Track execution time and success rate:
// In a custom engine
public function executeOrder(array $order) {
$start = microtime(true);
$result = parent::executeOrder($order);
\Blazar\TradingEngine\Models\Execution::create([
'duration_ms' => (microtime(true) - $start) * 1000,
'success' => $result['status'] === 'filled',
]);
return $result;
}
Multi-Tenancy Support
Scope strategies/orders to tenants by extending the Order model:
use Stancl\Tenancy\Contracts\Tenant;
class Order extends \Blazar\TradingEngine\Models\Order {
public function tenant(): Tenant {
return $this->strategy->tenant;
}
}
How can I help you explore Laravel packages today?